AOP编程
学习aop之前我们可以先掌握动态代理的原理和作用。
动态代理
- 动态代理:我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。通俗来讲,遭我们需要用到一个对象的某个方法时,但是它的某些代码块或者全部代码都不需要时,此时我们就可以用到动态代理去实现自己所需要的编程需求或者是在原有的代码上追加代码。
- java编程如何实现动态代理(jdk手动方式)?
利用java中的工具类Proxy.newProxyInstance()调用重写,返回一个代理类(此对象与被代理对象一样实现了同样的接口,也属于实现类,不过里面的方法通过动态代理发生了变化)。
Proxy.newProxyInstance()类中的三个参数分别是(反射机制获取): - 实现接口InvocationHander实现类的类加载器(匿名类写时可以使用当前类的类加载器)。
- 被代理类的所有接口。
- 创建匿名类InvocationHandler(){重写invoke()方法},也可以采取常规方式直接创建一个实现InvocationHandler接口的实现类对象,作为第三个参数。需要代理的代码就是invoke()方法中的代码。
AOP表示的就是使用spring容器进行代理操作,了解动态代理后,现在开始进一步学习AOP。
AOP
什么是AOP?
- AOP为Aspect Oriented Programming的缩写,意为面向切面编程。通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP技术可以将业务逻辑各个部分的耦合度降低,提高程序的可重用行,大大提高了开发效率。
- AOP与前面所学的IOC和DI类似,都是通过spring容器来创建自己所需的对象,无关时间,无关用途,只要是需要,spring容器都会存放好实例对象,然后通过spring容器的工具类将这些对象提取出来进行整合,最后spring返回一个整合好的对象,即代理类(proxy)。
- AOP通过spring容器存放了哪些实例对象?
切面类(aspect):切面类里的每个方法都是我们要在被代理的类里需要增加的代码(即增强字节码),这里的每个方法aop专业术语叫做通知(advice),所以切面类就是存放各种通知的类。
目标类(target):可以是实现了接口的类,也可以是一个普通类。
1.手动方式代理
jdk动态代理
(1)准备切面类(myAspect):
(2)准备目标类,实现类+接口:
(3)创建一个工厂类,将切面类和目标类进行融合,实现动态代理。通过工厂模式生产代理类。
代理原理:
首先将切面类和目标类实例化,为后面代码进行融合代理作准备工具类Proxy.newProxyInstance()通过三个参数的传递,最后返回一个代理成功的类,三个参数分别是:
- 实现接口InvocationHander实现类的类加载器(匿名类写时可以使用当前类的类加载器)。
- 被代理类的所有接口。
- 创建匿名类InvocationHandler(){重写invoke()方法}
代理过程是通过重写invoke()方法,在方法里进行一个代码的融合,通过invoke的第二个参数method的invoke方法,可以将获取到目标类的源代码,此时我们将切面类里的增强代码增加到编程需求的位置,就能成功实现业务逻辑。
(4)测试
显示正确的业务逻辑,代理成功。
2.CGLIB字节码增强
与jdk手动代理不同,CGLIB字节码增加是对没有借口,只有实现类进行代理,代理方式也不同,采用的是字节码增强cglib框架,在运行时创建目标类的子类,从而达到对目标类增强的效果。
(1)导入jar包
在spring-core-3.2.0.RELEASE.jar中整合了cglib中的核心与依赖两个jar包。
(2)准备目标类
(3)准备切面类
(4)准备cglib字节增强代理的工厂类
- cglib字节增强是通过设置核心类enhanser来成功进行代理。
- 如何对enhancer进行设置:
通过enhancer.setSuperClass()确定核心类的父类,即找到需要被代理的目标类;
采用enhancer.setCallback()方法设置回调函数,利用匿名内部类的方法对切面类与目标类方法进行融合。通过匿名类MethodInterceptor()重写intercept()方法进行代理,此过程相当于jdk中的invovationhandler()匿名类重写invoke()方法。
最后通过enhancer.create()返回代理类。
(5)测试代理类
与jdk动态代理一样
显示正确的业务逻辑,代理成功。
3.spring编写代理(半自动)
前面两个代理都是以手动方式获取到的代理类,并未使用到spring容器处理,现在我们以spring容器的方式获取代理类。
(1)导入jar包
核心:4+1
AOP:aop联盟(规范),spring-aop(实现)
AOP联盟通知类型:
- AOP联盟为通知Advice定义了org.aopalliance.aop.Advice
- Spring按照通知Advice在目标类方法的连接点位置,可以分为5类
- 前置通知 org.springframework.aop.MethodBeforeAdvice
在目标方法执行前实施增强 - 后置通知 org.springframework.aop.AfterReturningAdvice
在目标方法执行后实施增强 - 环绕通知 org.aopalliance.intercept.MethodInterceptor
在目标方法执行前后实施增强 - 异常抛出通知 org.springframework.aop.ThrowsAdvice
在方法抛出异常后实施增强 - 引介通知 org.springframework.aop.IntroductionInterceptor
在目标类中添加一些新的方法和属性
注意:环绕通知,必须手动执行目标方法
try{
//前置通知
//执行目标方法
//后置通知
} catch(){
//抛出异常通知
}
(2)提供实现类和接口
(3)提供切面类
由于使用的spring进行半自动代理,我们的切面类以实现环绕通知类型接口为例进行代理。
(4)spring配置
创建代理类代码逻辑:
- 前置通知 org.springframework.aop.MethodBeforeAdvice
- 使用工厂bean FactoryBean ,底层调用 getObject() 返回特殊bean
- ProxyFactoryBean 用于创建代理工厂bean,生成特殊代理对象
- interfaces : 确定接口们 (通过可以设置多个值 只有一个值时,value="")
- target : 确定目标类
- interceptorNames : 通知 切面类的名称,类型String[],如果设置一个值 value=""
- optimize :强制使用cglib
<property name="optimize" value="true"></property>
- 底层机制:如果目标类有接口,采用jdk动态代理;如果没有接口,采用cglib 字节码增强;如果声明 optimize = true,无论是否有接口,都采用cglib。
(5)测试spring
成功显示环绕通知代理后的正确业务逻辑,代理成功。
4.spring编写代理(全自动)
- 在spring半自动编写代理中,spring在创建一个代理类时,类似于我们在jdk中编写动态代理一样,是新建了一个代理对象的方式,然后将需要代理的目标类以及切面类注入到代理对象中,以此来进行代理。所以我们是否可以尝试只需告诉spring我们需要代理的目标类和切面类是谁,不再自己手动注入,让spring自己去完成注入的操作。于是就需要spring全自动编写代理。
因为要使用到切入点表达式确定目标类,需要导入jar包:
springframework3.0.2.RELEASEdependencies\org.aspectj\com.springsource.org.aspectj.weaver\1.6.8.RELEASE
(1)xml文件导入aop命名空间:
添加红线框里的配置文件。
(2)spring配置:
aop编程:
1 .导入命名空间
2 .使用<aop:config>
进行配置
proxy-target-class=“true” 声明时使用cglib代理<aop:pointcut>
切入点 ,从目标对象获得具体方法<aop:advisor>
特殊的切面,只有一个通知 和 一个切入点 advice-ref 通知引用 pointcut-ref 切入点引用。
3 .切入点表达式 execution(* com.itheima.c_spring_aop..(…))
格式:选择方法 返回值任意 包 类名任意 方法名任意 参数任意
(3)测试spring
与spring半自动编写代理不同的是getBean获得的id并不是新建的代理类id,而是目标类id:
成功显示环绕通知代理后的正确业务逻辑,代理成功。