责编 | 郭芮
出品 | CSDN 博客
AOP(Aspect Oriented Programming)面向切面编程是 Spring 框架最核心的组件之一,它通过对程序构造的另一种考虑,补充了 OOP(Object-Oriented Programming)面向工具编程。在 OOP 中模块化的关键单元是类,而在 AOP 中,模块化单元是切面。也便是说 AOP 关注的不再是类,而是一系列类里面须要共同能力的行为。

本文内容紧张包括:
讲解 OOP 与 AOP 的大略比拟,以及 AOP 的根本名词,比如口试中常常会被问到的 point cut、advice、join point 等。
讲解口试常常会被问到的 JDK 动态代理和 CGLIB 代理事理以及差异。
讲解 Spring 框架中基于 Schema 的 AOP 实现事理。
讲解 Spring 框架中如何基于 AOP 实现的事务管理。
AOP 根本观点
我们知道在 OOP 中模块化的关键单元是类,类封装了一类工具的行为和状态,当多个类有共同的属性和行为时候我们把这些共同的东西封装为一个基类,然后多个类可以通过继续基类的办法来复用这些共同的东西,如果子类须要定制基类行为则可以利用多态。OOP 中利用类来供应封装,继续,多态三个特性。
当我们须要在多个不干系的类的某些已有的行为里面添加一个共同的非业务逻辑时候,比如我们须要统计一些业务方法的实行耗时时候,以往做法须要在统计耗时的行为里面写入打算耗时的代码,在 OOP 里面这种不涉及业务的散落在多个类的行为里面的代码叫做横切(Cross-cutting)代码,OOP 中这种办法缺陷一是业务逻辑行为受到了打算耗时期码滋扰(业务逻辑行为该当只专注业务),缺陷二是打算耗时的代码不能被复用。
而在 AOP 中模块化单元是切面(Aspect),它将那些影响多个类的共同行为封装到可重用的模块中,然后你就可以决定在什么时候对哪些类的哪些行为实行进行拦截(切点),并利用封装好的可重用模块里面的行为(关照)对其拦截的业务行为进行功能增强,而不须要修正业务模块的代码,切面便是对此的一个抽象描述。
AOP 中有以下根本观点:
Join point(连接点):程序实行期间的某一个点,例如实行方法或处理非常时候的点。在 Spring AOP 中,连接点总是表示方法的实行。
Advice(关照):关照是指一个切面在特定的连接点要做的事情。关照分为方法实行前关照,方法实行后关照,环抱关照等。许多 AOP 框架(包括 Spring)都将关照建模为拦截器,在连接点周围掩护一系列拦截器(形成拦截器链),对连接点的方法进行增强。
Pointcut(切点):一个匹配连接点(Join point)的谓词表达式。关照(Advice)与切点表达式关联,并在切点匹配的任何连接点(Join point)(例如,实行具有特定名称的方法)上运行。切点是匹配连接点(Join point)的表达式的观点,是AOP的核心,并且 Spring 默认利用 AspectJ 作为切入点表达式措辞。
Aspect(切面):它是一个超过多个类的模块化的关注点,它是关照(Advice)和切点(Pointcut)合起来的抽象,它定义了一个切点(Pointcut)用来匹配连接点(Join point),也便是须要对须要拦截的那些方法进行定义;它定义了一系列的关照(Advice)用来对拦截到的方法进行增强;
Target object(目标工具):被一个或者多个切面(Aspect)关照的工具,也便是须要被 AOP 进行拦截对方法进行增强(利用关照)的工具,也称为被关照的工具。由于在 AOP 里面利用运行时期理,以是目标工具一贯是被代理的工具。
AOP proxy(AOP 代理):为了实现切面(Aspect)功能利用 AOP 框架创建一个工具,在 Spring 框架里面一个 AOP 代理要么指 JDK 动态代理,要么指 CgLIB 代理。
Weaving(织入):是将切面运用到目标工具的过程,这个过程可以是在编译时(例如利用 AspectJ 编译器),类加载时,运行时完成。Spring AOP 和其它纯 Java AOP 框架一样,是在运行时实行植入。
Advisor:这个观点是从 Spring 1.2的 AOP 支持中提出的,一个 Advisor 相称于一个小型的切面,不同的是它只有一个关照(Advice),Advisor 在事务管理里面会常常碰着,这个后面会讲到。
比较 OOP,AOP 有以下优点:
业务代码更加简洁,例如当须要在业务行为前后做一些事情时候,只须要在该行为前后配置切面进行处理,无须修正业务行为代码。
切面逻辑封装性好,并且可以被复用,例如我们可以把打日志的逻辑封装为一个切面,那么我们就可以在多个干系或者不干系的类的多个方法上配置该切面。
JDK 动态代理和 CGLIB 代理事理、差异
在 Spring 中 AOP 代理利用 JDK 动态代理和 CGLIB 代理来实现,默认如果目标工具是接口,则利用 JDK 动态代理,否则利用 CGLIB 来天生代理类,本节就大略来先容这两种代理的事理和差异。
JDK 动态代理
由于 JDK 代理是对接口进行代理,以是首先写一个接口类:
public interface UserServiceBo {
public int add;
}
然后实现该接口如下:
public class UserServiceImpl implements UserServiceBo {
@Override
public int add {
System.out.println(\公众--------------------add----------------------\"大众);
return 0;
}
}
JDK 动态代理是须要实现 InvocationHandler 接口,这里创建一个 InvocationHandler 的实现类:
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
super;
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//(1)
System.out.println(\"大众-----------------begin \公众+method.getName+\"大众-----------------\"大众);
//(2)
Object result = method.invoke(target, args);
//(3)
System.out.println(\"大众-----------------end \公众+method.getName+\"大众-----------------\"大众);
return result;
}
public Object getProxy{
//(4)
return Proxy.newProxyInstance(Thread.currentThread.getContextClassLoader, target.getClass.getInterfaces, this);
}
}
建立一个测试类:
public static void main(String[] args) {
//(5)打开这个开关,可以把天生的代理类保存到磁盘
System.getProperties.put(\"大众sun.misc.ProxyGenerator.saveGeneratedFiles\公众, \公众true\"大众);
//(6)创建目标工具(被代理工具)
UserServiceBo service = new UserServiceImpl;
//(7)创建一个InvocationHandler实例,并通报被代理工具
MyInvocationHandler handler = new MyInvocationHandler(service);
//(8)天生代理类
UserServiceBo proxy = (UserServiceBo) handler.getProxy;
proxy.add;
}
个中代码(6)创建了一个 UserServiceImpl 的实例工具,这个工具便是要被代理的目标工具。
代码(7)创建了一个 InvocationHandler 实例,并通报被代理目标工具 service 给内部变量 target。
代码(8)调用 MyInvocationHandler 的 getProxy 方法利用 JDK 天生一个代理工具。
代码(5)设置系统属性变量 sun.misc.ProxyGenerator.saveGeneratedFiles 为 true,这是为了让代码(8)天生的代理工具的字节码文件保存到磁盘。
运行上面代码会在项目的 com.sun.proxy 下面会天生 $Proxy0.class 类,经反编译后核心 Java 代码如下:
package com.sun.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.JDK.UserServiceBo;
public final class $Proxy0
extends Proxy
implements UserServiceBo
{
private static Method m1;
private static Method m3;
private static Method m0;
private static Method m2;
public $Proxy0(InvocationHandler paramInvocationHandler)
{
super(paramInvocationHandler);
}
public final int add
{
try
{
//(9)第一个参数是代理类本身,第二个是实现类的方法,第三个是参数
return h.invoke(this, m3, );
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
...
static
{
try
{
m3 = Class.forName(\"大众proxy.JDK.UserServiceBo\公众).getMethod(\公众add\"大众, new Class[0]);
...
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
...
}
}
代理类 $Proxy0
中代码(9)中的 h 便是 main 函数里面创建的 MyInvocationHandler 的实例,h.invoke(this, m3, ) 实际便是调用的 MyInvocationHandler 的 invoke 方法,而后者则是委托给被代理工具进行实行,这里可以对目标工具方法进行拦截,然后对其功能进行增强。
其余代码(8)天生的 proxy 工具实际便是 $Proxy0
的一个实例,当调用 proxy.add 时候,实际是调用的代理类$Proxy0
的 add 方法,后者内部则委托给 MyInvocationHandler 的 invoke 方法,invoke 方法内部有调用了目标工具 service 的 add 方法。
那么接口(UserServiceBo)、目标工具(被代理工具 UserServiceImpl)、代理工具($Proxy0
)三者详细关系可以利用下图表示:
enter image description here
可知 JDK 动态代理是对接口进行的代理;代理类实现了接口,并继续了 Proxy 类;目标工具与代理工具没有什么直接关系,只是它们都实现了接口,并且代理工具实行方法时候内部终极是委托目标工具实行详细的方法。
CGLIB 动态代理
比较 JDK 动态代理对接口进行代理,CGLIB 则是对实现类进行代理,这意味着无论目标工具是否有接口,都可以利用 CGLIB 进行代理。
下面结合一个利用 CGLIB 对实现类进行动态代理的大略代码来讲解。
利用 CGLIB 进行代理须要实现 MethodInterceptor,创建一个方法拦截器 CglibProxy 类:
public class CglibProxy implements MethodInterceptor {
//(10)
private Enhancer enhancer = new Enhancer;
//(11)
public Object getProxy(Class clazz) {
//(12)设置被代理类的Class工具
enhancer.setSuperclass(clazz);
//(13)设置拦截器回调
enhancer.setCallback( this);
return enhancer.create;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println(obj.getClass.getName+\公众.\"大众+method.getName);
Object result = proxy.invokeSuper(obj, args);
return result;
}
}
public void testCglibProxy {
//(14)天生代理类到本地
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, \公众/Users/zhuizhumengxiang/Downloads\"大众);
//(15)天生目标工具
UserServiceImpl service = new UserServiceImpl;
//(16)创建CglibProxy工具
CglibProxy cp = new CglibProxy;
//(17)天生代理类
UserServiceBo proxy = (UserServiceBo) cp.getProxy(service.getClass);
proxy.add;
}
实行上面代码会在 /Users/zhuizhumengxiang/Downloads
目录天生代理类UserServiceImpl$$EnhancerByCGLIB$$d0bce05a.class
文件,反编译后部分代码如下:
public class UserServiceImpl$$EnhancerByCGLIB$$d0bce05a extends UserServiceImpl
implements Factory
{
static void CGLIB$STATICHOOK1
{
//(18)空参数
CGLIB$emptyArgs = new Object[0];
//(19)获取UserServiceImpl的add方法列表
Method tmp191_188 = ReflectUtils.findMethods(new String { \"大众add\"大众, \"大众I\"大众 }, (localClass2 = Class.forName(\"大众zlx.cglib.zlx.UserServiceImpl\公众)).getDeclaredMethods);
CGLIB$add$0$Method = tmp191_188[0];
//(20)创建CGLIB$add$0,根据创建一个MethodProxy
CGLIB$add$0$Proxy = MethodProxy.create(localClass2, localClass1, \"大众I\"大众, \"大众add\"大众, \"大众CGLIB$add$0\"大众);
}
static
{
CGLIB$STATICHOOK1;
}
//(21)
final int CGLIB$add$0
{
return super.add;
}
//(22)
public final int add
{
...
//(23)获取拦截器,这里为CglibProxy的实例
MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;
if (tmp17_14 != )
{ //(24)调用拦截器的intercept方法
Object tmp36_31 = tmp17_14.intercept(this, CGLIB$add$0$Method, CGLIB$emptyArgs, CGLIB$add$0$Proxy);
tmp36_31;
return tmp36_31 == ? 0 : ((Number)tmp36_31).intValue;
}
return super.add;
}
}
代码(18)创建了一个 CGLIB$emptyArgs
,由于 add 方法是无入参的,以是这里创建的 Object 工具个数为0,这工具是 CglibProxy 拦截器的 intercept 的第三个参数。
代码(19)获取 UserServiceImpl 的 add 方法列表,并把唯一方法赋值给 CGLIB$add$0$Method
,这个工具是 CglibProxy 拦截器的 intercept 第二个参数。
代码(20)创建了一个 MethodProxy 工具,当调用 MethodProxy 工具的 invokeSuper 方法时候会直接调用代理工具的 CGLIB$add$0
方法,也便是直接调用父类 UserServiceImpl 的 add 方法,这避免了一次反射调用,创建的 MethodProxy 工具是 CglibProxy 拦截器的 intercept 的第四个参数。
代码(22)是代理类的 add 方法,代码(17)天生的代理工具 proxy 便是 UserServiceImpl$$EnhancerByCGLIB$$d0bce05a
的一个实例,当调用 proxy.add 方法时候便是调用的代码(22),从代码(22)可知内部调用了拦截器 CglibProxy 的 intercept 方法,可知 intercept 的第一个参数便是代理工具本身。
那么接口(UserServiceBo)、目标工具(被代理工具 UserServiceImpl),代理工具(UserServiceImpl$$EnhancerByCGLIB$$d0bce05a
)三者详细关系可以利用下图表示:
enter image description here
可知接口和代理工具没有啥关系,代理工具是继续了目标工具和实现了 Factory 接口。
总结
JDK 动态代理机制只能对接口进行代理,其事理是动态天生一个代理类,这个代理类实现了目标工具的接口,目标工具和代理类都实现了接口,但是目标工具和代理类的 Class 工具是不一样的,以是两者是没法相互赋值的。
CGLIB 是对目标工具本身进行代理,以是无论目标工具是否有接口,都可以对目标工具进行代理,其事理是利用字节码天生工具在内存天生一个继续目标工具的代理类,然后创建代理工具实例。由于代理类的父类是目标工具,以是代理类是可以赋值给目标工具的,自然如果目标工具有接口,代理工具也是可以赋值给接口的。
CGLIB 动态代理中天生的代理类的字节码比较 JDK 来说更加繁芜。
JDK 利用反射机制调用目标类的方法,CGLIB 则利用类似索引的办法直接调用目标类方法,以是 JDK 动态代理天生代理类的速率比较 CGLIB 要快一些,但是运行速率比 CGLIB 低一些,并且 JDK 代理只能对有接口的目标工具进行代理。
Spring 框架中基于 Schema 的 AOP 实现事理
Spring 供应了两种办法对 AOP 进行支持:基于 Schema 的 AOP,基于表明的 AOP。
基于 Schema 的 AOP 许可您基于 XML 的格式配置切面功能,Spring 2.0 供应了新的“aop”命名空间标记来定义切面的支持,基于表明的 AOP 则许可您利用 @Aspect 风格来配置切面。
本文就来先讲讲基于 Schema 的 AOP 的实现事理。
AOP 大略利用
一个切面实际上是一个被定义在 Spring application context 里面的一个正常的 Java 工具,配置切面对目标工具进行增强时候,一样平常利用下面配置格式:
<!--(1) -->
<bean id=\"大众helloService\"大众 class=\"大众zlx.test.aop.HelloServiceBoImpl\"大众 />
<!--(2) -->
<bean id=\"大众myAspect\"大众 class=\"大众zlx.test.aop.MyAspect\公众 />
<aop:config>
<!--(3) -->
<aop:pointcut id=\"大众pointcut\"大众
expression=\公众execution( ..BoImpl.sayHello(..))\公众/>
<!--(4) -->
<aop:aspect ref=\公众myAspect\公众>
<!--(4.1) -->
<aop:before pointcut-ref=\"大众pointcut\"大众 method=\公众beforeAdvice\"大众 />
<!--(4.2) -->
<aop:after pointcut=\"大众execution( ..BoImpl.sayHello(..))\公众
method=\"大众afterAdvice\"大众 />
<!--(4.3) -->
<aop:after-returning
pointcut=\"大众execution( ..BoImpl.sayHelloAfterReturn(..))\"大众 method=\"大众afterReturningAdvice\公众
arg-names=\"大众content\"大众 returning=\"大众content\"大众 />
<!--(4.4) -->
<aop:after-throwing pointcut=\"大众execution( ..BoImpl.sayHelloThrowExecption(..))\公众
method=\"大众afterThrowingAdvice\"大众 arg-names=\"大众e\"大众 throwing=\"大众e\"大众 />
<!--(4.5) -->
<aop:around pointcut=\"大众execution( ..BoImpl.sayHelloAround(..))\公众
method=\"大众aroundAdvice\公众 />
</aop:aspect>
</aop:config>
<!--(5) -->
<bean id = \"大众tracingInterceptor\"大众 class=\公众zlx.test.aop.TracingInterceptor\"大众/>
<!--(6) -->
<aop:config>
<aop:pointcut id=\"大众pointcutForadVisor\公众 expression=\"大众execution( ..BoImpl.sayHelloAdvisor(..))\"大众 />
<aop:advisor pointcut-ref=\"大众pointcutForadVisor\"大众 advice-ref=\"大众tracingInterceptor\"大众 />
</aop:config>
代码(1)创建一个要被 AOP 进行功能增强的目标工具(Target object),HelloServiceBoImpl 的代码如下:
public class HelloServiceBoImpl implements HelloServiceBo{
@Override
public void sayHello(String content) {
System.out.println(\"大众sayHello:\"大众 + content);
}
@Override
public String sayHelloAround(String content) {
System.out.println(\公众 sayHelloAround:\"大众 + content);
return content;
}
@Override
public String sayHelloAfterReturn(String content) {
System.out.println(\"大众sayHelloAround:\公众 + content);
return content;
}
@Override
public void sayHelloThrowExecption {
System.out.println(\"大众sayHelloThrowExecption\"大众);
throw new RuntimeException(\"大众hello Im an exception\公众);
}
}
public interface HelloServiceBo {
public void sayHello(String content);
public String sayHelloAround(String content);
public String sayHelloAfterReturn(String content);
public void sayHelloThrowExecption;
}
代码(2)本色是定义了切面要利用的一系列的关照方法(Advice),用来对目标工具(Target object)的方法进行增强,MyAspect 的代码如下:
public class MyAspect {
public void beforeAdvice(String content) {
System.out.println(\"大众---before advice \"大众+ \"大众---\"大众);
}
public void afterAdvice(JoinPoint jp) {
System.out.println(\公众---after advice \"大众 + jp.getArgs[0].toString+\"大众---\"大众);
}
public Object afterReturningAdvice(Object value) {
System.out.println(\"大众---afterReturning advice \"大众 + value+\"大众---\"大众);
return value + \"大众 ha\"大众;
}
public void afterThrowingAdvice(Exception e) {
System.out.println(\"大众---after throwing advice exception:\"大众 + e+\"大众---\"大众);
}
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
Object obj = pjp.getArgs;
String content = (String) obj[0];
System.out.println(\"大众---before sayHelloAround execute---\"大众);
String retVal = (String) pjp.proceed;
System.out.println(\公众---after sayHelloAround execute---\"大众);
return retVal+ \"大众 suffix\"大众;
}
}
代码(3)定义了一个切点(pointcut),这里是对知足 ..BoImpl
表达式的类里面的方法名称为 sayHello 的方法进行拦截。
代码(4)定义一个切面,一个切面中可以定义多个拦击器,个中(4.1)定义了一个前置拦截器,这个拦截器对知足代码(3)中定义的切点的连接点(方法)进行拦截,并利用 MyAspect 中定义的关照方法 beforeAdvice 进行功能增强。个中(4.2)定义了一个后置拦截器(finally),对知足 execution( ..BoImpl.sayHello(..))
条件的连接点方法利用 MyAspect 中的关照方法 afterAdvice 进行功能增强。个中(4.3)定义了一个后置拦截器,对知足execution( ..BoImpl.sayHelloAfterReturn(..))
条件的连接点方法利用 MyAspect 中的关照方法 afterReturningAdvice 进行功能增强,这个后置连接器与(4.2)不同在于如果被拦截方法抛出了非常,则这个拦截器不会被实行,而(4.2)的拦截器一贯会被实行。个中(4.4)定义了一个当被拦截方法抛出非常后对非常进行拦截的拦截器,详细拦截哪些方法由execution( ..BoImpl.sayHelloThrowExecption(..))
来决定,详细的关照方法是 MyAspect 中的 afterThrowingAdvice 方法。个中(4.5)对知足execution( ..BoImpl.sayHelloAround(..))
条件的连接点方法利用 MyAspect 中的关照方法 aroundAdvice 进行增强。
代码(5)创建一个方法拦截器,它是一个关照,代码如下:
class TracingInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation i) throws Throwable {
System.out
.println(\"大众---method \"大众 + i.getMethod + \"大众 is called on \"大众 + i.getThis + \"大众 with args \"大众 + i.getArguments+\"大众---\"大众);
Object ret = i.proceed;
System.out.println(\"大众---method \"大众 + i.getMethod + \"大众 returns \"大众 + ret+\"大众---\"大众);
return ret;
}
}
代码(6)创建了一个新的 aop:config 标签,内部首先创建了一个切点,然后创建了一个 advisor(一个小型切面),它对应的关照方法是 tracingInterceptor,对应的切点是 pointcutForadVisor。
须要把稳的是为了能够利用 AOP 命名空间下的 aop 标签,您须要在 XML 引入下面的 spring-aop schema:
<?xml version=\公众1.0\公众 encoding=\"大众UTF-8\"大众?>
<beans xmlns=\"大众http://www.springframework.org/schema/beans\"大众
xmlns:xsi=\"大众http://www.w3.org/2001/XMLSchema-instance\"大众
xmlns:aop=\"大众http://www.springframework.org/schema/aop\"大众
xsi:schemaLocation=\公众
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd\公众>
<!-- <bean/> definitions here -->
</beans>
把上面配置网络起来,放入到 beanaop.xml 配置文件后,写下下面代码,就可以进行测试:
public class TestAOP {
public static final String xmlpath = \"大众beanaop.xml\公众;
public static void main(String[] args) {
ClassPathXmlApplicationContext cpxa = new ClassPathXmlApplicationContext(xmlpath);
HelloServiceBo serviceBo = cpxa.getBean(\"大众helloService\"大众,HelloServiceBo.class);
serviceBo.sayHello(\"大众 I love you\"大众);
String result = serviceBo.sayHelloAround(\公众I love you\"大众);
System.out.println(result);
result = serviceBo.sayHelloAfterReturn(\公众I love you\"大众);
System.out.println(result);
serviceBo.sayHelloAdvisor(\公众I love you\"大众);
serviceBo.sayHelloThrowExecption;
}
}
事理阐发aop:config 标签的解析既然本文讲解基于 XML 配置的 AOP 的事理,那么我们就先从解析 XML 里面配置的 aop:config 讲起。首先看看 Spring 框架的 ConfigBeanDefinitionParser 类是如何解析aop:config 标签,紧张时序图如下:
enter image description here
代码(3)注册了一个 AspectJAwareAdvisorAutoProxyCreator 类到 Spring IOC 容器,该类的浸染是自动创建代理类,这个后面会讲。这里须要把稳的是在 registerAspectJAutoProxyCreatorIfNecessary的useClassProxyingIfNecessary 方法里面解析了 aop:config 标签里面的 proxy-target-class 和 expose-proxy 属性值,代码如下:
private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @able Element sourceElement) {
//解析proxy-target-class属性
if (sourceElement != ) {
boolean proxyTargetClass = Boolean.valueOf(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE)); //设置proxy-target-class属性值到AspectJAwareAdvisorAutoProxyCreator里面
if (proxyTargetClass) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
//解析expose-proxy属性值
boolean exposeProxy = Boolean.valueOf(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));
//设置expose-proxy属性属性值到AspectJAwareAdvisorAutoProxyCreator
if (exposeProxy) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
针对 proxy-target-class 属性(默认 false),如果设置为 true,则会逼迫利用 CGLIB 天生代理类;对付 expose-proxy 属性(默认 false)如果设置为 true,则对一个类里面的嵌套方法调用的方法也进行代理,详细说是如果一个 ServiceImpl 类里面有 a 和 b 方法,如果 a 里面调用了 b:
public void a{
this.b;
}
那么默认情形下在对方法 a 进行功能增强时候,里面的 b 方法是不会被增强的,如果也须要对 b 方法进行增强则可以设置 expose-proxy 为 true,并且在调用 b 方法时候利用下面办法:
public void a{
((ServiceBo)AopContext.currentProxy).b;
}
须要把稳的是 AopContext.currentProxy 返回的是代理后的类,如果利用 JDK 代理则这里类型转换时候必须要用接口类,如果是 CGLIB 代理则这里类型转换为实现类 ServiceImpl。
代码(4)是对 aop:config 标签里面的 aop:pointcut 元素进行解析,每个 pointcut 元素会创建一个 AspectJExpressionPointcut 的 bean 定义,并注册到Spring IOC,parsePointcut 的主干代码如下:
private AbstractBeanDefinition parsePointcut(Element pointcutElement, ParserContext parserContext) {
//获取aop:pointcut标签的id属性值
String id = pointcutElement.getAttribute(ID);
//获取aop:pointcut标签的expression属性值
String expression = pointcutElement.getAttribute(EXPRESSION);
AbstractBeanDefinition pointcutDefinition = ;
try {
//创建AspectJExpressionPointcut的bean定义,并设置属性
pointcutDefinition = createPointcutDefinition(expression);
pointcutDefinition.setSource(parserContext.extractSource(pointcutElement));
//如果aop:pointcut标签的id属性值不为空,则id作为该bean在Spring容器里面的bean名字,并注册该bean到Spring容器。
String pointcutBeanName = id;
if (StringUtils.hasText(pointcutBeanName)) {
parserContext.getRegistry.registerBeanDefinition(pointcutBeanName, pointcutDefinition);
}
else {//否者系统自动天生一个名字,并注入该bean到Spring容器
pointcutBeanName = parserContext.getReaderContext.registerWithGeneratedName(pointcutDefinition);
}
...
}
...
return pointcutDefinition;
}
注:AspectJExpressionPointcut 的成员变量 expression 保存了 aop:pointcut 标签的 expression 属性值,这个在自动代理时候会用到。
代码(5)是对 aop:config 标签里面的 aop:advisor 元素进行解析,每个 advisor 元素会创建一个 DefaultBeanFactoryPointcutAdvisor 的 bean 定义,并注册到 Spring IOC,parseAdvisor代码如下:
private void parseAdvisor(Element advisorElement, ParserContext parserContext) {
//创建DefaultBeanFactoryPointcutAdvisor的定义,并设置对advice的引用
AbstractBeanDefinition advisorDef = createAdvisorBeanDefinition(advisorElement, parserContext);
String id = advisorElement.getAttribute(ID);
try {
...
//注册DefaultBeanFactoryPointcutAdvisor到Spring容器
String advisorBeanName = id;
if (StringUtils.hasText(advisorBeanName)) {
parserContext.getRegistry.registerBeanDefinition(advisorBeanName, advisorDef);
}
else {
advisorBeanName = parserContext.getReaderContext.registerWithGeneratedName(advisorDef);
}
//解析aop:advisor标签中引用的切点,并设置到DefaultBeanFactoryPointcutAdvisor的定义
Object pointcut = parsePointcutProperty(advisorElement, parserContext);
if (pointcut instanceof String) {
advisorDef.getPropertyValues.add(POINTCUT, new RuntimeBeanReference((String) pointcut));
...
}
...
}
...
}
注:每个 DefaultBeanFactoryPointcutAdvisor 工具里面通过成员变量 adviceBeanName 保存引用关照在 BeanFactory 里面的 Bean 名称,通过成员变量 pointcut 保存切点。
DefaultBeanFactoryPointcutAdvisor 继续自 Advisor 接口,这里须要把稳的是 adviceBeanName 保存的只是关照的字符串名称,那么如何获取真正的关照工具呢,实在 DefaultBeanFactoryPointcutAdvisor 实现了 BeanFactoryAware,其内部担保着 BeanFactory 的引用,既然有了 BeanFactory,那么就可以根据 Bean 名称拿到想要的 Bean,这个可以参考 Chat:Spring 框架常用扩展接口揭秘(https://gitbook.cn/gitchat/activity/5a84589a1f42d45a333f2a8e)。
代码(6)是对 aop:config 标签里面的 aop:aspect 元素进行解析,会解析 aop:aspect 元素内的子元素,每个子元素会对应创建一个 AspectJPointcutAdvisor 的 bean 定义,并注册到 Spring IOC,parseAspect的代码如下:
private void parseAspect(Element aspectElement, ParserContext parserContext) {
//获取 aop:aspect 元素的id属性
String aspectId = aspectElement.getAttribute(ID);
//获取 aop:aspect 元素的ref属性
String aspectName = aspectElement.getAttribute(REF);
try {
this.parseState.push(new AspectEntry(aspectId, aspectName));
List<BeanDefinition> beanDefinitions = new ArrayList<>;
List<BeanReference> beanReferences = new ArrayList<>;
...
//循环解析```aop:aspect```元素内所有关照
NodeList nodeList = aspectElement.getChildNodes;
boolean adviceFoundAlready = false;
for (int i = 0; i < nodeList.getLength; i++) {
Node node = nodeList.item(i);
//判断是否为关照节点,比如前置,后者关照等
if (isAdviceNode(node, parserContext)) {
if (!adviceFoundAlready) {
adviceFoundAlready = true;
...
beanReferences.add(new RuntimeBeanReference(aspectName));
}
//根据关照类型的不同,创建不同的关照工具,末了封装为AspectJPointcutAdvisor的bean定义,并注册到Spring容器
AbstractBeanDefinition advisorDefinition = parseAdvice(
aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);
beanDefinitions.add(advisorDefinition);
}
}
//循环解析```aop:aspect```元素内所有切点,并注册到Spring容器
List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);
for (Element pointcutElement : pointcuts) {
parsePointcut(pointcutElement, parserContext);
}
...
}
finally {
...
}
}
须要把稳的是 parseAdvice 函数内部会根据关照类型的不同创建不同的 advice 工具,对应 before 关照会创建关照工具为 AspectJMethodBeforeAdvice,对应 after 关照创建的关照工具为 AspectJAfterAdvice,对应 after-returning 关照创建的关照工具为 AspectJAfterReturningAdvice,对应 after-throwing 关照创建的关照工具为 AspectJAfterThrowingAdvice,对应 around 关照创建的关照工具为 AspectJAroundAdvice,一个共同点是这些关照工具都实现了 MethodInterceptor 接口。
注:每个 AspectJPointcutAdvisor 工具里面通过 advice 掩护着一个关照,通过 pointcut 掩护这么一个切点,AspectJPointcutAdvisor 继续自 Advisor 接口。
至此 Spring 框架把 aop:config 标签里面的配置全部转换为了 Bean 定义的形式并注入到 Spring 容器了,须要把稳的是对应一个 Spring 运用程序高下文的 XML 配置里面可以配置多个 aop:config 标签,每次解析一个 aop:config 标签的时候都会重新走这个流程。
代理类的天生 (1)代理类天生的主干流程 上节在解析 aop:config 标签时候注入了一个 AspectJAwareAdvisorAutoProxyCreator 类到 Spring 容器,实在这个类便是实现动态天生代理类的,AspectJAwareAdvisorAutoProxyCreator 实现了 BeanPostProcessor 接口(这个接口是 Spring 框架在 bean 初始化前后干工作的扩展接口,详细可以参考:http://gitbook.cn/gitchat/activity/5a84589a1f42d45a333f2a8e),以是会有 Object postProcessAfterInitialization(@able Object bean, String beanName) 方法,下面看下这个方法代码实行时序图: enter image description here 代码(3)在 Spring 容器中查找可以对当前 bean 进行增强的关照 bean,内部首先实行代码(4)在 Spring 容器查找所有实现了 Advisor 接口的 Bean,也便是上节讲解的从 aop:config 标签里面解析的所有 DefaultBeanFactoryPointcutAdvisor 和 AspectJPointcutAdvisor 的实例都会被找到。代码(5)则看找到的 Advisor 里面哪些可以运用到当前 bean 上,这个是通过切点表达式来匹配的。代码(6)则对匹配的 Advisor 进行排序,根据每个 Advisor 工具的 getOrder 方法的值进行排序。 代码(13)这里根据一些条件决定是利用 JDK 动态代理,还是利用 CGLIB 进行代理,属于设计模式里面的策略模式。 public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { //如果optimize =true或者proxyTargetClass=ture 或者没有指定代理接口,则利用CGLIB进行代理 if (config.isOptimize || config.isProxyTargetClass || hasNoUserSuppliedProxyInterfaces(config)) { Class<?> targetClass = config.getTargetClass; //如果目标类为接口或者目标类是利用JDK动态代理天生的类,则是要利用JDK对其进行代理 if (targetClass.isInterface || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } //否者利用CGLIB进行代理 return new ObjenesisCglibAopProxy(config); } //利用JDK进行代理 else { return new JdkDynamicAopProxy(config); } 对付 proxyTargetClass 前面已经讲过了可以通过在 aop:config 这个标签里面配置,那么 optimize 是在哪里配置的呢?实在除了本文讲的基于标签的 AOP 模式,Spring 还供应了比如下面的办法进行动态代理: <bean class=\"大众org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator\公众> <property name=\"大众beanNames\"大众 value=\"大众impl\"大众></property> <!-- 只为后缀为\公众impl\"大众的bean生产代理 --> <property name=\"大众interceptorNames\"大众 value=\"大众myAdvisor\公众></property> <!--自定义方法拦击器--> <property name=\公众optimize\"大众 value=\"大众true\公众></property> </bean> BeanNameAutoProxyCreator 可以针对指定规则的 beanName 的 bean 利用 interceptorNames 进行增强,由于这时候不能确定匹配的 bean 是否有接口,以是这里 optimize 设置为 true,然后在创建代理工厂时候详细看被代理的类是否是接口决定是利用 JDK 代理还是 CGLIB 代理。 代码(14)则是详细调用 JdkDynamicAopProxy 或者 ObjenesisCglibAopProxy 的 getProxy 方法获取代理类,下面两节详细先容如何天生。 (2)JDK 动态代理天生代理工具 首先看下 JdkDynamicAopProxy 的 getProxy 方法: public Object getProxy(@able ClassLoader classLoader) { if (logger.isDebugEnabled) { logger.debug(\"大众Creating JDK dynamic proxy: target source is \"大众 + this.advised.getTargetSource); } Class<?> proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true); findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); } 由于 JdkDynamicAopProxy 类实现了 InvocationHandler 接口,以是这里利用 Proxy.newProxyInstance 创建代理工具时候第三个参数通报的为 this。下面看下 JdkDynamicAopProxy 的 invoke 方法: public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { ... try { ... //获取可以利用到该方法上的拦截器列表 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); //创建一个invocation方法,这个里面是一个interceptor链,是一个任务链模式 invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); //利用拦截器链处理这个连接点方法 retVal = invocation.proceed; ... return retVal; } finally { ... } } 下面看下上面小节“AOP 大略利用”例子中调用 serviceBo.sayHello 时候的时序图从而加深理解: “AOP 大略利用”这一小节例子中我们在 sayHello 方法实行前加了一个前置拦截器,在 sayHello 方法实行后加了个后置拦截器; 当实行 serviceBo.sayHello 时候实际上实行的代理类的 sayHello 方法,以是会被 JdkDynamicAopProxy 的 invoke 方法拦截,invoke 方法内首先调用 getInterceptorsAndDynamicInterceptionAdvice 方法获取配置到 sayHello 方法上的拦截器列表,然后创建一个 ReflectiveMethodInvocation 工具(内部是一个基于数数组的任务链),然后调用该工具的 procced 方法激活拦截器链对 sayHello 方法进行增强,这里是首先调用了前置连接器对 sayHello 进行增强,然后调用实际业务方法 sayHello,末了调用了后置拦截器对 sayHello 进行增强。 (3)CGLIB 动态代理天生代理工具 首先看下 ObjenesisCglibAopProxy 的 getProxy 方法: public Object getProxy(@able ClassLoader classLoader) { try { Class<?> rootClass = this.advised.getTargetClass; //创建CGLIB Enhancer Enhancer enhancer = createEnhancer; ... enhancer.setSuperclass(proxySuperClass); enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised)); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); ... //获取callback,紧张是DynamicAdvisedInterceptor Callback callbacks = getCallbacks(rootClass); Class<?> types = new Class<?>[callbacks.length]; for (int x = 0; x < types.length; x++) { types[x] = callbacks[x].getClass; } //设置callback enhancer.setCallbackTypes(types); //产生代理类并创建一个代理实例 return createProxyClassAndInstance(enhancer, callbacks); } catch (CodeGenerationException | IllegalArgumentException ex) { ... } ... } 下面看下拦截器 DynamicAdvisedInterceptor 的 intercept 方法,代码如下: public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { ... TargetSource targetSource = this.advised.getTargetSource; try { ... //获取可以利用到该方法上的拦截器列表 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); Object retVal; //如果拦截器列表为空,则直接反射调用业务方法 if (chain.isEmpty && Modifier.isPublic(method.getModifiers)) { Object argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); retVal = methodProxy.invoke(target, argsToUse); } else { //创建一个方法invocation,实在这个是个拦截器链,这里调用proceed激活拦截器链对当前方法进行功能增强 retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed; } //处理返回值 retVal = processReturnType(proxy, target, method, retVal); return retVal; } finally { ... } } 下面看“AOP 大略利用”这一节例子中调用 serviceBo.sayHello 时候的调用链路时序图从而加深理解: enter image description here 由于默认利用的 JDK 动态代理,要利用 CGLIB 进行代理,须要在 aop:config 标签添加属性如下: <aop:config proxy-target-class=\"大众true\"大众> 实行流程类似于 JDK 动态代理,这里不再累述。 Spring 框架中如何基于 AOP 实现的事务管理 事务的大略配置 XML 利用标签配置事务,一样平常是按照下面办法进行配置: <aop:config> <!--(1) --> <aop:pointcut id=\"大众businessService\"大众 expression=\公众execution( com.xyz.myapp.service..(..))\"大众/> <!--(2) --> <aop:advisor pointcut-ref=\"大众businessService\公众 advice-ref=\"大众tx-advice\公众/> </aop:config> <!--(3) --> <tx:advice id=\"大众tx-advice\公众> <tx:attributes> <tx:method name=\公众\公众 propagation=\公众REQUIRED\"大众/> </tx:attributes> </tx:advice> 如上配置(1),配置了一个 id 为 businessService 的切点用来匹配要对哪些方法进行事务增强; 配置(2)配置了一个 advisor ,利用 businessService 作为切点,tx-advice 作为关照方法; 配置(3)则利用 tx:advice 标签配置了一个关照,这个是重点,下节专门讲解。 注:这个配置浸染是对知足 id 为 businessService 的切点条件的方法进行事务增强,并且设置事务传播性级别为 REQUIRED。 事理阐发tx:advice 标签的解析 tx:advice 标签是利用 TxAdviceBeanDefinitionParser 进行解析的,下面看看解析时序图: enter image description here 如上时序图 BeanDefinitionBuilder 是一个建造者模式,用来布局一个 bean 定义,这个 bean 定义终极会天生一个 TransactionInterceptor 的实例; 步骤(5)通过循环解析 tx:attributes 标签里面的所有 tx:method 标签,每个 tx:method 对应一个 RuleBasedTransactionAttribute 工具,个中 tx:method 标签中除了可以配置事务传播性,还可以配置事务隔离级别,超时时间,是否只读,和回滚策略。 步骤(12)把所有的 method 标签的 RuleBasedTransactionAttribute 工具存在到了一个 map 数据构造,步骤(13)设置解析的所有属性到建造者模式的工具里面,步骤(14)利用建造者工具创建一个 bean 定义,步骤(15)则注册该 bean 到 Spring 容器。 注:tx:advice 浸染是创建一个 TransactionInterceptor 拦截器,内部掩护事务配置信息。 事务拦截器事理大略阐发 下面看下 TransactionInterceptor 的 invoke 方法: public Object invoke(final MethodInvocation invocation) throws Throwable { ... return invokeWithinTransaction(invocation.getMethod, targetClass, invocation::proceed); } protected Object invokeWithinTransaction(Method method, @able Class<?> targetClass, final InvocationCallback invocation) throws Throwable { // If the transaction attribute is , the method is non-transactional. TransactionAttributeSource tas = getTransactionAttributeSource; final TransactionAttribute txAttr = (tas != ? tas.getTransactionAttribute(method, targetClass) : ); final PlatformTransactionManager tm = determineTransactionManager(txAttr); final String joinpointIdentification = methodIdentification(method, targetClass, txAttr); if (txAttr == || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { // 标准事务,内部有getTransaction(开缘由务) 和commit(提交)/rollback(回滚)事务被调用. TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); Object retVal = ; try { //这是一个环抱关照,调用proceedWithInvocation激活拦截器链里面的下一个拦击器 retVal = invocation.proceedWithInvocation; } catch (Throwable ex) { // target invocation exception completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { cleanupTransactionInfo(txInfo); } commitTransactionAfterReturning(txInfo); return retVal; } ... } 个中 createTransactionIfNecessary 是主要方法,其内部逻辑处理流程如下图: enter image description here 注:Spring 事务管理通过配置一个 AOP 切面来实现,个中定义了一个切点用来决定对哪些方法进行方法拦截,定义了一个 TransactionInterceptor 关照,来对拦截到的方法进行事务增强,详细事务拦击器里面是怎么做的,读者可以结合上面的 TransactionInterceptor 方法的流程图结合源码来研究下,如果必要后面会再开一个 Chat 专门讲解 Spring 事务的实现,以及事务隔离性与传播性。 总结 本文以大纲的形式阐发了 AOP 事理,以及事务拦击器如何利用 AOP 来实现,由于篇幅限定并没有深入到每个实现细节进行讲解,希望读者能够依赖本文作为大纲,对照源码深入到大纲里面的细节进行研究,以便加深对 AOP 事理的理解和节制。