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. nodejs+mongoose操作mongodb副本集实例

    继上一篇设置mongodb副本集之后,开始使用nodejs访问mongodb副本集: 1:创建项目     express 项目名称 2:npm install mongoose    安装mongo ...

  2. Codeforces A. Trip For Meal

    A. Trip For Meal time limit per test 1 second memory limit per test 512 megabytes input standard inp ...

  3. Java-JMS Bug记录

    1.Junit测试时,使用for循环发送JMS(ReportQuestionSender)的时候,监听器(ReportQuestionListener)只接受到一条消息. 原因:使用Junit测试会阻 ...

  4. RT-thread 利用Scons 工具编译提示python编码错误解决办法

    错误信息: scons: Reading SConscript files ...UnicodeDecodeError: 'ascii' codec can't decode byte 0xbd in ...

  5. Python学习笔记(二)-Python文件类型及编程模式

    Python环境搭建:linux,Windows... Linux下:[root@localhost StudyPython]# python #进入交互模式Python 2.7.11 (defaul ...

  6. LeetCode 531. Longly Pixel I (孤独的像素之一) $

    Given a picture consisting of black and white pixels, find the number of black lonely pixels. The pi ...

  7. LeetCode 122. Best Time to Buy and Sell Stock II (买卖股票的最好时机之二)

    Say you have an array for which the ith element is the price of a given stock on day i. Design an al ...

  8. PhiloGL学习(4)——三维对象、加载皮肤

    前言 上一篇文章中介绍了如何响应鼠标和键盘事件,本文介绍如何加载三维对象并实现给三维对象添加一个漂亮的皮肤. 一. 原理分析 我对三维的理解为:所谓三维对象无非是多个二维对象拼接到一起,贴图就更简单了 ...

  9. 【JAVA零基础入门系列】Day11 Java中的类和对象

    今天要说的是Java中两个非常重要的概念--类和对象. 什么是类,什么又是对象呢?类是对特定集合的概括描述,比如,人,这个类,外观特征上,有名字,有年龄,能说话,能吃饭等等,这是我们作为人类的相同特征 ...

  10. 教你用SVG画出一条龙

    先看demo,九十七度 其实使用svg画出这条龙很简单,关键不在于怎么使用svg,而在于你的美术功底,哈哈. 好吧,当然基础是不能忽略的,先看下这条龙的代码: <svg id="lon ...