Sprig AOP原理及源码解析

在介绍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的底层代码及其原理。

后期会把所有的工程实现代码,放在我的GitHub上,到时候我会更新文章。

一、AOP的Demo

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

Spring的AOP是支持JDK的动态代理和Cglib的动态代理的。JDK的动态代理是针对接口的,而Cglib是针对类的。本文针对JDK的动态代理。

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

package com.weili.cn;

/**
* Created by zsqweilai on 17/6/27.
*/
public interface AnimalInterface {
   public abstract void 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 zsqweilaion 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增强。具体在这里的增强,就是在执行这些切点方法之前和之后,会分别执行animalEmpty 和 animalFull方法。

<?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/beanshttp://www.springframework.org/schema/beans/spring-beans-4.0.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-4.0.xsdhttp://www.springframework.org/schema/aophttp://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吃饱了

然后回去执行invokeJoinpoint方法,

二、AOP源码分析

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

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

如下图,这个是在第一行代码执行完毕后,ctx的各个属性。可以在下图看到,singlentonObjects中,已经存放了代理生成的animal。生层bean的过程在之前的里面已经讲的比较清楚了,这里就不再说明。毕竟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方法。

进入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方法。

原文地址:https://www.cnblogs.com/zhaosq/p/10020760.html

时间: 2024-10-10 13:49:48

Sprig AOP原理及源码解析的相关文章

【特征匹配】BRIEF特征描述子原理及源码解析

相关:Fast原理及源码解析 Harris原理及源码解析 SIFT原理及源码解析 SURF原理及源码解析 转载请注明出处: http://blog.csdn.net/luoshixian099/article/details/48338273 传统的特征点描述子如SIFT,SURF描述子,每个特征点采用128维(SIFT)或者64维(SURF)向量去描述,每个维度上占用4字节,SIFT需要128×4=512字节内存,SURF则需要256字节.如果对于内存资源有限的情况下,这种描述子方法显然不适应

【特征匹配】RANSAC算法原理与源码解析

转载请注明出处:http://blog.csdn.net/luoshixian099/article/details/50217655 随机抽样一致性(RANSAC)算法,可以在一组包含"外点"的数据集中,采用不断迭代的方法,寻找最优参数模型,不符合最优模型的点,被定义为"外点".在图像配准以及拼接上得到广泛的应用,本文将对RANSAC算法在OpenCV中角点误匹配对的检测中进行解析. 1.RANSAC原理 OpenCV中滤除误匹配对采用RANSAC算法寻找一个最佳

LinkedList原理及源码解析

简介 LinkedList是一个双向线性链表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer).由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而顺序表相应的时间复杂度分别是O(logn)和O(1). UML关系图 使用示例 LinkedList list = new LinkedList<>(); //新增 list.add("a"

Spring核心框架 - AOP的原理及源码解析

一.AOP的体系结构 如下图所示:(引自AOP联盟) 层次3语言和开发环境:基础是指待增加对象或者目标对象:切面通常包括对于基础的增加应用:配置是指AOP体系中提供的配置环境或者编织配置,通过该配置AOP将基础和切面结合起来,从而完成切面对目标对象的编织实现. 层次2面向方面系统:配置模型,逻辑配置和AOP模型是为上策的语言和开发环境提供支持的,主要功能是将需要增强的目标对象.切面和配置使用AOP的API转换.抽象.封装成面向方面中的逻辑模型. 层次1底层编织实现模块:主要是将面向方面系统抽象封

【Spring】Spring IOC原理及源码解析之scope=request、session

一.容器 1. 容器 抛出一个议点:BeanFactory是IOC容器,而ApplicationContex则是Spring容器. 什么是容器?Collection和Container这两个单词都有存放什么东西的意思,但是放在程序猿的世界,却注定是千差万别.Collection,集合,存放obj instanceof Class为true的一类对象,重点在于存放:Container,容器,可以存放各种各样的obj,但不仅仅是存放,他被称为容器,更重要的是他能管理存放对象的生命周期和依赖. 容器:

【特征匹配】SURF原理与源码解析

SURF (Speed Up Robust Features)是SIFT改进版也是加速版,提高了检测特征点的速度,综合性能要优于SIFT. 下面先逐次介绍SURF的原理,最后解析opencv上SURF源码. 转载请注明出处:http://blog.csdn.net/luoshixian099/article/details/47778143 1.积分图像 SURF是对积分图像进行操作,从而实现了加速,采用盒子滤波器计算每个像素点的Hessian矩阵行列式时,只需要几次加减法运算,而且运算量与盒子

可阻塞队列-原理及源码解析

阻塞原理:比如,一个队列中有8个格子,代表可放入8条数据,当一条信息到来就放入一个格子中,然后就进行处理.但是这个时候一次性来了8条数据,格子满了,数据还没有处理完,就来个一条数据.这个时候就把这条数据进行阻塞. 示例:假定有一个绑定的缓冲区,它支持 put 和 take 方法.如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞:如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞.我们喜欢在单独的等待 set 中保存 put 线程和

Java基础知识强化之集合框架笔记11:Collection集合之迭代器的原理及源码解析

1. 迭代器为什么不定义成一个类,而是定义为一个接口 ?  答:假设迭代器定义的是一个类,这样我们就可以创建该类的对象,调用该类的方法来实现集合的遍历.但是呢? 我们想想,Java中提供了很多的集合类,而这些集合类的数据结构是不同的,所以,存储的方式和遍历的方式应该是不同的.进而它们的遍历方式也应该不是一样的,最终,就没有定义迭代器类.        而无论你是哪种集合,你都应该具备获取元素的操作,而且,最好在辅助于判断功能,这样,在获取前,先判断.这样的话就不容易出错.也就是说,判断功能和获取

6.Spark streaming技术内幕 : Job动态生成原理与源码解析

原创文章,转载请注明:转载自 周岳飞博客(http://www.cnblogs.com/zhouyf/) Spark streaming 程序的运行过程是将DStream的操作转化成RDD的操作,Spark Streaming 和 Spark Core 的关系如下图(图片来自spark官网) Spark Streaming 会按照程序设定的时间间隔不断动态生成Job来处理输入数据,这里的Job生成是指将Spark Streaming 的程序翻译成Spark内核的RDD操作,翻译的过程并不会触发J