欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring AOP的实现及源码解析

程序员文章站 2022-04-09 18:02:43
在介绍AOP之前,想必很多人都听说AOP是基于动态代理和反射来实现的,那么在看AOP之前,你需要弄懂什么是动态代理和反射及它们又是如何实现的。 想了解JDK的动态代理及反射的实现和源码分析,请参见下面三篇文章 JDK的动态代理源码分析之一 (http://blog.csdn.net/weililan ......

      在介绍aop之前,想必很多人都听说aop是基于动态代理和反射来实现的,那么在看aop之前,你需要弄懂什么是动态代理和反射及它们又是如何实现的。

想了解jdk的动态代理及反射的实现和源码分析,请参见下面三篇文章

jdk的动态代理源码分析之一 (http://blog.csdn.net/weililansehudiefei/article/details/73655925)

jdk的动态代理源码分析之二(http://blog.csdn.net/weililansehudiefei/article/details/73656923)

java反射机制 (http://blog.csdn.net/weililansehudiefei/article/details/70194940)

那么接下里进入aop的环节。

aop即面向切面编程,刚学aop的时候,单是各种aop的概念都搞的有点懵,什么切面,切点,通知,织入、连接点、目标对象。。。。aop的原理都没看呢,这些词语的意思就已经让人不想看了。本文将在实现aop的时候,讲解我理解的这些aop的术语,对应的aop的代码和动作。

本文将先从aop代码实现入手,然后分析aop的底层代码及其原理。

一、aop的demo

如果我们把对象的继承关系看成纵向关系,就像一棵树,多个不同类的多个继承关系就相当于有一排的树。aop的好处就在于,你想对这些树进行相同的操作时,这个时候,不用纵向的为每个树定义操作方法,你只需要横向的一刀切,给他们提供个共有的操作方法。

spring的aop是支持jdk的动态代理和cglib的动态代理的。jdk的动态代理是针对接口的,而cglib是针对类的。本文针对jdk的动态代理。

首先定义一个接口:起名字时候特意给这个接口名,带上了interface,这样后面会更引人注意一些。接口很简单,里面一个抽象方法eat()

实现类:作为一个吃货,实现类里面当然得打印 chi  chi  chi。撑死我吧!!!
这个实现类里面,只有一个方法,这个方法就是aop的切点。虽然切点这个概念本身并不一定是method,但在spring中,所有的切点都是method。我们增强的是方法。

package com.weili.cn;

/**
* created by zsqweilai  on 17/6/27.
动物接口实现类
*/
public class animal implements animalinterface{

public void eat() {
system.out.println("animal类中 chi chi chi");
}
}

切面类,又称增强类。因为我们是要用这个类的方法,来给原先的切点方法增强。切面类中,我们要去执行的方法,称为通知。所谓织入通知,就是将切面类里面的方法,和切点的方法进行联系。

 package com.weili.cn;

import org.aopalliance.intercept.joinpoint;

/**
* created by zsqweilai on 17/6/27.
*/
public class adviceanimal {
  public void animalempty(){
   //system.out.println("joint before "+ joinpoint.getclass().getname());
   system.out.println("我饿了");
 }

   public void animalfull(){
    system.out.println("吃饱了");
 }

   public void animaleat(){
   system.out.println("正在吃");
 }

}

接下来通过xml配置的方式,在xml文件里面配置aop。
配置<aop:pointcut>的时候,通过expressi表达式,定义了com.weili.cn这个包下的所有类的所有方法 为切入点。也就是说,这个包下的所有方法,在调用执行的时候,会被spring增强。具体在这里的增强,就是在执行这些切点方法之前和之后,会分别执行animalemptyanimalfull方法。

<?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.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">


<bean id="animal" class="com.weili.cn.animal"/>

<bean id="adviceanimal" class="com.weili.cn.adviceanimal"/>

<aop:config>
<aop:aspect id="myaop" ref="adviceanimal">
<aop:pointcut id="logpointcut" expression="execution(* com.weili.cn.*.*(..))" />
<aop:before method="animalempty" pointcut-ref="logpointcut" />
<aop:after method="animalfull" pointcut-ref="logpointcut" />
</aop:aspect>
</aop:config>
</beans>

最后就是调用的方法了。
我得说明一点,在我们进行spring-aop.xml解析的时候,aop还没实现呢。在第二行getbean的时候,才真正进行aop。具体的源码那里 会说明。

package com.weili.cn;
import org.springframework.context.support.classpathxmlapplicationcontext;
/**
* hello world!
*
*/
public class app 
{
public static void main( string[] args )
{
classpathxmlapplicationcontext ctx = new classpathxmlapplicationcontext("spring-aop.xml");
animalinterface animal = (animalinterface) ctx.getbean("animal");
animal.eat();
}
}


紧接着就是output输出了。所以,我们可以看到,获取的bean,确实是增强后的bean。那么就赶紧看看源码吧。

我饿了
animal类中 chi chi chi
吃饱了

二、aop源码分析

源码解析这块,首先就是bean加载。之前也说了,aop标签也是自定义标签,它的解析也和我们之前自定义标签一样,走自定义标签的解析流程。不同的是,aop调用的是aop自己的解析器。由于在 spring源码解析之二 ------ 自定义标签的解析和注册 中已经很详细的描述了自定义标签的解析流程,所以这里我们就不再去一一看bean标签的解析注册。

所以aop的源码分析,我们将从调用类里面的第二行,ctx.getbean("animal")开始。在你调试走到这里的时候,在ctx中可以看到解析和注册的bean,我们不妨先来看一下。

如下图,这个是在第一行代码执行完毕后,ctx的各个属性。可以在下图看到,singlentonobjects中,已经存放了代理生成的animal。生层bean的过程在之前的里面已经讲的比较清楚了,这里就不再说明。毕竟aop嘛,我们需要知道,它是如何在我们需要执行的方法前后将我们需要执行的方法执行完成的。

Spring AOP的实现及源码解析

ctx.getbean("animal")获取完animal bean后,接下来调用eat()方法。这个时候,会进入jdkdynamicaopproxy类的invoke方法。
在这个invoke方法中,先是获取代理类targetclass,然后根据method和targetclass获取此方法对应的拦截器执行链chain。

public object invoke(object proxy, method method, object[] args) throws throwable {
object oldproxy = null;
boolean setproxycontext = false;
targetsource targetsource = this.advised.targetsource;
class<?> targetclass = null;
object target = null;

boolean var10;
try {
if(this.equalsdefined || !aoputils.isequalsmethod(method)) {
if(!this.hashcodedefined && aoputils.ishashcodemethod(method)) {
integer var18 = integer.valueof(this.hashcode());
return var18;
}

object retval;
if(!this.advised.opaque && method.getdeclaringclass().isinterface() && method.getdeclaringclass().isassignablefrom(advised.class)) {
retval = aoputils.invokejoinpointusingreflection(this.advised, method, args);
return retval;
}

if(this.advised.exposeproxy) {
oldproxy = aopcontext.setcurrentproxy(proxy);
setproxycontext = true;
}

target = targetsource.gettarget();
if(target != null) {
targetclass = target.getclass();
}
list<object> chain = this.advised.getinterceptorsanddynamicinterceptionadvice(method, targetclass);//获取执行链
if(chain.isempty()) {
object[] argstouse = aopproxyutils.adaptargumentsifnecessary(method, args);
retval = aoputils.invokejoinpointusingreflection(target, method, argstouse);
} else {
methodinvocation invocation = new reflectivemethodinvocation(proxy, target, method, args, targetclass, chain);
retval = invocation.proceed();
}

class<?> returntype = method.getreturntype();
if(retval != null && retval == target && returntype.isinstance(proxy) && !rawtargetaccess.class.isassignablefrom(method.getdeclaringclass())) {
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);
}

object var13 = retval;
return var13;
}

var10 = boolean.valueof(this.equals(args[0]));
} finally {
if(target != null && !targetsource.isstatic()) {
targetsource.releasetarget(target);
}

if(setproxycontext) {
aopcontext.setcurrentproxy(oldproxy);
}
}
return var10;
}

这个chain的内容如下。通过名字可以看到,一个是afteradvice,一个是beforeadvice。获取chain后,构造出一个methodinvoke方法,然后执行proceed方法。

Spring AOP的实现及源码解析

进入proceed方法。currentinterceptorindex的初始化值为-1.紧接着就如invoke方法。这里的this是我们的eat方法。

public object proceed() throws throwable {
if(this.currentinterceptorindex == this.interceptorsanddynamicmethodmatchers.size() - 1) {
return this.invokejoinpoint();
} else {
object interceptororinterceptionadvice =
this.interceptorsanddynamicmethodmatchers.get(++this.currentinterceptorindex);
if(interceptororinterceptionadvice instanceof interceptoranddynamicmethodmatcher) {
interceptoranddynamicmethodmatcher dm = (interceptoranddynamicmethodmatcher)interceptororinterceptionadvice;
return dm.methodmatcher.matches(this.method,
this.targetclass, this.arguments)?dm.interceptor.invoke(this):this.proceed();
} else {
return ((methodinterceptor)interceptororinterceptionadvice).invoke(this);
}
}
}

在invoke方法里,这里的mi是我们的interface里面的eat方法。然后执行mi的proceed()方法。

public object invoke(methodinvocation mi) throws throwable {
methodinvocation oldinvocation = (methodinvocation)invocation.get();
invocation.set(mi);

object var3;
try {
var3 = mi.proceed();
} finally {
invocation.set(oldinvocation);
}

return var3;
}

这个时候,会继续回到开始时候的proceed方法。这个时候获取到的是

    interceptororinterceptionadvice,也就是前面拦截器的list里面的第二个,after的那个方法。然后继续递归调用,会到链表的最后一个before方法。
    最终会调用before里面的方法,

public object invoke(methodinvocation mi) throws throwable {
this.advice.before(mi.getmethod(), mi.getarguments(), mi.getthis());
return mi.proceed();
}

    然后回去执行invokejoinpoint方法,

public static object invokejoinpointusingreflection(object target, method method, object[] args) throws throwable {
   try {
   reflectionutils.makeaccessible(method);
   return method.invoke(target, args);
   } catch (invocationtargetexception var4) {
      throw var4.gettargetexception();
   } catch (illegalargumentexception var5) {
      throw new aopinvocationexception("aop configuration seems to be invalid: tried calling method [" + method + "] on target ["         + target + "]", var5);
  } catch (illegalaccessexception var6) {
      throw new aopinvocationexception("could not access method [" + method + "]", var6);
   }
  }
}

  最后执行after方法。