spring-AOP源码浅析

spring-AOP源码浅析

入口

spring aop功能可以使用@EnableAspectJAutoProxy注解开启,这个注解中,通过@Import(AspectJAutoProxyRegistrar.class)导入了AspectJAutoProxyRegistrar这个注册类,该类实现了ImportBeanDefinitionRegistrar接口,这个接口会在ConfigurationClassPostProcessor后置处理器中被调用的(具体位置在:postProcessBeanDefinitionRegistry=> processConfigBeanDefinitions=> reader.loadBeanDefinitions=> loadBeanDefinitionsForConfigurationClass=> loadBeanDefinitionsFromRegistrars,也就是说它也是在beanFactory后置处理器中执行的,可以用于注册bean定义),这里注册了AnnotationAwareAspectJAutoProxyCreator这个类;

自动代理创建器

AnnotationAwareAspectJAutoProxyCreator

类图

请添加图片描述

可以看到这个类实现了SmartInstantiationAwareBeanPostProcessor后置处理器接口和Aware注入接口

功能实现

重写postProcessBeforeInstantiation方法:

  • 根据advisedBeans判断是否需要增强,不需要直接跳过,否则继续;
  • 判断是否是aop基础类,是的话标记到advisedBeans,跳出执行,否则继续;判断是否aop有两个方法:
    • isInfrastructureClass:判断是否通知类(Advice)、切入点类(Pointcut)、切面类(Advisor)、aop基础类(AopInfrastructureBean)
    • shouldSkip:调用findCandidateAdvisors方法获取所有候选切面,判断bean名称是否与候选切面相同;
  • 判断是否存在TargetSourceCreator,如果有则尝试调用getTargetSource方法获取TargetSource
  • 获取成功则提前创建代理类,并添加到targetSourcedBeans,否则什么也不做;

重写postProcessAfterInitialization方法:

  • 根据earlyProxyReferences判断是否已经处理(当存在循环依赖时,会在getEarlyBeanReference方法中提前完成增强),是则直接返回,否则调用wrapIfNecessary方法增强对象并返回;

重写getEarlyBeanReference方法:当有循环依赖时,会在实例化完成之前调用这个方法:

  • 将bean缓存到earlyProxyReferences
  • 调用wrapIfNecessary方法提前增强目标对象;

wrapIfNecessary方法执行流程:

  • 判断targetSourcedBeans,即是否已经代理,没有则继续;
  • 根据advisedBeans判断是否需要增强,需要则继续;
  • 判断是否aop基础类,是则标记到advisedBeans,否则继续;
  • 调用getAdvicesAndAdvisorsForBean方法获取通知方法(这个方法又是调用findEligibleAdvisors方法实现的)
    • 查找所有候选切面;
    • 从候选切面中获取满足条件的切面;
    • 如果切面列表不为空,则排序;
  • 如果获取到的通知方法为空,则标记到advisedBeans为false,否则标记为true,然后继续;
  • 调用createProxy方法,先创建ProxyFactory,设置通知方法,然后创建并返回代理对象;

切面获取流程

了解了代理工厂的用法,这里再简单分析下,查找候选切面、查找满足条件的切面以及创建proxyFactory的过程;

查找所有候选切面(org.springframework.aop.aspectj.annotation.BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors):这个方法只会在首次调用时查找和创建切面并缓存到map中,后续调用就直接在map中取,这里主要看第一次调用:

  • 从容器中获取所有bean,遍历所有bean,做后续的处理:
  • 判断:有Aspect注解,并且没有被ajc编译;
  • 将切面类封装到AspectMetadata
  • 判断代理类型是否单例,这里只考虑单例;
  • 创建BeanFactoryAspectInstanceFactory
  • 调用this.advisorFactory.getAdvisors方法创建切面;
    • 获取类型、名称;校验Aspect注解、ajc、切面类型;
    • BeanFactoryAspectInstanceFactory包装到LazySingletonAspectInstanceFactoryDecorator,使得每个切面实例只实例化一次;
    • 获取所有方法(排除掉Pointcut注解修饰的方法,然后按AroundBeforeAfterAfterReturningAfterThrowing注解顺序排序)
    • 根据方法注解创建AspectJExpressionPointcut切入点;
    • 创建InstantiationModelAwarePointcutAdvisorImpl切面,这是PointcutAdvisor的一个子接口,添加了懒加载的功能;
    • 处理DeclareParents注解修饰方法的切面;
    • 将创建的切面缓存到map中;

查找满足条件的切面(org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#findAdvisorsThatCanApply):

  • 通过ThreadLocal保存当前正在操作的Bean;
  • 调用AopUtils工具类的findAdvisorsThatCanApply方法判断是否满足增强条件:
  • 先判断IntroductionAdvisor类型的切面,调用matches方法,满足条件的引介切面;
  • 再判断PointcutAdvisor:获取ClassFilter,调用matches方法判断是否满足条件,如果满足则继续判断;
  • 获取MethodMatcher,遍历所有class和实现了的接口,调用matches方法判断是否满足条件;
  • 最后移除ThreadLocal中保存的变量;

如果找到的满足条件的切面不为空,spring默认会在切面列表的第一个位置插入一个增强为ExposeInvocationInterceptorDefaultPointcutAdvisor切面,

ProxyFactory

在上面的分析中,可以知道AOP最终是要获取通知,然后通过ProxyFactory这个来创建增强对象的(这个类最终还是调用的jdk或者cglib的方法来创建的代理类,两个实现类分别对应JdkDynamicAopProxy
CglibAopProxy,需要的时候可以借鉴下里面的代码,这里不细讨论),这里先了解下代理工厂结合通知、切面的简单用法;

通知

Advice接口标记的接口,它有下面这些子接口:

  • MethodBeforeAdvice:前置通知;
  • ThrowsAdvice:异常通知;
  • AfterReturningAdvice:返回通知;
  • MethodInterceptor:可以简单理解为环绕通知;

用法示例:

package com.demo.core.spring;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.ThrowsAdvice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.util.ClassUtils;

import java.lang.reflect.Method;

class AdviceTest {

    @Test
    void test() {
        ProxyFactory proxyFactory = new ProxyFactory(new A());
        // 前置通知
        proxyFactory.addAdvice(new MethodBeforeAdvice() {
            @Override
            public void before(Method method, Object[] args, Object target) throws Throwable {
                System.out.println("======> before ....");
            }
        });
        // 异常通知
        proxyFactory.addAdvice(new CustomThrowAdvice());
        // 返回通知
        proxyFactory.addAdvice(new AfterReturningAdvice() {
            @Override
            public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
                System.out.println("======> after returning ...");

            }
        });
        // 可以理解为环绕通知
        proxyFactory.addAdvice(new MethodInterceptor() {
            @Override
            public Object invoke(MethodInvocation invocation) throws Throwable {
                System.out.println("======> method interceptor ...");
                return invocation.proceed();
            }
        });
        A proxy = (A) proxyFactory.getProxy();
        proxy.doSth("参数....");
        System.out.println("\n==================下面这个方法会抛异常================\n");
        try {
            proxy.doSth(null);
        } catch (Exception e) {
            Assertions.assertTrue(ClassUtils.isAssignable(RuntimeException.class, e.getClass()));
            System.out.println(e.getMessage());
        }
    }

    /**
     * 异常通知方法是通过反射调用的,不能定义为匿名内部类
     */
    public static class CustomThrowAdvice implements ThrowsAdvice {
        public void afterThrowing(Method method, Object[] args, Object target, Exception ex) {
            System.out.println("======> after throw ... ");
        }
    }

    public static class A {
        public String doSth(String param) {
            System.out.println("===> do sth: " + param);
            if (param == null) {
                throw new RuntimeException("随便抛个异常");
            }
            return "返回值...";
        }
    }

}

切面

Advisor接口标记的接口,由切入点加通知组成,它有下面这些子接口:

  • IntroductionAdvisor:引介切面,通过matches方法判断是否需要增强类,可以做到直接代理接口,不需要实现类;
  • PointcutAdvisor:切入点切面,根据指定规则判断是否需要增强类,具体的规则一般有:
    • NameMatchMethodPointcut:通过方法名精确匹配;
    • ControlFlowPointcut:根据当前线程的堆栈信息确定是否切入(效率低);
    • ComposablePointcut:组合模式;
    • JdkRegexpMethodPointcut:正则匹配;
    • AspectJExpressionPointcut:切点表达式匹配;

用法示例:

package com.demo.core.spring;

import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.jupiter.api.Test;
import org.springframework.aop.*;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.stream.Collectors;

class AdvisorTest {

    @Test
    void introduction() {
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.addAdvisor(new CustomIntroductionAdvisor(new IntroductionInterceptor() {
            @Override
            public Object invoke(MethodInvocation invocation) throws Throwable {
                System.out.println("======> introduction interceptor ...... ");
                if (invocation.getThis() == null) {
                    Method method = invocation.getMethod();
                    return String.format("%s#%s %s(%s)", method.getDeclaringClass().getSimpleName(),
                            method.getReturnType().getSimpleName(), method.getName(),
                            StringUtils.collectionToDelimitedString(
                                    Arrays.stream(invocation.getArguments()).collect(Collectors.toList()),
                                    ",", "\"", "\""));
                }
                return invocation.proceed();
            }

            @Override
            public boolean implementsInterface(Class<?> intf) {
                return false;
            }
        }));

        I1 i1 = (I1) proxyFactory.getProxy();
        System.out.println("=========> " + i1.doSth("i1 param ...."));
        I2 i2 = (I2) proxyFactory.getProxy();
        // 随手一写发现个问题,这里的代理配置相当于:创建代理类同时实现I1、I2两个接口,这两个接口刚好有相同签名的方法;
        // 那么这里默认只实现第一个接口的同签名方法,所以调用i2.doSth方法时,打印的是i1的信息;
        // 如果这两个方法只有返回值不一样,则会报错,这一点和Java语法是一致的;
        System.out.println("=========> " + i2.doSth("i2 param ...."));
        System.out.println("=========> " + i2.doSth("i2 param1 ....", "i2 param2 ...."));

    }

    @Test
    void pointcut() {
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.addAdvisor(new CustomPointcutAdvisor(new MethodBeforeAdvice() {
            @Override
            public void before(Method method, Object[] args, Object target) throws Throwable {
                System.out.println("======> pointcut advisor before ....");
            }
        }));

        proxyFactory.setTarget(new A());
        A a = (A) proxyFactory.getProxy();
        System.out.println("=========> " + a.doSth("param a..."));

        System.out.println("===> b");
        proxyFactory.setTarget(new B());
        B b = (B) proxyFactory.getProxy();
        System.out.println("=========> " + b.doSth("param b..."));

    }

    /**
     * 引介切面
     */
    public static class CustomIntroductionAdvisor implements IntroductionAdvisor, ClassFilter {

        private final Advice advice;

        public CustomIntroductionAdvisor(Advice advice) {
            this.advice = advice;
        }

        @Override
        public boolean matches(Class<?> clazz) {
            // 这个方法返回false,则不增强这个class
            return ClassUtils.isAssignable(I1.class, clazz) || ClassUtils.isAssignable(I2.class, clazz);
        }

        @Override
        public ClassFilter getClassFilter() {
            return this;
        }

        @Override
        public void validateInterfaces() throws IllegalArgumentException {
        }

        @Override
        public Advice getAdvice() {
            return advice;
        }

        @Override
        public boolean isPerInstance() {
            return true;
        }

        @Override
        public Class<?>[] getInterfaces() {
            return new Class[]{I1.class, I2.class};
        }
    }

    /**
     * 切入点切面
     */
    public static class CustomPointcutAdvisor implements PointcutAdvisor {
        private final Advice advice;

        public CustomPointcutAdvisor(Advice advice) {
            this.advice = advice;
        }

        @Override
        public Pointcut getPointcut() {
            AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
            pointcut.setExpression("execution(public * com.demo.core.spring.AdvisorTest$B.*(..))");
            return pointcut;
        }

        @Override
        public Advice getAdvice() {
            return this.advice;
        }

        @Override
        public boolean isPerInstance() {
            return false;
        }
    }

    public interface I1 {
        String doSth(String param);
    }

    public interface I2 {
        String doSth(String param);

        String doSth(String param1, String param2);
    }

    public static class A {
        public String doSth(String param) {
            System.out.println("=========> do sth: " + param);
            return "a...";
        }
    }

    public static class B {
        public String doSth(String param) {
            System.out.println("=========> do sth: " + param);
            return "b...";
        }
    }

}

执行过程

ProxyFactory的执行过程要分情况,当frozentrue时,走FixedChainStaticTargetInterceptor,为false时,走DynamicAdvisedInterceptor,这两个类都实现了org.springframework.cglib.proxy.MethodInterceptor接口,这里以false情况为例做简单分析;

DynamicAdvisedInterceptor:是CglibAopProxy的私有静态内部类,其AdvisedSupport成员变量保存切面相关信息;还实现了MethodInterceptor接口的intercept方法,这个方法是执行的入口,其执行大致流程如下:

  • 获取目标对象和class类型;
  • 获取执行链(就是排序好的包装了通知方法的和目标方法的MethodInterceptor的子接口的list集合);
  • 创建CglibMethodInvocation(这里会判断:是非Object派生方法的共有方法),然后执行proceed方法;
  • proceed方法会被递归调用,直到执行链执行结束(这里并不是按执行链依次调用,而是在执行每个执行链节点方法中的适当位置调用proceed方法,这个方法中会根据currentInterceptorIndex的值从执行链list中去对应的执行节点后自增,直到超过list大小,调用invokeJoinpoint方法,具体调用顺序为:before -> throws -> after return -> method interceptor -> invokeJoinpoint -> 目标方法);
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值