Spring的AOP实现原理

Spring的AOP实现原理本学习笔记将尽可能的将AOP的知识讲解的通俗易懂,先从一个典型的问题出发,引入AOP这个概念,介绍AOP的基本概念,再到Spring中的AOP的实现方案,最后进行一个简单的总结归纳。本学习笔记中不考虑cglib、也不会太关注SpringAOP如何使用,而是尽可能的简单的说清楚AOP的工作原理。笔记中贴出的源代码均是Spring5.1.7-RELEASE版本问题提出如下代码块,现在需要统计这个方法执行的耗时情况publicvoidrunTask(){doSome

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

本学习笔记将尽可能的将AOP的知识讲解的通俗易懂,先从一个典型的问题出发,引入AOP这个概念,介绍AOP的基本概念,再到Spring中的AOP的实现方案,最后进行一个简单的总结归纳。本学习笔记中不考虑cglib、也不会太关注Spring AOP如何使用,而是尽可能的简单的说清楚AOP的工作原理。

笔记中贴出的源代码均是Spring 5.1.7-RELEASE 版本

问题提出

如下代码块,现在需要统计这个方法执行的耗时情况

public void runTask() {
    doSomething();
}

一次性的解决肯定非常简单,直接添加一个时间记录即可,如下代码块

public void runTask() {
    long start = System.currentTimeMillis();
    doSomething();
    System.out.println(System.currentTimeMillis() - start);
}
  • 改写原方法:就如上述直接添加时间点记录,针对一两个简单的需求这种方案是最快最高效的,但是弊端也是非常明显的。直接把非业务功能和业务功能耦合在一起、需要改动太大的业务功能、不能灵活修改,如果下一次需要把时间记录去掉,换成统计次数调用,那么所有的地方都得改动,成本非常大,稍有不慎就容易出错
  • 适配包装:即把原对象通过组合的方式包装到一个代理对象中,类似于适配器模式,如下图

Spring的AOP实现原理

⚠️ 这不是说真的就按照适配器模式去开发,而是采取类似的套路。新弄一个类然后新弄一个对应的方法,在新创建的方法里面再具体调用目标对象的方法。AOP也就是为了解决这类问题所提出的一种解决方案。

AOP 的基本概念

AOP(Aspect Oriented Programming)是基于切面编程的,可无侵入的在原本功能的切面层添加自定义代码,一般用于日志收集、权限认证等场景。

在了解AOP包含的组件之前,如果是你去设计实现一套解决方案会如何设计呢?

思考几分钟得处一些必备点~

需要知道在什么地方进行切面操作
需要知道切面操作的具体内容
如果有多个切面操作,应该得有一个先后执行的顺序

事实上AOP也确实是按照这个类似的思路去实现的,先来了解下AOP包含的几个概念

  • Jointpoint(连接点):具体的切面点点抽象概念,可以是在字段、方法上,Spring中具体表现形式是PointCut(切入点),仅作用在方法上。
  • Advice(通知): 在连接点进行的具体操作,如何进行增强处理的,分为前置、后置、异常、最终、环绕五种情况。
  • 目标对象:被AOP框架进行增强处理的对象,也被称为被增强的对象。
  • AOP代理:AOP框架创建的对象,简单的说,代理就是对目标对象的加强。Spring中的AOP代理可以是JDK动态代理,也可以是CGLIB代理。
  • Weaving(织入):将增强处理添加到目标对象中,创建一个被增强的对象的过程

总结为一句话就是:在目标对象(target object)的某些方法(jointpoint)添加不同种类的操作(通知、增强操处理),最后通过某些方法(weaving、织入操作)实现一个新的代理目标对象。

动态代理

在继续学习之前有必要介绍一下动态代理。动态代理(Dynamic Proxy)是采用Java的反射技术,在运行时按照某一接口要求创建一个包装了目标对象的新的代理对象,并通过代理对象实现对目标对象的控制操作。

使用动态代理需InvocationHandler + Proxy,可看如下代码块

public class HelloInvocationHandle implements InvocationHandler {
    private Object object;
    public HelloInvocationHandle(Object o) {
        this.object = o;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("method: " + method.getName() + " is invoked");
        System.out.println("proxy: " + proxy.getClass().getName());
        Object result = method.invoke(object, args);
        // 反射方法调用
        return result;
    }
}

// HelloWorld 是一个接口,此处没有贴出来
Class<?> proxyClass = Proxy.getProxyClass(HelloWorld.class.getClassLoader(), HelloWorld.class);
Constructor cc = proxyClass.getConstructor(InvocationHandler.class);
InvocationHandler ihs = new HelloInvocationHandle(new HelloWorldImpl());
HelloWorld helloWorld = (HelloWorld) cc.newInstance(ihs);

套路就是先获取Proxy生成的class,然后获取去其中使用了InvocationHandler作为参数的构造器,使用反射newInstance 实现代理对象helloWorld的生成,当然Proxy也提供了更加方便的方法给我们使用

final InvocationHandler in = new HelloInvocationHandle(new HelloWorldImpl());
HelloWorld helloWorld = (HelloWorld) Proxy.newProxyInstance(
    HelloWorld.class.getClassLoader(),    // 被代理对象的类加载器
    HelloWorld.class.getInterfaces(),    // 被代理对象的接口(数组,可保护多个)
    in);   // InvocationHandler实例对象

此外可以使用ProxyGenerator.generateProxyClass方法去获取到动态生成的代理类实际内容,具体如下

Spring的AOP实现原理

继承了Proxy类,而且其构造函数传入的确实是一个InvocationHandler实例

Spring的AOP实现原理

这个方法最后调用相当于InvocationHandler.invoke,经过代理类的保证调用链路就到了HelloInvocationHandle类的invoke方法中,再利用反射调用被代理对象的方法。

接下来就来学习和了解下Spring AOP中最关键的两个类ProxyFactory、ProxyFactoryBean学习AOP的实现原理

ProxyFactory

ProxyFactory或许会比较陌生,可是无论是使用注解的方式还是XML的方式百转千回后还是会去创建ProxyFactory对象,所以忽略Spring前面一系列的操作,利用ProxyFactory做为入口,直面感受和学习AOP代理对象是如何生成的,demo如下

// 本代码来自官方单元测试NameMatchMethodPointcutTests类中的代码
// 在引入spring aop的模块环境下可直接运行
public void setup() {
    ProxyFactory pf = new ProxyFactory(new SerializablePerson());  // 1
    nop = new SerializableNopInterceptor();   // 2
    pc = new NameMatchMethodPointcut();  // 3
    pf.addAdvisor(new DefaultPointcutAdvisor(pc, nop));  // 4
    proxied = (Person) pf.getProxy(); // 5
    // proxied就是生成的AOP代理对象
}

上面的5个步骤每一个都很关键,现在就逐一进行解释

1、ProxyFactory实例化后传入的被代理对象,会被存储到TargetSource对象中,可通过getTarget方法获取到具体的被代理对象,为什么会存储到TargetSource对象中后面会说明,再一个就是获取代理对象可能存在的接口情况也被存储到interfaces列表中。

2、实例化一个SerializableNopInterceptor对象,这是一个实现了MethodInterceptor接口的类,里面的invoke方法是提供给外界触发该增强操作的入口,类图如下:

Spring的AOP实现原理

还记得上面介绍AOP的基本概念时说的Advice通知么?其实这就是一个增强器,包含了我们需要增强的功能,日志的收集、权限认证的具体代码就是写在这些增强器中的。通过调用invoke实现相关的非业务功能。后面会具体说到是谁触发了invoke方法调用。

3、实例化了一个NameMatchMethodPointcut对象,一个非常简单的基于名字匹配的切入点,通俗的说就是通过名字判断是否需要添加通知

Spring的AOP实现原理

其实现了Pointcut接口,并且Pointcut接口包含了ClassFilter getClassFilter();MethodMatcher getMethodMatcher();通过这个名字也能看的出来一个是类过滤器,一个是方法过滤器,两者共同作用就可以判断添加增强器的位置。

曾经使用过Spring AOP的小伙伴们是否记得自己的代码里写过如下类似的注解代码

@Pointcut("@annotation(XXXXAnnotation)")   // 匹配的是 方法添加XXXXAnnotation注解
@Pointcut("execution(public void com.XXXXX.controller.*.*(..))") 
// 匹配的是 public类型 返回void并且是com.XXXXX.controller文件夹下面的所有类方法

从类名称NameMatchMethodPointcut判断是通过方法名称匹配的,可是Pointcut接口却告诉我们是有类匹配和方法匹配两种,那意味着NameMatchMethodPointcut肯定有默认了类过滤的操作,看下StaticMethodMatcherPointcut类,代码如下

public abstract class StaticMethodMatcherPointcut extends StaticMethodMatcher implements Pointcut {
    private ClassFilter classFilter = ClassFilter.TRUE;
    // 直接就定义好了类过滤对象ClassFilter.TRUE,也就是下面的TrueClassFilter对象
    public void setClassFilter(ClassFilter classFilter) {
        this.classFilter = classFilter;
    }

final class TrueClassFilter implements ClassFilter, Serializable {
     // 这还是经典的单例写法
    public static final TrueClassFilter INSTANCE = new TrueClassFilter();
    private TrueClassFilter() {
    }

    @Override
    public boolean matches(Class<?> clazz) {
           // 重点在这,默认全部返还true
        return true;
    }

    private Object readResolve() {
        return INSTANCE;
    }
}

现在知道了NameMatchMethodPointcut是调用了TrueClassFilter单例,所以每一次通过类过滤时,都会返回true,也就是都命中,从而实现了忽略类匹配的操作机制。

到现在2实现了一个通知(增强器)、3实现了一个切入点,那么现在应该需要把2和3组合起来实现切点增强,继续看4

4、pf.addAdvisor(new DefaultPointcutAdvisor(pc, nop));,实例化了DefaultPointcutAdvisor对象,参数传入了实例化好的通知和切入点,形成了一个Advisor添加到了ProxyFacotry的advisors列表中

在实际的spring服务中,可能存在多个通知点和切入点,需要通过各种匹配的规则组合成一系列的Advisor对象,然后添加到对应的ProxyFacotry对象中,以便后面的织入

5、实例化代理对象,pf.getProxy()方法写的是createAopProxy().getProxy();。大致的可以看出来是先创建一个AopProxy对象,然后调用其getProxy()方法返回。先来看看如何创建AopProxy的

// ProxyCreatorSupport 类
private AopProxyFactory aopProxyFactory;
public ProxyCreatorSupport() {
    this.aopProxyFactory = new DefaultAopProxyFactory();  // 1
}

// ProxyFactory 类
public AopProxyFactory getAopProxyFactory() {
    return this.aopProxyFactory;
}
protected final synchronized AopProxy createAopProxy() {
    if (!this.active) {
        activate();
    }
    return getAopProxyFactory().createAopProxy(this);  // 2
}

原来aopProxyFactory默认就是DefaultAopProxyFactory对象,通过其createAopProxy方法返回一个AopProxy对象,并且这传递的参数是this,有必要贴一下ProxyFactory的UML图

Spring的AOP实现原理

如圈住的地方是一个AdvisedSupport类,也包含了当前代理类的一些信息。来到DefaultAopProxyFactory类

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
        Class<?> targetClass = config.getTargetClass();
        if (targetClass == null) {
            throw new AopConfigException("TargetSource cannot determine target class: " +
                    "Either an interface or a target is required for proxy creation.");
        }
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
        return new ObjenesisCglibAopProxy(config);
    }
    else {
        return new JdkDynamicAopProxy(config);
    }
}

参数传递的是AdvisedSupport对象,而ProxyFactory又是继承AdvisedSupport的,所以上面的this参数是正常的。通过对optimize、proxyTargetClas、是否存在对象接口三个条件判断选择是生成JdkDynamicAopProxy还是ObjenesisCglibAopProxy。这里也就是AOP判断使用动态代理还是CGLIB的地方

在xml配置中添加了proxy-target-class属性也就是上面说的config.isProxyTargetClass()判断操作,当设置为true时,就会使用CGLIB

继续深入,进入到JdkDynamicAopProxy的getProxy()方法中

@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
    if (logger.isTraceEnabled()) {
        logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
    }
    Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
    // 明确各种需要实现功能的接口
    findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

看到这是不是很熟悉,就是我们上面所说的动态代理Proxy.newProxyInstance方法完成代理类的实例化
到这里整个的代理对象就生成了,其实梳理一遍整个流程还是比较清晰的

代理对象调用

动态代理对象生成后调用的入口都是InvocationHandler对象的invoke方法,而且生成代理类的InvocationHandler对象参数传入就是JdkDynamicAopProxy本身

Spring的AOP实现原理

原来JdkDynamicAopProxy也实现了InvocationHandler接口,那么其invoke方法应该包含了具体的调用逻辑

// 精简了很多代码,但是并不影响主流程的学习
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    MethodInvocation invocation;
    Object oldProxy = null;
    boolean setProxyContext = false;
    TargetSource targetSource = this.advised.targetSource;
    Object target = null;
    try {
        target = targetSource.getTarget();
        Class<?> targetClass = (target != null ? target.getClass() : null);
        
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        // 1 获取增强器执行链
        if (chain.isEmpty()) {
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
            // 2 无增强器调用链,直接通过反射调用target 被代理对象的对应method方法
            retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
        }
        else {
              // 3 生成了新的MethodInvocation对象,开始执行增强器调用执行链
            invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
            retVal = invocation.proceed();
        }
        return retVal;
    }
}

1、获取增强器执行链,具体实现在DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice方法中

对切点的过滤匹配,也就是上面说的类过滤和方法过滤,调用类过滤matches方法+方法过滤matches方法,返回true添加到返回的容器中。如果是Interceptor对象则直接添加至返回的容器中。最后生成可被调用的增强器执行链

2、反射method.invoke 调用操作

3、包装成了ReflectiveMethodInvocation对象,然后调用其proceed方法

public Object proceed() throws Throwable {
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
           // 运行到最后了执行被代理对象的方法
        return invokeJoinpoint();
    }

    Object interceptorOrInterceptionAdvice =
            this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
    // 从增强器执行链获取一个增强器,索引值currentInterceptorIndex+1
    if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
        // 动态参数匹配,匹配后后方可执行
        InterceptorAndDynamicMethodMatcher dm =
                (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
        Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
        if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
            return dm.interceptor.invoke(this);
        }
        else {
            return proceed();
        }
    }
    else {
        // 一般增强器调用invoke
        return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
    }
}

上面说的 ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);,调用的是methodinterceptor的invoke方法,这地方也就是ProxyFactory开头提的增强器调用invoke操作的调用触发点

来看看@Before注解对应的增强器是如何操作的

@Override
public Object invoke(MethodInvocation mi) throws Throwable {
    this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
    return mi.proceed();
}

和我们设想的一样先执行了增强器的方法,然后循环调用MethodInvocation的proceed的方法,那同理肯定可以猜到@After操作肯定是先执行proceed方法,然后调用相关的增强方法。

到这里整个的AOP过程就算完成了,但是上面还留有一个疑问TargetSource是干什么用的?

上面已经提到targetsource只是包装了一下具体的被代理类,被包装成SingletonTargetSource类,每次获取实际的被代理对象都是通过targetsource.getTarget方法获取的。那我们就可以自定义targetsource改写其中的getTarget()方法,从而实现动态控制被代理对象实际对象了。其实热部署也是采用类似的原理实现的,关于热部署的更多代码可以看看官方提供的HotSwappableTargetSourceTests 单元测试代码。

ProxyFactoryBean

了解完ProxyFactory的整个过程,就很容易理解ProxyFactoryBean了,需知道ProxyFactoryBean = Proxy + FactoryBean,是一种特殊的工厂bean,如下图是其UML类图

Spring的AOP实现原理

通过FactoryBean很自然的想到起代理类是通过getObject方法完成

public Object getObject() throws BeansException {
    initializeAdvisorChain();
    // 初始化增强器链,完成advisor
    if (isSingleton()) {
           // 依旧需要考虑是否为单例bean
        return getSingletonInstance();
    }
    else {
        if (this.targetName == null) {
            logger.info("Using non-singleton proxies with singleton targets is often undesirable. " +
                    "Enable prototype proxies by setting the 'targetName' property.");
        }
        return newPrototypeInstance();
    }
}
    
private synchronized Object getSingletonInstance() {
    if (this.singletonInstance == null) {
        this.targetSource = freshTargetSource();
        if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {
            Class<?> targetClass = getTargetClass();
            if (targetClass == null) {
                throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy");
            }
            setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));
        }
        super.setFrozen(this.freezeProxy);
        this.singletonInstance = getProxy(createAopProxy());
    }
    return this.singletonInstance;
}

protected Object getProxy(AopProxy aopProxy) {
    return aopProxy.getProxy(this.proxyClassLoader);
}

需要关注的是this.singletonInstance = getProxy(createAopProxy());aopProxy.getProxy(this.proxyClassLoader);方法,对比发现其和FactoryBean所生成代理对象的方式是一模一样的,先生成AopProxy,再调用AopProxy.getProxy方法。只是其中寻找增强器和切点的逻辑存在差异。

作者:jwfy
链接:https://www.jianshu.com/p/22cf46235d75
来源:简书

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/192224.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • Linux C中的open函数「建议收藏」

    Linux C中的open函数「建议收藏」open函数属于Linux中系统IO,用于“打开”文件,代码打开一个文件意味着获得了这个文件的访问句柄。intfd=open(参数1,参数2,参数3);intfd=open(constchar*pathname,intflags,mode_tmode);1.句柄(filedescriptor简称fd)首先每个文件都属于自己的句柄,例如标准输入是0,标准输出是…

  • SQL之视图与索引[通俗易懂]

    SQL之视图与索引[通俗易懂]SQL之视图与索引视图的定义、修改、使用索引的创建、查看视图人们在使用数据库时,并不是直接对数据源表进行操作,通常人们只关心源表的部分数据,因此为了使得用户在查询时方便,用不着在每次查询时都编写复杂的代码(比如连接等),可以事先将用户要使用的查询结果通过视图定义在数据库中,这样人们在进行查询时只需查看视图即可,简化了用户的操作,同时使得数据同源数据分离,提高了安全性。1.视图的创建语法:

  • Navicat连接SQL Server2000提示错误08001

    Navicat连接SQL Server2000提示错误08001数据库是SQLServer2000问题描述无论是本机的数据库还是局域网内的,都出现如图的提示使用系统自带的“SQL查询分析器”则可以访问!百度了一圈,给出的都是sql2005的解决办法请问2000该如何解决啊,先谢过~http://bbs.csdn.net/topics/390715240?page=1解决方法运行Navicat安装目录下的sqlncli.msi,选择修复(R

  • 首个可用于深度学习的ToF相关数据集!基于置信度的立体相机以及ToF相机深度图融合框架…

    首个可用于深度学习的ToF相关数据集!基于置信度的立体相机以及ToF相机深度图融合框架…点击上方“计算机视觉工坊”,选择“星标”干货第一时间送达作者|cocoon编辑|3D视觉开发者社区目录✦contents1.概述2.方法以及网络结构2.1使用网络学习置信度2.1.1训练细节2.2双目以及ToF视差的fusion3.合成数据4.实验结果4.1测试集场景4.2置信度估计结果4.3视差估计定性以及定量结果5.参考文献附录:数据…

  • jvm可达性分析算法_对点网络

    jvm可达性分析算法_对点网络作者:张华发表于:2016-04-05版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本版权声明(http://blog.csdn.net/quqi99)IP层叫分片,TCP/UDP层叫分段。网卡能做的事(TCP/UDP组包校验和分段,IP添加包头校验与分片)尽量往网卡做,网卡不能做的也尽量迟后分片(发送)或提前合并片(接收)来减少在网络栈中传输和处理的包数目,

    2022年10月31日
  • android官方原生主题,原生Android可以更换系统主题吗?

    android官方原生主题,原生Android可以更换系统主题吗?现在越来越多的智能手机用户喜欢使用原生的Android系统,因为原生Android系统更加纯净流畅,没有乱七八糟的第三方厂商软件预装,安全性更高,最重要的是原生Android可以最快获得系统更新。也有人喜欢用第三方定制的安卓系统,是因为它的可玩性更高,而其中可更换主题的功能更是受一众玩家欢迎。不过对于原生Android系统的用户来说,想要平时随随便便更换主题可不是件容易的事。原生Android用户…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号