2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架。
2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版。
Rod Johnson 的学历 , 他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。
Spring理念 : 使现有技术更加实用 。本身可以认为是一个大杂烩 ,,就是整合了现有的框架技术
优点
- Spring是一个开源免费的框架 , 容器 .
- Spring是一个轻量级的框架 , 非侵入式的 .
- 控制反转 IoC , 面向切面 Aop
- 对事物的支持 , 对框架的支持
Spring是一个轻量级的控制反转 ( IoC ) 和面向切面 ( AOP ) 的容器(框架)
**
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory ,它是工厂模式的实现。BeanFactory 使用控制反转( IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中
Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
IOC
ioc是一个容器,无论依赖注入还是自动装配的bean中都被保存在ioc容器中,被spring管理和使用
- 容器启动会实例所有的bean
- autowire自动装配时,是从容器中寻找合适的bean
- getbean也是去容器中找bean实例
- 容器是一个map,map中保持好所有创建的bean,并提供给外界使用
依赖注入DI
依赖 : 指 bean 对象所依赖的资源
注入 : 指 bean 对象所依赖的资源 (属性值等),由容器来设置和装配
反射实现对象的创建,在xml文件中,当有一个bean时,xml会自动的生成一个对象,供java程序去使用,bean属性的赋值是通过调用对象的set方法进行赋值。
<!--
bean可以注册一个组件(对象、类)
class:组件的全类名
id:对象的唯一标识
-->
<bean id="person01" class="com.spring.ioc.Person" >
<property name="age" value="19"></property>
<property name="name" value="张三"></property>
</bean>
通过class对象来获取xml中的对象
<bean id="person02" class="com.spring.ioc.Person" >
<property name="age" value="19"></property>
<property name="name" value="李四"></property>
</bean>
//当xml文件中只有一个bean时,通过class对象来获得bean,但当bean有多个时,ioc.xml文件无法正常匹配
//No qualifying bean of type 'com.spring.ioc.Person' available:
//expected single matching bean but found 2: person01,person02
Person bean = ioc.getBean(Person.class);
当有多个bean时,如果想用class对象获取对象,必须通过指定id名来获取
//通过class对象获得必须指定响应的name值
Person person02 = ioc.getBean("person02", Person.class);
System.out.println(person02);
构造器注入
通过有参构造器赋值
<bean id="person03" class="com.spring.ioc.Person">
<!-- 通过有参构造器来创建对象并赋值 -->
<constructor-arg name="name" value="王五"></constructor-arg>
<constructor-arg name="age" value="20"></constructor-arg>
</bean>
省略name属性赋值
<!-- 省略name属性还赋值 -->
<bean id="person04" class="com.spring.ioc.Person">
<!-- 不指定响应name值,就必须按照顺序传入-->
<constructor-arg value="小米"></constructor-arg>
<!-- 如果顺序不正确时,可以使用index来指定参数的位置-->
<constructor-arg value="16"></constructor-arg>
<!-- 如果两个构造器的参数相同时,可以指定type的类型来使得参数的传入正确 -->
</bean>
使用p名称空间注入
<!-- 使用p名称来为bean赋值-->
<!-- 名称空间:xml中防止标签重复 -->
<bean id="person05" class="com.spring.ioc.Person" p:name="p名称" p:age="12"></bean>
Set注入
为复杂属性赋值
<bean id="car01" class="com.spring.factory.Car">
<property name="carName" value="宝马"></property>
<property name="carNum" value="123123"></property>
<property name="length" value="200"></property>
</bean>
<bean id="person01" class="com.spring.ioc.Person">
<!--为赋值属性的赋值都需要写在property中-->
<property name="name">
<!--为对象的属性设置为null,value=null得到的是字符串-->
<null></null>
</property>
<!-- ref表示引用的意思,引用外部的值,与xml中car01创建的对象是相同的对象,只是引用不同而已-->
<property name="car" ref="car01"></property>
<property name="car">
<bean class="com.spring.factory.Car">
<!--为复杂属性赋值-->
<property name="carName" value="法拉利"></property>
<property name="carNum" value="123123"></property>
<property name="length" value="200"></property>
</bean>
</property>
</bean>
注:内部bean中设置id是无效的,无法通过getbean获取
为list赋值
<bean id="person02" class="com.spring.ioc.Person">
<property name="list">
<list>
<value>1</value>
<value>2</value>
</list>
</property>
</bean>
为map赋值
<property name="map">
<map>
<!--entry表示一个键值对-->
<entry key="key01" value="mao01"></entry>
<entry key="key02" value-ref="car01"></entry>
<entry key="key03">
<bean class="com.spring.factory.Car">
<property name="carName" value="东风"></property>
</bean>
</entry>
</map>
</property>
为properties配置类赋值
<property name="properties">
<props>
<!--prop都是字符串,以k=v形式-->
<prop key="username" >root</prop>
<prop key="password">123456</prop>
</props>
</property>
使用util名称空间
<!--使用util名称空间赋值-->
<util:map id="myMap">
<!--entry表示一个键值对-->
<entry key="key01" value="mao01"></entry>
<entry key="key02" value-ref="car01"></entry>
<entry key="key03">
<bean class="com.spring.factory.Car">
<property name="carName" value="东风"></property>
</bean>
</entry>
</util:map>
级联属性赋值
<!--级联属性赋值:属性的属性-->
<bean id="person03" class="com.spring.ioc.Person">
<property name="car" ref="car01"></property>
<property name="car.carName" value="奥迪"></property>
</bean>
继承实现bean中配置信息的重用,但不是同一对象
<!--继承实现bean中配置信息的重用-->
<bean id="person04" class="com.spring.ioc.Person" >
<property name="age" value="19"></property>
<property name="name" value="张三"></property>
</bean>
<bean id="person05" parent="person04">
<property name="name" value="李四"></property>
<property name="car" ref="car01"></property>
</bean>
abstract属性创建一个模板bean
<!--不能创建一个关于该bean的一个实例,只能通过继承实现-->
<bean id="person04" class="com.spring.ioc.Person" abstract="true">
<property name="age" value="19"></property>
<property name="name" value="张三"></property>
</bean>
bean之间的依赖
depend-on:
- 指定相应的bean,会按照从左到右一次创建对象
bean的作用域
prototype
<bean id="person06" class="com.spring.ioc.Person" scope="singleton"></bean>
<bean id="person07" class="com.spring.ioc.Person" scope="prototype"></bean>
静态工厂和实例工厂
静态工厂
<!--静态工厂
factory-method:指定工厂方法
constructor-arg:可以为方法指定参数
-->
<bean id="car01" class="com.spring.factory.CarStaticFactory" factory-method="createCar">
<!-- constructor-arg 可以为方法指定参数-->
<constructor-arg value="宝马"></constructor-arg>
</bean>
实例工厂
<!--实例工厂
1.需要先创建一个实例工厂
-->
<bean id="carfactory" class="com.spring.factory.CarInstanceFactory"></bean>
<!--
2.factory-bean:指定哪个实例工厂bean
3.factory-method:指定实例工厂中的工厂方法
-->
<bean id="car02" class="com.spring.factory.Car" factory-bean="carfactory" factory-method="createCar">
<!--赋值-->
<constructor-arg value="奔驰"></constructor-arg>
</bean>
使用FactoryBean创建工厂
- 实现FactoryBean类,spring会认为该类是个工厂
- xml中配置好bean,则spring会自动的创建工厂
无论对象是单实例还是多实例的,factorybean只有在调用的时候才创建对象,容器启动阶段是不会创建对象的。
package com.spring.factory;
import org.springframework.beans.factory.FactoryBean;
public class MyFactoryBeanImpl implements FactoryBean<Car> {
//调用时,spring会创建一个对象
public Car getObject() throws Exception {
System.out.println("MyFactoryBeanImpl被调用了....");
Car car = new Car();
car.setCarName("大众");
return car;
}
//获取对象class
public Class<?> getObjectType() {
return Car.class;
}
//是否是单实例的,是工厂会创建一个实例,不是则可以创建多个实例
public boolean isSingleton() {
return false;
}
}
<!--FactoryBean-->
<bean id="myFactoryBeanImpl" class="com.spring.factory.MyFactoryBeanImpl"></bean>
Bean的生命周期
ioc容器中的bean:
- 单实例下,容器启动时就创建对象,容器关闭时则销毁创建bean
- 多实例下,当获取的时候,对象才会被创建;容器关闭不会销毁
自定义方法测试
<bean id="car03" class="com.spring.factory.Car" destroy-method="myDestory" init-method="myInit"></bean>
bean的后置处理器
- 实现BeanPostProcessor类
- 将MybeanPostProcessor注册进配置文件中
Car实体类
public class Car {
private int carNum;
private String carName;
private int length;
//构造器调用
public Car() {
System.out.println("car被调用");
}
//car的初始化方法
public void myInit(){
System.out.println("bean被创建");
}
//car的销毁方法
public void myDestory(){
System.out.println("bean被销毁");
}
public int getCarNum() {
return carNum;
}
public void setCarNum(int carNum) {
this.carNum = carNum;
}
public String getCarName() {
return carName;
}
public void setCarName(String carName) {
this.carName = carName;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
@Override
public String toString() {
return "Car{" +
"carNum=" + carNum +
", carName='" + carName + '\'' +
", length=" + length +
'}';
}
}
MybeanPostProcessor后置处理器类
public class MybeanPostProcessor implements BeanPostProcessor {
//初始化前调用
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("【"+beanName+"】bean初始化前调用....postProcessBeforeInitialization");
return bean;
}
//初始化后调用
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("【"+beanName+"】bean初始化后调用...postProcessAfterInitialization");
return bean;
}
}
实现BeanPostProcessor接口,ioc初始bean时会自动调用这方法。
XML配置文件信息,需要指定bean中的自定义初始化方法
<!--后置处理器-->
<bean id="postProcessor" class="com.spring.bean.MybeanPostProcessor">
</bean>
<!--bean初始化 指定自定义的初始化方法和销毁方法-->
<bean id="car" class="com.spring.factory.Car" init-method="myInit"
destroy-method="myDestory">
</bean>
测试类,获得测试结果
public class BeanTest {
ApplicationContext ioc=new ClassPathXmlApplicationContext("bean/applicationContext.xml");
@Test
public void test01(){
Car car = (Car) ioc.getBean("car");
System.out.println(car);
}
}
从结果可以看出,ioc容器被创建时,自动调用对象的构造器,在调用对象的初始化方法,而在初始化方法前后这插入后置处理器的方法。
即调用顺序:构造器—->before——>初始化——>after——>销毁方法
无论bean是否有自定义初始化方法,后置处理器都会默认有此方法。
Spring管理连接池
在maven中到入c3p0连接池jar包和mysql连接的依赖
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
在配置文件中配置好链接
<!--引用外部属性文件,spring管理连接池-->
<!--数据库连接池最好是单实例的-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"></property>
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
</bean>
获得链接池的链接
引用外部属性配置文件
需要导入spring-context的jar包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>
在bin目录下写数据库链接的配置文件
jdbc.username=root
password=123456
jdbcUrl=jdbc:mysql://localhost:3306/spring
driverClass=com.mysql.jdbc.Driver
注:username是xml中的一个关键字,为了防止冲突,因此需要将配置文件中的名字修改
<!--加载外部配置文件-->
<context:property-placeholder location="classpath:dbconfig.properties"></context:property-placeholder>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${password}"></property>
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="driverClass" value="${driverClass}"></property>
</bean>
导入context标签加载外部配置文件${username}
是什么?
它是计算机的用户名
源码文件夹和普通文件夹
resource目录下的文件,一般的存储在bin目录下,随着.class文件一起被编译
自动装配Bean
对象的赋值:
- java文件中赋值
- ioc容器中bean标签property中赋值
-
autowire
自动赋值:
default:不自动装配
- byName:以属性名作为id去ioc容器中寻找
- byType:根据类型去ioc容器中寻找
-
XML自动装配
byName的自动装配
创建一个Person类和Car类,其中Person类中有Car类的变量
Car类public class Car {
private int carNum;
private String carName;
private int length;
public int getCarNum() {
return carNum;
}
public void setCarNum(int carNum) {
this.carNum = carNum;
}
public String getCarName() {
return carName;
}
public void setCarName(String carName) {
this.carName = carName;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
@Override
public String toString() {
return "Car{" +
"carNum=" + carNum +
", carName='" + carName + '\'' +
", length=" + length +
'}';
}
}
Person类 ```java public class Person { private String name; private int age; private Car car; private List
list; private Properties properties; public Properties getProperties() {
return properties;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
public void setCar(Car car) {
this.car = car;
}
public void setList(List
list) { this.list = list;
}
public void setMap(Map<String, Object> map) {
this.map = map;
}
private Map<String,Object> map;
public Person() {
System.out.println("person对象被创建...");
}
public Car getCar() {
return car;
}
public List<Integer> getList() {
return list;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Map<String, Object> getMap() {
return map;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", car=" + car +
", list=" + list +
", properties=" + properties +
", map=" + map +
'}';
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
在xml中配置以byName的形式自动装配
```xml
<bean id="car" class="com.spring.factory.Car">
<property name="carName" value="宝马"></property>
</bean>
<bean id="person" class="com.spring.ioc.Person" autowire="byName"></bean>
获得结果
xml中Car对象被自动装配到了Person中,据此,ByName属性是根据Person中Car类的属性名作为id的,根据这个id,去寻找在ioc容器中相同类名的Bean标签进行匹配的。
如果属型名car改为car1,则Person对象中car并没有被赋值。
byType的自动装配
<bean id="car1" class="com.spring.factory.Car">
<property name="carName" value="宝马"></property>
</bean>
<bean id="person" class="com.spring.ioc.Person" autowire="byType"></bean>
byType自动装配是根据是否为同类进行匹配的,无论属性名如何,只要是同类对象,byType都能匹配上,唯一的缺点就是bean中不允许两个相同的bean,不然ioc容器会找不到对象。
Constructor的自动装配
在Person类中添加Car的构造器方法
public Person(Car car1) {
this.car1 = car1;
}
xml文件修改为constructor
<bean id="car1" class="com.spring.factory.Car">
<property name="carName" value="宝马"></property>
</bean>
<bean id="person" class="com.spring.ioc.Person" autowire="constructor"></bean>
可以看到Car对象已经赋值给Person对象中了。
constructor装配,先按照有参构造器的类型进行装配,没有就赋值问null;如果ioc容器中类型有多个的话,就以属性名作为id进行装配。
自动装配的应用:
- 主要给自定义类型进行赋值,基本类型不能使用自动装配
注解自动装配
依赖注入(DI)
首先创建UserDao、UserService、UserController类这三层MVC架构
UserDao类
UserService类@Repository
public class UserDao {
public void save(){
System.out.println("userDao...");
}
}
UserController类@Service
public class UserService {
@Autowired
private UserDao userDao;
public void save(){
userDao.save();
System.out.println("userService...");
}
}
单元测试@Controller
public class UserController {
@Autowired
private UserService userService;
public void save(){
userService.save();
System.out.println("userController...");
}
}
xml配置,开启包扫描@Test
public void test02(){
UserController controller = (UserController) ioc.getBean("userController");
controller.save();
}
显示结果<!--指定哪个包开始包扫描-->
<context:component-scan base-package="com.spring,annotation"></context:component-scan>
从结果可以看出,注解自动装配极大的减少了xml配置信息的修改,减少程序猿的工作量,但是注解在仅仅只是使用在一些不是十分重要的代码上,如果像是权限管理等这些aop切面操作时,需要单独写在xml文件中。autowire原理
首先按照类型去容器中寻找组件,找到就赋值;如果找到多个组件,会按照id去容器中寻找bean。
当有一个UserServiceExt继承UserService时,并且自动装配,如果Controller层使用的UserService变量名和这两个类名不相同时,会报错。因此需要采用注解Qualifier来对UserService赋值使用Qualifier指定使用bean的id
如果Qualifier指定其他的id,找不到则会报错public class UserController {
@Autowired
@Qualifier("userService")
private UserService userServiceExt1;
public void save(){
userServiceExt1.save();
System.out.println("userController...");
}
}
required指定某属性允许不被设置
required时autowire的属性,默认时true,设置为false时,得到的UserService变量为nullpublic class UserController {
@Autowired(required = false)
@Qualifier("userServicehh")
private UserService userServiceExt1;
public void save(){
userServiceExt1.save();
System.out.println("userController...");
}
}
SpEL
spring表达式,格式#{},可以计算、获取其他bean的属性和方法,通过id,略微介绍
注解代替XML
添加上注解,可以快速的将bean加入到ioc容器中
四大注解:
- Controller:给控制器层(servlet的包下)的组件添加
- Service:给业务层(service层)的组件添加
- Repository:给数据库层(Dao层、持久化层)的组件添加
- Component:描述spring中的bean,不属于以上三层,仅仅只是一个bean而已
注解使用
想要使用注解,就必须导入spring-context,利用包扫描机制,将给组件添加进ioc容器中
<!--指定哪个包开始包扫描-->
<context:component-scan base-package="com.spring,annotation"></context:component-scan>
创建UserDao类
@Repository
public class UserDao {
public void save(){
UserDao bean = ioc.getBean(UserDao.class);
UserDao userDao = (UserDao) ioc.getBean("userDao");
bean.save();
userDao.save();
System.out.println(bean==userDao);
}
}
单元测试查看是否创建userdao对象
public class AnnotationTest {
ApplicationContext ioc=new ClassPathXmlApplicationContext("annotation/applicationContext.xml");
@Test
public void test01(){
UserDao bean = ioc.getBean(UserDao.class);
bean.save();
}
}
结果
通过类名小写或者获得类的class来创建对象,得到的对象都是同一对象,是单实例的,并且可以看到通过注解,快速的将一个对象加入ioc容器中。
修改为多实例
@Repository
@Scope(value = "prototype")
public class UserDao {
public void save(){
System.out.println("userDao...");
}
}
指定不使用哪个类
<!--annotion:指定注解不使用
assignable:指定哪个类不适用
-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:exclude-filter type="assignable" expression="com.spring.annotation.control.UserController"/>
指定使用哪个类
<context:include-filter type="annotation" expression=""/>
和上面那个一样
泛型依赖注入
定义两个实体类
实现Dao和Service层
可以看到User和Book的Dao和Service都继承于Base
而BaseDao和BaseService是一个泛型类
public abstract class BaseDao<T> {
public abstract void save();
}
@Repository
public class BookDao extends BaseDao<Book>{
public void save(){
System.out.println("BookDao为你保存图书");
}
}
@Repository
public class UserDao extends BaseDao<User>{
public void save(){
System.out.println("UserDao为你保存用户");
}
}
UserDao和BookDao都继承实现了BaseDao的save方法
public class BaseService<T> {
@Autowired
BaseDao<T> baseDao;
public void save(){
baseDao.save();
}
}
User和Book的Dao、Service都继承于这两个
@Service
public class BookService extends BaseService<Book>{
}
@Service
public class UserService extends BaseService<User>{
}
Service类并没有写任何东西,只是继承BaseService,而在BaseService中,只是调用泛型的BaseDao类实例,并且自动装配,它继承的子类,都会继承这个自动装配,从而通过泛型,实现了User和Book调用自己的Dao层方法。
单元测试
ApplicationContext ioc=new ClassPathXmlApplicationContext("generic/applicationContext.xml");
@Test
public void test01(){
BaseService bookService = (BaseService) ioc.getBean("bookService");
UserService userService = (UserService) ioc.getBean("userService");
bookService.save();
userService.save();
}
AOP
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP 是 OOP 的延续,是软件开发中的一个热点,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范型。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
即程序运行期间,将某段代码动态的插入到指定方法的指定位置进行运行,这就是AOP
在每个方法前后都添加一些处理函数,如果是原来的java程序的设计,就在目标方法内部进行修改,或者引入调用其他处理函数,这样的方法,大大的增强的代码的耦合度,而且及其不适合开发,修改也十分麻烦。如果是使用AOP的话,这减少代码的修改,只需要通过代理对象,就可以对目标方法进行修改。
AOP的使用场景
将其他代码公用部分抽取处理,成为一个切面,动态的切入进所有的函数中,如日志管理,权限验证等此类具有公用特点的处理形式,均可以作为切面切入到方法中。
使用场景:
- 添加日志
- 权限验证
- 安全检测
- 事务处理
动态代理
是AOP的实现原型,需要先创建一个接口,通过Proxy.newProxyInstance生成代理对象,通过代理对象,我们可以对目标方法进行增强。
抽象类
实现动态代理必须创建抽象类,代理对象和被代理对象实现同一个接口public interface Calculator {
public int add(int i,int j);
public int sub(int i,int j);
public int mul(int i,int j);
public int div(int i,int j);
}
实体类,这是目标增强方法
public class CalculatorImpl implements Calculator{
public int add(int i, int j) {
return i+j;
}
public int sub(int i, int j) {
return i-j;
}
public int mul(int i, int j) {
return i*j;
}
public int div(int i, int j) {
return i/j;
}
}
代理类
public class CalculatorProxy {
public static Calculator getProxy(final Calculator calculator){
//ClassLoader:目标对象的类加载器
ClassLoader classLoader = calculator.getClass().getClassLoader();
//Class<?>[]:目标对象的继承的接口
Class<?>[] classes = calculator.getClass().getInterfaces();
//方法加载类,会执行目标对象的目标方法,当方法运行时,jvm会自动将方法编码到此。
InvocationHandler invocationHandler = new InvocationHandler() {
/**
* Object proxy:代理对象,会交给jdk使用
* Method method:目标对象的目标方法
* Object[] args:目标方法的参数,防止重载找不到目标方法
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("这是动态代理执行的方法");
//反射执行目标方法
//目标方法的返回值
Object invoke = method.invoke(calculator, args);
return invoke;
}
};
Object proxy = Proxy.newProxyInstance(classLoader, classes, invocationHandler);
return (Calculator)proxy;
}
}
注:jdk的动态代理,如果目标对象没有任何接口,则无法为它创造代理对象
单元测试
@Test
public void test01(){
Calculator calculator = new CalculatorImpl();
Calculator proxy = CalculatorProxy.getProxy(calculator);
System.out.println(proxy.getClass());
proxy.add(1,1);
}
AOP名词
基于注解的AOP
@Component
@Aspect
要想使用Aspect注解,必须想导入AspectJ包和Aspectweaver包才能使用
Before:前置注解,在方法执行前执行
After:后置注解,在方法结束的时候执行
AfterReturning:在方法执行完成后执行
AfterThrowing:在方法出现异常之后执行
Around:给方法的执行前后添加处理方法
执行的切面类
@Component
@Aspect
public class LogUtils {
@Before("execution(int com.spring.aop.calculator.CalculatorImpl.*(int,int))")
public void startLog(){
System.out.println("这是start的前置方法");
}
@After("execution(int com.spring.aop.calculator.CalculatorImpl.*(int,int))")
public void endLog(){
System.out.println("这是后置方法");
}
@AfterReturning("execution(int com.spring.aop.calculator.CalculatorImpl.*(int,int))")
public void returnLog(){
System.out.println("这是返回方法");
}
@AfterThrowing("execution(int com.spring.aop.calculator.CalculatorImpl.*(int,int))")
public void exceptionLog(){
System.out.println("这是异常方法");
}
}
执行的切面类必须设置为ioc容器中的组件,并告诉ioc容器这是一个切面。
单元测试
@Test
public void test02(){
ApplicationContext ioc=new ClassPathXmlApplicationContext("aop/applicationContext.xml");
Calculator bean = ioc.getBean(Calculator.class);
bean.add(1,1);
}
得到的结果是
这是通过AspectJ类实现的AOP功能,但是本质底层实现还是动态代理,目标对象必须实现接口类,不然aop就无法正常为它创建代理对象了
从他获得的class中可以看出,底层还是有动态代理实现的AOP功能
如果目标对象没有实现接口,则自动的由cglib为目标对象实现AOP,cglib并不需要接口来实现动态代理
基于XML的AOP
<!--将目标类和切面类加入到ioc容器中-->
<bean id="calculator" class="com.spring.aop.calculator.CalculatorImpl"></bean>
<bean id="logUtils" class="com.spring.aop.utils.LogUtils"></bean>
<!--aop名称空间-->
<aop:config>
<!--指定切面-->
<aop:aspect ref="logUtils">
<!--切入点表达式-->
<aop:pointcut id="pointCut" expression="execution(int com.spring.aop.calculator.CalculatorImpl.*(int,int))"/>
<!--指定通知方法-->
<aop:before method="startLog" pointcut-ref="pointCut"></aop:before>
<aop:after method="endLog" pointcut-ref="pointCut"></aop:after>
<!--要指定通知方法参数的类型-->
<aop:after-returning method="returnLog" pointcut-ref="pointCut" returning="result"></aop:after-returning>
<aop:after-throwing method="exceptionLog" pointcut-ref="pointCut" throwing="exception"></aop:after-throwing>
<aop:around method="aroundLog" pointcut-ref="pointCut" ></aop:around>
</aop:aspect>
</aop:config>
Cglib代理的底层实现原理
代理类
public class CglibProxy {
public static Object getProxy(Object o){
//目标对象的Class,将其设为父类
Class aClass = o.getClass();
//设置回调函数
Callback callback = new MethodInterceptor() {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("这是cglib代理执行的方法");
Object o1 = methodProxy.invokeSuper(o, objects);
return o1;
}
};
//返回代理对象
return Enhancer.create(aClass,callback);
}
}
通过Enhancer类的create创建动态代理,然后通过MethodInterceptor方法实现动态代理。
单元测试对象
@Test
public void test03(){
CalculatorImpl calculator = new CalculatorImpl();
CalculatorImpl o = (CalculatorImpl) CglibProxy.getProxy(calculator);
System.out.println(o.getClass());
o.add(1,1);
}
结果
从结果可以发现,获得的Class和Cglib代理的Class是同一个类的,而且并不需要实现接口,就能实现AOP功能。
通过哪种方式实现AOP
可以设置XML中的proxy-target-class的值,false为AspectJ,true为Cglib
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
AspectJ和Cglib代理的差别
Spring的AspecJ代理底层是通过Proxy.newProxyInstance实现的,InvocationHandler类在调用方法过程中,会自动被JVM编码进内存中,因此可以这样来实现动态代理,特点就是目标类必须要有接口。
和Cglib代理是通过Enhancer.create,必须指定目标对象的Class和回调函数,回调函数是通过MethodInterceptor类实现的,具体是怎么操作的还没看过源码。但是他并不需要目标类实现接口。
切入点表达式
格式:execution(public int com.spring.aop.calculator.CalculatorImpl.(int,int))
execution(public(可省略) 返回类型 全类名.函数(参数))
和..的用法:
*:
- 匹配一个或任意多个字符(路径)
- 匹配任意一个参数
..:
- 匹配任意多个参数 任意参数类型
- 匹配任意多层路径
最模糊的书写:execution( .*(..)) —->不推荐
JoinPoint参数获得方法的参数
public void startLog(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
String name = joinPoint.getSignature().getName();
System.out.println(name+"为你处理"+ Arrays.asList(args));
}
Spring通知方法
//告诉spring result是返回值
@AfterReturning(value = "execution(int com.spring.aop.calculator.CalculatorImpl.*(int,int))",returning = "result")
public void returnLog(JoinPoint joinPoint,Object result){
System.out.println(joinPoint.getSignature().getName()+"处理完毕,结果是"+result);
}
//告诉spring exception是用来接受异常的
@AfterThrowing(value = "execution(int com.spring.aop.calculator.CalculatorImpl.*(int,int))",throwing ="exception")
public void exceptionLog(JoinPoint joinPoint,Exception exception){
System.out.println(joinPoint.getSignature().getName()+"方法处理异常,异常信息是"+exception.getMessage());
}
spring中的通知方法是通过反射实现的,因此每个方法的参数必须告诉给spring,spring才可以通过参数去调用相应方法,不然不声明的话,spring就会报错
抽取可重用的切入点表达式
@Pointcut("execution(int com.spring.aop.calculator.CalculatorImpl.*(int,int))")
public void pointCut(){
}
@Before("pointCut()")
将重复的切入点表达式,提取出来,可以降低代码的修改次数,而且方便修改,只要将切入点中的增强方法修改,其他同名切入点的通知方法都会相应修改。
环绕通知
@Around("pointCut()")
public Object aroundLog(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
String name = proceedingJoinPoint.getSignature().getName();
Object[] args = proceedingJoinPoint.getArgs();
Object proceed=null;
try {
System.out.println(name+"环绕前置通知方法");
//proceed方法类似method.invoke方法,proceed是方法的返回值
proceed = proceedingJoinPoint.proceed();
System.out.println(name+"环绕后置通知方法");
} catch (Exception e) {
System.out.println(name+"环绕异常通知方法"+e.getMessage());
}finally {
System.out.println(name+"环绕返回通知方法");;
}
return proceed;
}
环绕通知优先于其他通知执行,因此执行结果如下
环绕前置—->普通前置—->环绕后置——>环绕返回——>普通后置——>普通返回
多切面运行顺序
当有多个切面切入同一个切入点时,加入的通知方法执行顺序也会不同。
切面的执行顺序是跟着切面类的类名的字符串大小排序的,小的会优先执行外层切面的前置通知方法,外层切面会包裹内层切面,当内层切面执行完毕之后,外层切面才会相应的执行完毕。
当然也可以指定切面的执行顺序,使用Order注解
@Order(1) //小的优先级高
如果是基于XML配置的话,那个前面写在XML前面的话,就优先执行那个切面