不一样的视角来学习Spring源码之AOP---下

Posted 大忽悠爱忽悠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了不一样的视角来学习Spring源码之AOP---下相关的知识,希望对你有一定的参考价值。

不一样的视角来学习Spring源码之AOP---下


系列文章:

不一样的视角来学习Spring源码之容器与Bean—上

不一样的视角来学习Spring源码之容器与Bean—下

不一样的视角来学习Spring源码之AOP—上

不一样的视角来学习Spring源码之AOP—中


jdk 和 cglib 在 Spring 中的统一

Spring 中对切点、通知、切面的抽象如下

  • 切点:接口 Pointcut,典型实现 AspectJExpressionPointcut
  • 通知:典型接口为 MethodInterceptor 代表环绕通知
  • 切面:Advisor,包含一个 Advice 通知,PointcutAdvisor 包含一个 Advice 通知和一个 Pointcut


代理相关类图

  • AopProxyFactory 根据 proxyTargetClass 等设置选择 AopProxy 实现
  • AopProxy 通过 getProxy 创建代理对象
  • 图中 Proxy 都实现了 Advised 接口,能够获得关联的切面集合与目标(其实是从 ProxyFactory 取得)
  • 调用代理方法时,会借助 ProxyFactory 将通知统一转为环绕通知:MethodInterceptor


底层切点、通知、切面使用演示

public class A15 
    public static void main(String[] args) 
        /*
            两个切面概念
            aspect =
                通知1(advice) +  切点1(pointcut)
                通知2(advice) +  切点2(pointcut)
                通知3(advice) +  切点3(pointcut)
                ...
            advisor = 更细粒度的切面,包含一个通知和切点
         */

        // 1. 备好切点
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* foo())");
        // 2. 备好通知
        MethodInterceptor advice = invocation -> 
            System.out.println("before...");
            Object result = invocation.proceed(); // 调用目标
            System.out.println("after...");
            return result;
        ;
        // 3. 备好切面----只包含一个pointcut和advice
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice);

        /*
           4. 创建代理
                a. proxyTargetClass = false, 目标实现了接口, 用 jdk 实现
                b. proxyTargetClass = false,  目标没有实现接口, 用 cglib 实现
                c. proxyTargetClass = true, 总是使用 cglib 实现
         */
        Target2 target = new Target2();
        ProxyFactory factory = new ProxyFactory();
        factory.setTarget(target);
        factory.addAdvisor(advisor);
        factory.setInterfaces(target.getClass().getInterfaces());
        factory.setProxyTargetClass(false);
        Target2 proxy = (Target2) factory.getProxy();
        System.out.println(proxy.getClass());
        proxy.foo();
        proxy.bar();
        /*
            学到了什么
                a. Spring 的代理选择规则
                b. 底层的切点实现
                c. 底层的通知实现
                d. ProxyFactory 是用来创建代理的核心实现, 用 AopProxyFactory 选择具体代理实现
                    - JdkDynamicAopProxy
                    - ObjenesisCglibAopProxy
         */
    

    interface I1 
        void foo();

        void bar();
    

    static class Target1 implements I1 
        public void foo() 
            System.out.println("target1 foo");
        

        public void bar() 
            System.out.println("target1 bar");
        
    

    static class Target2 
        public void foo() 
            System.out.println("target2 foo");
        

        public void bar() 
            System.out.println("target2 bar");
        
    



收获💡

  1. 底层的切点实现
  2. 底层的通知实现
  3. 底层的切面实现
  4. ProxyFactory 用来创建代理
    • 如果指定了接口,且 proxyTargetClass = false,使用 JdkDynamicAopProxy
    • 如果没有指定接口,或者 proxyTargetClass = true,使用 ObjenesisCglibAopProxy
      • 例外:如果目标是接口类型或已经是 Jdk 代理,使用 JdkDynamicAopProxy

注意

  • 要区分本章节提到的 MethodInterceptor,它与之前 cglib 中用的的 MethodInterceptor 是不同的接口

切点匹配

切点匹配演示

public class A16 
    public static void main(String[] args) throws NoSuchMethodException 
        System.out.println(">>>>>>>>>>>>>>>>>>>>通过execution以具体方法为切入点>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");

        AspectJExpressionPointcut pt1 = new AspectJExpressionPointcut();
        pt1.setExpression("execution(* bar())");
        System.out.println(pt1.matches(T1.class.getMethod("foo"), T1.class));
        System.out.println(pt1.matches(T1.class.getMethod("bar"), T1.class));

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");

        System.out.println(">>>>>>>>>>>>>>>>>>>>通过annotation以方法上是否标注指定注解作为为切入点的依据>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        AspectJExpressionPointcut pt2 = new AspectJExpressionPointcut();
        pt2.setExpression("@annotation(org.springframework.transaction.annotation.Transactional)");
        System.out.println(pt2.matches(T1.class.getMethod("foo"), T1.class));
        System.out.println(pt2.matches(T1.class.getMethod("bar"), T1.class));

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");


        System.out.println(">>>>>>>>>>>>>>>>>>>>>>方法和类上标注了指定注解的类都会被切入>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        StaticMethodMatcherPointcut pt3 = new StaticMethodMatcherPointcut() 
            @Override
            public boolean matches(Method method, Class<?> targetClass) 
                // 检查方法上是否加了 Transactional 注解
                MergedAnnotations annotations = MergedAnnotations.from(method);
                if (annotations.isPresent(Transactional.class)) 
                    return true;
                
                // 查看类上是否加了 Transactional 注解----到继承树上去寻找---本类---父类--接口上有也算
                annotations = MergedAnnotations.from(targetClass, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY);
                if (annotations.isPresent(Transactional.class)) 
                    return true;
                
                return false;
            
        ;

        System.out.println(pt3.matches(T1.class.getMethod("foo"), T1.class));
        System.out.println(pt3.matches(T1.class.getMethod("bar"), T1.class));
        System.out.println(pt3.matches(T2.class.getMethod("foo"), T2.class));
        System.out.println(pt3.matches(T3.class.getMethod("foo"), T3.class));

        /*
            学到了什么
                a. 底层切点实现是如何匹配的: 调用了 aspectj 的匹配方法
                b. 比较关键的是它实现了 MethodMatcher 接口, 用来执行方法的匹配
         */
    


    static class T1 
        @Transactional
        public void foo() 
        
        public void bar() 
        
    

    @Transactional
    static class T2 
        public void foo() 
        
    

    @Transactional
    interface I3 
        void foo();
    
    static class T3 implements I3 
        public void foo() 
        
    



收获💡

  1. 常见 aspectj 切点用法
  2. aspectj 切点的局限性,实际的 @Transactional 切点实现

从 @Aspect 到 Advisor

/**
 * @author 大忽悠
 * @create 2022/3/30 9:17
 */
public class A17 
    public static void main(String[] args) 
        //不会自动注册相关后置处理器的干净的容器
        GenericApplicationContext applicationContext=new GenericApplicationContext();
        applicationContext.registerBean("aspectJ",AspectJ.class);
        applicationContext.registerBean("config",Config.class);
        applicationContext.registerBean(ConfigurationClassPostProcessor.class);
        applicationContext.registerBean(Target.class);

        applicationContext.refresh();

        for (String beanDefinitionName : applicationContext.getBeanDefinitionNames()) 
            System.out.println(beanDefinitionName);
        

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");

        Target bean = applicationContext.getBean(Target.class);
        bean.foo();
    

    static class Target
        public void foo()
        
            System.out.println("foo");
        
    


    @Aspect//高级切面
    static class AspectJ
        @Before("execution(* foo())")
        public void Before()
        
            System.out.println("aspect1 before");
        

        @After("execution(* foo())")
        public void after()
        
            System.out.println("aspect1 after");
        
    

    @Configuration
    static class Config
        @Bean//低级切面
        public Advisor advisor(MethodInterceptor advice)
        
            AspectJExpressionPointcut pointcut=new AspectJExpressionPointcut();
            pointcut.setExpression("execution(* foo())");
            return new DefaultPointcutAdvisor(pointcut,advice);
        

        @Bean
        public MethodInterceptor advice()
        
              return new MethodInterceptor() 
                  @Override
                  public Object invoke(MethodInvocation methodInvocation) throws Throwable 
                      System.out.println("advice before");
                      Object proceed = methodInvocation.proceed();
                      System.out.println("advice after");
                      return proceed;
                  
                           ;
        
    



可以看到,我们注册了一个高级切面类AspectJ和低级切面Advisor到容器中,但是此时运行,发现目标对象方法没有被代理,说明光有切面没用,还少了点啥子,让切面能够运作起来


代理创建器

少了神魔呢?—》少了下面这个自动代理的后置处理器

        //自动代理
        applicationContext.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);

该后置处理器: 创建bean----> 上面的后置处理器干点事 ---->依赖注入----->初始化—>上面的后置处理器干点事

此时再进行测试:


findEligibleAdvisors—找出符合当前目标对象的所有切面

这里不直接翻源码,而是通过调用该后置处理器中的方法来模拟一下源码中的思路:


这里因为是受保护的方法,所以除了反射调用之外,还可以把我们的测试类所在包名改为上面这个后置处理器包名相同,也可以直接调用

//包名改为这个即可
package org.springframework.aop.framework.autoproxy;

public class A17 
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException 
        //不会自动注册相关后置处理器的干净的容器
        GenericApplicationContext applicationContext=new GenericApplicationContext();
        applicationContext.registerBean("aspectJ",AspectJ.class);
        applicationContext.registerBean("config",Config.class);
        applicationContext.registerBean(ConfigurationClassPostProcessor.class);
        applicationContext.registerBean(Target.class);
        //自动代理
        applicationContext.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);
        applicationContext.refresh();

        for (String beanDefinitionName : applicationContext.getBeanDefinitionNames()) 
            System.out.println(beanDefinitionName);
        

        System.out.println("- - - - - - - - - - - - - - - - - - - - - - - - - ");
        AnnotationAwareAspectJAutoProxyCreator annotationAwareAspectJAutoProxyCreator = applicationContext.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
        List<Advisor> advisors = annotationAwareAspectJAutoProxyCreator.findEligibleAdvisors(Target.class, "target");
        System.out.println("符合当前传入目标对象的低级切面有:");
        advisors.forEach(advisor -> System.out.println(advisor));
    

    static class Target
        public void foo()
        
            System.out.println("foo");
        
    


    @Aspect//高级切面
    static class AspectJ
        @Before("execution(* foo())")
        public void Before()
        
            System.out.println("aspect1 before");
        

        @After("execution(* foo())")
        public void after()
        
            System.out.println("aspect1 after");
        
    

    @Configuration
    static class Config
        @Bean//低级切面
        public Advisor advisor(MethodInterceptor advice)
        
            AspectJExpressionPointcut pointcut=new AspectJExpressionPointcut();
            pointcut.setExpression("execution(* foo())");
            return new DefaultPointcutAdvisor(pointcut,advice);
        

        @Bean
        public MethodInterceptor advice()
        
              return new MethodInterceptor() 
                  @Override
                  public Object invoke(MethodInvocation methodInvocation) throws Throwable 
                      System.out.println("advice before");
                      Object proceed = methodInvocation.proceed(不一样的视角来学习Spring源码之容器与Bean---上

spring源码之AOP下篇

Spring 源码学习

Spring读源码系列之AOP--03---aop底层基础类学习

Spring读源码系列之AOP--05---aop常用工具类学习

Spring源码高级笔记之——Spring AOP应用