spring-aop-4.3.7.RELEASE 

  在《Spring AOP高级——源码实现(1)动态代理技术》中介绍了两种动态代理技术,当然在Spring AOP中代理对象的生成也是运用的这两种技术。本文将介绍Spring AOP如何通过JDK动态代理的方式创建代理对象。

  JDK动态代理以及CGLIB代理这两种生成代理对象的方式在Spring AOP中分别对应两个类:JdkDynamicAopProxy和CglibAopProxy,而AopProxy是这两个类的父接口。

  AopProxy接口中定义了两个基本方法:

 public interface AopProxy {
/**
*使用默认的类加载器生成代理对象,默认的类加载器通常是当前线程
*的上下文类加载器,可通过Thread#getContextClassLoader()获得
*/
Object getProxy();
/**
* 使用指定的类加载器创建代理对象,通常用于比较低级别的代理对象
* 创建,至于什么时候用这个暂时先放一放
*/
Object getProxy(ClassLoader classLoader);
}

  接着来看它的实现——JdkDynamicAopProxy。

 //①首先查看JdkDynamicAopProxy类的成员变量
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable { //JDK动态代理需要实现 InvocationHandler类 private static final long serialVersionUID = 5531744639992436476L; private static final Log logger = LogFactory.getLog(JdkDynamicAopProxy.class); /** 代理类的相关配置,这个类继承自ProxyConfig,都是代理类的相关配置*/
private final AdvisedSupport advised; /**
* 用于判断目标对象实现的接口是否定义了equals方法
*/
private boolean equalsDefined; /**
* 用于判断目标对象实现的接口是否定义了hashCode方法
*/
private boolean hashCodeDefined;

  类中有两个成员变量需要额外注意一下,一个是第14行的equalsDefined变量和第19行的hashCodeDefined变量。

  讨论它们之前需要首先明确一点,通常情况下,Spring AOP代理对象不会对equals和hashCode方法增强,注意这是在通常情况下,那什么是“通常情况”,什么又是“不通常情况”呢?

  如果目标对象直接重写Object对象的equals或hashCode方法,此时Spring AOP则不会对它增强,equalsDefined=false或hashCodeDefined=false;如果目标对象实现的接口定义了equals或hashCode方法,此时Spring AOP则会对它增强,equalsDefined=true或hashCodeDefined=true。所以“通常情况”就是我们并不会在接口定义equals或hashCode方法,“不通常情况”就是在有的特殊情况下在接口定义equals或hashCode方法。再换句话说,如果我们需要Spring AOP增强equals或hashCode方法则必须要在其目标对象的实现接口定义equals或hashCode方法。

 //②再来看看JdkDynamicAopProxy类的构造方法
public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException { //只有一个带AdvisedSupport类型的构造方法,这个类型上面提到过是生成代理类的相关配置,必须不能为空,否则将抛出参数异常的错误
Assert.notNull(config, "AdvisedSupport must not be null");
if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) { //AdvisedSupport配置类中需要定义通知器和目标源。
throw new AopConfigException("No advisors and no TargetSource specified");
}
this.advised = config; //赋值给成员变量
}

  构造方法中对于参数的校验有个比较特殊地方,构造方法不仅需要判断参数不能为空,而且要判断参数中的两个变量——advisors通知器和targetSource目标源。

  通知器在《Spring AOP高级——源码实现(2)Spring AOP中通知器(Advisor)与切面(Aspect)》中讲解过它是一个特殊的切面,而targetSource目标则是为了获取target目标对象,这两个都是实现AOP的重要组成部分必然不能为空。

 //③接下来是生成代理对象的getProxy方法
@Override
public Object getProxy() {
return getProxy(ClassUtils.getDefaultClassLoader()); //没有指定ClassLoader则通过Thread.currentThread.getContextClassLoader()获取当前线程上下文的类加载器。
} @Override
public Object getProxy(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); //这里就是上面提到过的判断的目标对象实现的接口是否定义了equals或hashCode方法,具体原因不再展开
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); //通过JDK生成代理对象。第一个ClassLoader代表创建类的类加载器,第二个表示需要被代理的目标对象的接口,第三个参数InvocationHandler叫做调用处理器,在这里它就是对象本身,调用的代理对象方法实际就是调用InvocationHandler接口中的invoke方法。
}

  第12行调用了AopProxyUtils.completeProxiedInterfaces(this.advised, true)方法,传入了Spring AOP代理对象配置对象,第二个参数表示是否暴露DecoratingProxy接口,如果设置为true则代理对象会实现DecoratingProxy接口,这个方法是在Spring4.3后新增的方法。来看看这个方法的实现。

 static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised, boolean decoratingProxy) {
Class<?>[] specifiedInterfaces = advised.getProxiedInterfaces(); //获取目标对象实现的接口
…//省略的代码是通过AdviceSupport没有获取到目标对象的实现接口时,则通过直接通过target目标对象来获取
boolean addSpringProxy = !advised.isInterfaceProxied(SpringProxy.class);//是否新增SpringProxy,在AdvisedSupport#isInterfaceProxied方法中会判断传入的接口是否已经由目标对象实现。此处传入SpringProxy.class判断目标对象是否已经实现该接口,如果没有实现则在代理对象中需要新增SpringProxy,如果实现了则不必新增。
boolean addAdvised = !advised.isOpaque() && !advised.isInterfaceProxied(Advised.class); //是否新增Adviced接口,注意不是Advice通知接口。ProxyConfig#isOpaque方法用于返回由这个配置创建的代理对象是否应该避免被强制转换为Advised类型。还有一个条件和上面的方法一样,同理,传入Advised.class判断目标对象是否已经实现该接口,如果没有实现则在代理对象中需要新增Advised,如果实现了则不必新增。
boolean addDecoratingProxy = (decoratingProxy && !advised.isInterfaceProxied(DecoratingProxy.class)); //是否新增DecoratingProxy接口,同样的判断条件有两个,第一个参数decoratingProxy,在调用completeProxiedInterfaces方法时传入的是true,第二个判断条件和上面一样判断被代理的目标对象是否已经实现了DecoratingProxy接口。通常情况下这个接口也会被加入到代理对象中,这是Spring4.3新增的。
int nonUserIfcCount = 0;
if (addSpringProxy) {
nonUserIfcCount++;
}
if (addAdvised) {
nonUserIfcCount++;
}
if (addDecoratingProxy) {
nonUserIfcCount++;
}
Class<?>[] proxiedInterfaces = new Class<?>[specifiedInterfaces.length + nonUserIfcCount]; //代理类的接口一共是目标对象的接口+上面三个接口SpringProxy、Advised、DecoratingProxy
System.arraycopy(specifiedInterfaces, 0, proxiedInterfaces, 0, specifiedInterfaces.length); //将目标对象的接口拷贝,这个方法在《System.arraycopy(src, srcPos, dest, destPos, length) 与 Arrays.copyOf(original, newLength)区别》有介绍
//下面就是讲那三个接口加入到specifiedInterfaces数组中并返回
int index = specifiedInterfaces.length;
if (addSpringProxy) {
proxiedInterfaces[index] = SpringProxy.class;
index++;
}
if (addAdvised) {
proxiedInterfaces[index] = Advised.class;
index++;
}
if (addDecoratingProxy) {
proxiedInterfaces[index] = DecoratingProxy.class;
}
return proxiedInterfaces;
}

  生成代理对象后,下一步当然就是调用代理方法,当然这就包括了调用AOP的增强方法以及目标对象的方法。  

  JDK动态代理生成的代理对象需要实现InvocationHandler接口和invoke方法,这个invoke方法就是JDK代理对象进行拦截的回调入口。 JdkDynamicAopProxy就实现了InvocationHandler接口,这个接口只有一个方法invoke。

public Object invoke(Object proxy, Method method, Object[] args)

  这个方法有三个参数:

  proxy:指的是我们所代理的那个真实的对象;

  method:指的是我们所代理的那个真实对象的某个方法的Method对象;

  args:指的是调用那个真实对象方法的参数。

  接下来一步一步查看JdkDynamicAopProxy是如何实现invoke方法的。

 //⑤代理对象的回调方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInvocation invocation;
Object oldProxy = null;
boolean setProxyContext = false; TargetSource targetSource = this.advised.targetSource;
Class<?> targetClass = null;
Object target = null; try {
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
// 这里就是之前提到过的,“通常情况”Spring AOP不会对equals方法进行拦截增强,所以这里判断如果目标对象没有定义equals方法的话,就会直接调用而不会增强
return equals(args[0]);
}
else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
//同上
return hashCode();
}
else if (method.getDeclaringClass() == DecoratingProxy.class) {
// 这里是上面的疑点,也是Spring4.3新出现的特性
return AopProxyUtils.ultimateTargetClass(this.advised);
}
else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
method.getDeclaringClass().isAssignableFrom(Advised.class)) {
//这个地方就有点意思了,Spring AOP不会增强直接实现Advised接口的目标对象,再重复一次,也就是说如果目标对象实现的Advised接口,则不会对其应用切面进行方法的增强。
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); //这个方法是一个对Java通过反射调用方法的封装
}
Object retVal; //方法的返回值
if (this.advised.exposeProxy) { //是否暴露代理对象,默认false可配置为true,如果暴露就意味着允许在线程内共享代理对象,注意这是在线程内,也就是说同一线程的任意地方都能通过AopContext获取该代理对象,这应该算是比较高级一点的用法了。
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
target = targetSource.getTarget(); //通过目标源获取目标对象
if (target != null) {
targetClass = target.getClass(); //获取目标对象Class对象
}
//...暂略
}

  上面可以说是对方法调用的一些预处理,有几个重要的地方:

  1. 第13-29行,对一些情况的特殊判断,主要是不对目标对象应用切面;
  2. 第31行,判断是否暴露代理对象,默认false不暴露,如果暴露则表示在线程内的任意位置都能通过AopContext获取代理对象;
  3. 第35行,通过目标源获取目标对象。

  下面可以说是重中之重,也就是具体是如何对目标对象应用切面对其方法进行增强的。

 //⑥获取拦截器链,并调用增强方法及目标对象的方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//...
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); //这个方法获取拦截器链。
if (chain.isEmpty()) { //拦截器链如果为空的话就直接调用目标对象的方法。.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); //直接调用目标对象的方法。
}
else { //通过ReflectiveMethodInvocation.proceed调用拦截器中的方法和目标对象方法
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); //ReflectiveMethodInvocation对象完成对AOP功能实现的封装
retVal = invocation.proceed(); //获取返回值
}
Class<?> returnType = method.getReturnType(); //获取返回值类型
if (retVal != null && retVal == target && returnType != Object.class && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
// 一些列的判断条件,如果返回值不为空,且为目标对象的话,就直接将目标对象赋值给retVal
retVal = proxy;
}
else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method); //没有返回值
}
return retVal;
23 }
//...暂略

  这段代码有3个比较关键的地方:

  1. 第5行,获取拦截器链,相当于是获取潜在的增强方法,只是潜在,后续还有匹配的判断;
  2. 第8行,没有获取到拦截器链,此时相当于直接调用目标对象的方法,AopUtils.invokeJoinpointUsingReflection方法实际是对JDK反射调用方法的一个封装;
  3. 第12行,这里就是进入拦截器链中增强方法和目标对象的调用的地方,关键。

  接下来就是进入ReflectiveMethodInvocation.proceed方法,来探讨下Spring AOP是如何对目标对象调用方法进行增强以及调用的。

 //⑦连接器链的调用,ReflectiveMethodInvocation#proceed,这是一个递归方法,退出条件就是调用完了拦截链中的所有拦截器方法后,再调用目标对象的方法。
这个方法的逻辑在于通过拦截器链,逐个获取其中的拦截器,再通过匹配判断,判断是否适用,如果适用则取出拦截器中的通知器并通过通知器的invoke方法调用,如果不适用则递归调用。
@Override
public Object proceed() throws Throwable {
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { //调用完了所有的拦截链中拦截器的增强方法,直接调用目标对象的方法并退出
return invokeJoinpoint(); //这个方法就是调用AopUtils.invokeJoinpointUsingReflection,上面提到过。
} Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex); //从拦截器链中获取拦截器
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) { //这里进行动态匹配
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) { //这里如果和定义的切点匹配,那么这个通知就会得到执行
return dm.interceptor.invoke(this);
}
else {
return proceed(); //不适用递归继续获取拦截器进行匹配、判断、调用
}
}
else { //这里判断出这个拦截器是一个MethodInterceptor则直接调用
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); //动态匹配失败则直接调用
}
}

  这里就是整个调用过程,同样有一个大的重点:

  第11行,这里是进行动态匹配,什么是动态匹配呢?我们给出如下的示例,按照xml配置一个通知器而不是切面。

 <!--定义通知器bean,需要实现Advice接口-->
<bean id="testAdvisor" class="com.springdemo.aop.MyAdvice"/>
<!--目标对象-->
<bean id="testPoint" class="com.springdemo.aop.TestPoint"/>
<!--advisor通知器-->
<bean id="testAop" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--目标对象实现的接口-->
<property name="proxyInterfaces">
<value>com.springdemo.aop.Test</value>
</property>
<!--拦截器,也就是上面定义的通知器beanId-->
<property name="interceptorNames">
<list>
<value>testAdvisor</value>
</list>
</property>
<!--目标对象,也就是上面定义的目标对象beanId-->
<property name="targetName">
<value>testPoint</value>
</property>
</bean>

  上面的注释已经比较清楚了,如果我们按照这样的方式来实现一个目标对象的方法增强,那么此时,在调用代理对象的方法时,也就是在执行上面ReflectiveMethodInvocation.proceed方法时是不会进行动态匹配的,因为我们在定义一个advisor通知器也就是上面的Myadvice类的时候是一定会实现Advice接口,如果我们定义的是MethodBeforeAdvice那么此时就已经确定一定会是前置通知。所以它一定会进入else那个分支去。

  如果,我们不是通过定义advisor通知器的方式,而是直接定义一个切面,那么,在我们定义切面这个类是是不需要实现任何接口的,其中的任意方法都可以作为前置或者后置通知,这取决于你的xml配置,例如下面的配置:

<aop:config>
<aop:aspect ref="aspectTest">
<aop:pointcut id="test" expression="execution(* com.demo.TestPoint.test())"/>
<aop:before method="doBefore" pointcut-ref="test"/>
<aop:after-returning method="doAfter" pointcut-ref="test"/>
</aop:aspect>
</aop:config>

  关于这个地方的理解,建议多写几个demo通过打断点的方式反复咀嚼。下一篇将会介绍Spring AOP是如何通过CGLib生成代理对象的,本文还有很多很多不足之处,希望有看到的人懂的不懂的都能指出来,非常非常感谢。

这是一个能给程序员加buff的公众号 

Spring AOP高级——源码实现(3)AopProxy代理对象之JDK动态代理的创建过程的更多相关文章

  1. Spring AOP高级——源码实现(1)动态代理技术

    在正式进入Spring AOP的源码实现前,我们需要准备一定的基础也就是面向切面编程的核心——动态代理. 动态代理实际上也是一种结构型的设计模式,JDK中已经为我们准备好了这种设计模式,不过这种JDK ...

  2. Spring AOP高级——源码实现(2)Spring AOP中通知器(Advisor)与切面(Aspect)

    本文例子完整源码地址:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/Spring%20AOP%E9%A ...

  3. Spring代理模式(jdk动态代理模式)

    有动态代理和静态代理: 静态代理就是普通的Java继承调用方法. Spring有俩种动态代理模式:jdk动态代理模式 和 CGLIB动态代理 jdk动态代理模式: 代码实现: 房东出租房子的方法(继承 ...

  4. Spring AOP源码分析(三):基于JDK动态代理和CGLIB创建代理对象的实现原理

    AOP代理对象的创建 AOP相关的代理对象的创建主要在applyBeanPostProcessorsBeforeInstantiation方法实现: protected Object applyBea ...

  5. Spring AOP部分源码分析

    Spring源码流程分析-AOP相关 根据Spring源码整理,其中Calculator为自定义的实现方法. AnnotationConfigApplicationContext()加载配置类的流程 ...

  6. Spring AOP的源码流程

    一.AOP完成日志输出 1,导入AOP模块 <dependency> <groupId>org.springframework</groupId> <arti ...

  7. 分析spring aop的源码实现

    AOP就是面向切面编程,我们可以从几个层面来实现AOP. 在编译器修改源代码,在运行期字节码加载前修改字节码或字节码加载后动态创建代理类的字节码,以下是各种实现机制的比较. spring AOP是Sp ...

  8. Spring 代理对象,cglib,jdk的问题思考,AOP 配置注解拦截 的一些问题.为什么不要注解在接口,以及抽象方法.

    可以被继承 首先注解在类上是可以被继承的 在注解上用@Inherited /** * Created by laizhenwei on 17:49 2017-10-14 */ @Target({Ele ...

  9. Spring动态代理的生成-如何判断是使用JDK动态代理还是CGlib代理

    前言 在上一篇文章中讲到了Spring是如何获取对应的Bean的增强,然后本次主要讲解一下Spring如何在获取到增强后创建Spring代理的. 在步入正题之前先给大家看一下Spring创建代理的大致 ...

随机推荐

  1. cocos2dx - 在MFC中使用cocos2dx

    本节主要讲一下如何在MFC窗口中使用cocos2dx 在做比较复杂的游戏,有时需要通过一些工具来编辑生成关卡或者特效,技能等的配置文件.为了方便配置,需要可以通过修改参数直观得到显示的效果.这就需要将 ...

  2. 简述static关键字、void与void *(void指针)、函数指针

    static关键字1.修饰局部变量,延长局部变量的生命周期.使变量成为静态局部变量,在编译时就为变量分配内存,直到程序退出才释放存储单元.2.修饰全局变量,限制全局变量的使用范围为本文件中.全局变量默 ...

  3. win10 uwp 绑定多数据

    经常我们需要绑定的数据有多个,当添加到集合控件的对象类型结构比较复杂,我们希望自己来定义排版布局,这时可以使用ItemTemplate用资源的定义 现在有数据 public class caddres ...

  4. OpenWRT (RT5350) 使能两个串口

    OpenWRT(RT5350) 默认使能一个串口(uartlite) ,当做console口了,另外一个串口(uartf)与gpio复用. 在查找资料的过程中,发现在新的内核中使用到了设备树(devi ...

  5. Python C++扩展

    Python C++扩展 前段时间看了一篇文章,http://blog.jobbole.com/78859/, 颇有感触,于是就结合自己工作中的知识作了一个简单的Python移动侦测:移动侦测的算法使 ...

  6. SpringMVC 配置

    1.在WEB-INF\web.xml中定义前端控制器 <servlet> <servlet-name>springmvc</servlet-name> <se ...

  7. sublime中安装package control总是失败

    今天下载了个sublime编辑器,要运行vue文件,想让vue也能高亮显示,在网上搜了一下如何安装.但总是提示控制器没有安装Package Control:There are no packages ...

  8. 一张表搞懂各种 Docker 监控方案 - 每天5分钟玩转 Docker 容器技术(86)

    前面我们已经介绍了ps/top/stats.Sysdig.Weave Scope.cAdvisor 和 Prometheus 多种容器监控工具和方案,是时候做一个比较了.下面将从五个方面来对比它们之间 ...

  9. URL的编码和解码

    URL的编码和解码 参考:阮一峰--关于URL编码 1 为什么要URL编码 在因特网上传送URL,只能采用ASCII字符集 也就是说URL只能使用英文字母.阿拉伯数字和某些标点符号,不能使用其他文字和 ...

  10. 径向基网络(RBF network)

    来源:http://blog.csdn.net/zouxy09/article/details/13297881 1.径向基函数 径向基函数(Radical Basis Function,RBF)方法 ...