Spring原理-7.切点与切面

  • 👏作者简介:大家好,我是爱吃芝士的土豆倪,24届校招生Java选手,很高兴认识大家
  • 📕系列专栏:Spring原理、JUC原理、Kafka原理、分布式技术原理、数据库技术
  • 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
  • 🍂博主正在努力完成2023计划中:源码溯源,一探究竟
  • 📝联系方式:nhs19990716,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬👀

切点匹配

根据名字进行匹配

java 复制代码
static class T1 {
        @Transactional
        public void foo() {
        }
        public void bar() {
        }
    }
java 复制代码
        AspectJExpressionPointcut pt1 = new AspectJExpressionPointcut();
        pt1.setExpression("execution(* bar())");
        System.out.println(pt1.matches(T1.class.getMethod("foo"), T1.class));// false
        System.out.println(pt1.matches(T1.class.getMethod("bar"), T1.class));// true

根据注解进行匹配

java 复制代码
AspectJExpressionPointcut pt2 = new AspectJExpressionPointcut();
        pt2.setExpression("@annotation(org.springframework.transaction.annotation.Transactional)");
        System.out.println(pt2.matches(T1.class.getMethod("foo"), T1.class));// true
        System.out.println(pt2.matches(T1.class.getMethod("bar"), T1.class));// false

@Transactional注解

前面提到的,无论是execution 还是 annotation只能去匹配方法上的,都不能解决如特定情况

java 复制代码
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() {
        }
    }

因此需要换一个别的实现

java 复制代码
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));

总结

底层切点实现是如何匹配的: 调用了 aspectj 的匹配方法

如果aspectj不能满足的话,那么就使用MethodMatcher 接口, 用来执行方法的匹配,通过这个我们完全可以自行的去定义匹配规则。

从 @Aspect 到 Advisor

java 复制代码
static class Target1 {
        public void foo() {
            System.out.println("target1 foo");
        }
    }

    static class Target2 {
        public void bar() {
            System.out.println("target2 bar");
        }
    }

高级切面类@Aspect

java 复制代码
@Aspect // 高级切面类
    @Order(1)
    static class Aspect1 {
        @Before("execution(* foo())")
        public void before1() {
            System.out.println("aspect1 before1...");
        }

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

低级切面类

java 复制代码
@Configuration
    static class Config {
        @Bean // 低级切面
        public Advisor advisor3(MethodInterceptor advice3) {
            AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
            pointcut.setExpression("execution(* foo())");
            DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice3);
            return advisor;
        }
        @Bean
        public MethodInterceptor advice3() {
            return invocation -> {
                System.out.println("advice3 before...");
                Object result = invocation.proceed();
                System.out.println("advice3 after...");
                return result;
            };
        }
    }

注册bean

java 复制代码
GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("aspect1", Aspect1.class);
        context.registerBean("config", Config.class);
        context.registerBean(ConfigurationClassPostProcessor.class);
        context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);

 context.refresh();

其中AnnotationAwareAspectJAutoProxyCreator有两个比较重要的方法

css 复制代码
第一个重要方法 findEligibleAdvisors 找到有【资格】的 Advisors
a. 有【资格】的 Advisor 一部分是低级的, 可以由自己编写, 如下例中的 advisor3
b. 有【资格】的 Advisor 另一部分是高级的, 由本章的主角解析 @Aspect 后获得
java 复制代码
AnnotationAwareAspectJAutoProxyCreator creator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
        List<Advisor> advisors = creator.findEligibleAdvisors(Target1.class, "target1");
        for (Advisor advisor : advisors) {
            System.out.println(advisor);
        }

返回三个

css 复制代码
InstantiationModelAwarePointcutAdvisor: expression [execution(* foo())]; advice method [public void org.springframework.aop.framework.autoproxy.A17$Aspect1.before1()];
perClauseKind=SINGLETON

InstantiationModelAwarePointcutAdvisor: expression [execution(* foo())]; advice method [public void org.springframework.aop.framework.autoproxy.A17$Aspect1.before2()];
perClauseKind=SINGLETON

org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.framework.autoproxy.A17$Config$$Lambda$117/0x00000008001ff630@5c530d1e]

其实也就是将高级切面转换成低级切面

css 复制代码
第二个重要方法 wrapIfNecessary
a. 它内部调用 findEligibleAdvisors, 只要返回集合不空, 则表示需要创建代理
java 复制代码
        Object o1 = creator.wrapIfNecessary(new Target1(), "target1", "target1");
        System.out.println(o1.getClass());
        Object o2 = creator.wrapIfNecessary(new Target2(), "target2", "target2");
        System.out.println(o2.getClass());

((Target1) o1).foo();

当最后执行的时候,就能看到增强的效果

代理创建时机

java 复制代码
 GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean(ConfigurationClassPostProcessor.class);
        context.registerBean(Config.class);
        context.refresh();
        context.close();
java 复制代码
@Configuration
    static class Config {
        @Bean // 解析 @Aspect、产生代理
        public AnnotationAwareAspectJAutoProxyCreator annotationAwareAspectJAutoProxyCreator() {
            return new AnnotationAwareAspectJAutoProxyCreator();
        }

        @Bean // 解析 @Autowired
        public AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor() {
            return new AutowiredAnnotationBeanPostProcessor();
        }

        @Bean // 解析 @PostConstruct
        public CommonAnnotationBeanPostProcessor commonAnnotationBeanPostProcessor() {
            return new CommonAnnotationBeanPostProcessor();
        }

        @Bean
        public Advisor advisor(MethodInterceptor advice) {
            AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
            pointcut.setExpression("execution(* foo())");
            return new DefaultPointcutAdvisor(pointcut, advice);
        }

        @Bean
        public MethodInterceptor advice() {
            return (MethodInvocation invocation) -> {
                System.out.println("before...");
                return invocation.proceed();
            };
        }

        @Bean
        public Bean1 bean1() {
            return new Bean1();
        }

        @Bean
        public Bean2 bean2() {
            return new Bean2();
        }
    }

    static class Bean1 {
        public void foo() {

        }
        public Bean1() {
            System.out.println("Bean1()");
        }
        @PostConstruct public void init() {
            System.out.println("Bean1 init()");
        }
    }

    static class Bean2 {
        public Bean2() {
            System.out.println("Bean2()");
        }
        @Autowired public void setBean1(Bean1 bean1) {
            System.out.println("Bean2 setBean1(bean1) class is: " + bean1.getClass());
        }
        @PostConstruct public void init() {
            System.out.println("Bean2 init()");
        }
    }

当无循环依赖的时候,结果如下:

scss 复制代码
Bean1()
Bean1 init()
[TRACE] 20:58:25.379 [main] o.s.a.a.a.AnnotationAwareAspectJAutoProxyCreator - Creating implicit proxy for bean 'bean1' with 0 common interceptors and 2 specific interceptors 
Bean2()
Bean2 setBean1(bean1) class is: class org.springframework.aop.framework.autoproxy.A17_1$Bean1$$EnhancerBySpringCGLIB$$393b91a3
Bean2 init()

bean1是在初始化完成后才进行创建代理的,而bean2直接使用bean1的代理类

但是当存在循环依赖的时候

java 复制代码
static class Bean1 {
        public void foo() {

        }
        public Bean1() {
            System.out.println("Bean1()");
        }
        @Autowired public void setBean2(Bean2 bean2) {
            System.out.println("Bean1 setBean2(bean2) class is: " + bean2.getClass());
        }
        @PostConstruct public void init() {
            System.out.println("Bean1 init()");
        }
    }

    static class Bean2 {
        public Bean2() {
            System.out.println("Bean2()");
        }
        @Autowired public void setBean1(Bean1 bean1) {
            System.out.println("Bean2 setBean1(bean1) class is: " + bean1.getClass());
        }
        @PostConstruct public void init() {
            System.out.println("Bean2 init()");
        }
    }

对应的结果如下:

csharp 复制代码
Bean1()

Bean2()

[TRACE] 21:00:32.516 [main] o.s.a.a.a.AnnotationAwareAspectJAutoProxyCreator - Creating implicit proxy for bean 'bean1' with 0 common interceptors and 2 specific interceptors 

Bean2 setBean1(bean1) class is: class org.springframework.aop.framework.autoproxy.A17_1$Bean1$$EnhancerBySpringCGLIB$$b7a50cf3

Bean2 init()

Bean1 setBean2(bean2) class is: class org.springframework.aop.framework.autoproxy.A17_1$Bean2

Bean1 init()

首先也是调用了bean1的构造,接下来按照bean1的流程来讲需要setbean2,但是此时没有,按照bean1的流程来讲,需要暂停进入bean2的流程。在bean2这个流程中,但是问题来了,bean2的依赖注入需要bean1,当注入了一个代理的bean1时,因此bean1的代理就会在bean2的依赖注入之前就被创建出来。因此它的代理对象提前被创建出来了。

总结

代理的创建时间有两个

一个是无循环依赖,那么就是 创建 -> 依赖注入 -> 初始化 这三个流程中的初始化之后创建代理。

如果有循环依赖,那么就是 创建 -> 依赖注入 -> 初始化 这三个流程中 依赖注入之前创建代理,并且将其存入到二级缓存当中。

循环依赖

我们都知道,单例Bean初始化完成,要经历三步:

注入就发生在第二步,属性赋值,结合这个过程,Spring 通过三级缓存解决了循环依赖:

  1. 一级缓存 : Map singletonObjects ,单例池,用于保存实例化、属性赋值(注入)、初始化完成的 bean 实例
  2. 二级缓存 : Map earlySingletonObjects ,早期曝光对象,用于保存实例化完成的 bean 实例
  3. 三级缓存 : Map> singletonFactories ,早期曝光对象工厂,用于保存 bean 创建工厂,以便于后面扩展有机会创建代理对象。

我们来看一下三级缓存解决循环依赖的过程:

当 A、B 两个类发生循环依赖时:

A实例的初始化过程:

创建A实例,实例化的时候把A对象⼯⼚放⼊三级缓存,表示A开始实例化了,虽然我这个对象还不完整,但是先曝光出来让大家知道

A注⼊属性时,发现依赖B,此时B还没有被创建出来,所以去实例化B

同样,B注⼊属性时发现依赖A,它就会从缓存里找A对象。依次从⼀级到三级缓存查询A,从三级缓存通过对象⼯⼚拿到A,发现A虽然不太完善,但是存在,把A放⼊⼆级缓存,同时删除三级缓存中的A,此时,B已经实例化并且初始化完成,把B放入⼀级缓存。

接着A继续属性赋值,顺利从⼀级缓存拿到实例化且初始化完成的B对象,A对象创建也完成,删除⼆级缓存中的A,同时把A放⼊⼀级缓存

最后,⼀级缓存中保存着实例化、初始化都完成的A、B对象

所以,我们就知道为什么Spring能解决setter注入的循环依赖了,因为实例化和属性赋值是分开的,所以里面有操作的空间。如果都是构造器注入的话,那么都得在实例化这一步完成注入,所以自然是无法支持了。

为什么要三级缓存?⼆级不⾏吗?

不行,主要是为了⽣成代理对象。如果是没有代理的情况下,使用二级缓存解决循环依赖也是OK的。但是如果存在代理,三级没有问题,二级就不行了。

因为三级缓存中放的是⽣成具体对象的匿名内部类,获取Object的时候,它可以⽣成代理对象,也可以返回普通对象。使⽤三级缓存主要是为了保证不管什么时候使⽤的都是⼀个对象。

假设只有⼆级缓存的情况,往⼆级缓存中放的显示⼀个普通的Bean对象,Bean初始化过程中,通过 BeanPostProcessor 去⽣成代理对象之后,覆盖掉⼆级缓存中的普通Bean对象,那么可能就导致取到的Bean对象不一致了。

切面加载顺序

还是拿前面的例子举例

java 复制代码
static class Target1 {
        public void foo() {
            System.out.println("target1 foo");
        }
    }

    static class Target2 {
        public void bar() {
            System.out.println("target2 bar");
        }
    }

    @Aspect // 高级切面类
    @Order(1)
    static class Aspect1 {
        @Before("execution(* foo())")
        public void before1() {
            System.out.println("aspect1 before1...");
        }

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

    @Configuration
    static class Config {
        @Bean // 低级切面
        public Advisor advisor3(MethodInterceptor advice3) {
            AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
            pointcut.setExpression("execution(* foo())");
            DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice3);
            return advisor;
        }
        @Bean
        public MethodInterceptor advice3() {
            return invocation -> {
                System.out.println("advice3 before...");
                Object result = invocation.proceed();
                System.out.println("advice3 after...");
                return result;
            };
        }
    }

Object o1 = creator.wrapIfNecessary(new Target1(), "target1", "target1");
        System.out.println(o1.getClass());
        Object o2 = creator.wrapIfNecessary(new Target2(), "target2", "target2");
        System.out.println(o2.getClass());

        ((Target1) o1).foo();

结果:

erlang 复制代码
advice3 before...
aspect1 before1...
target1 foo
aspect1 before2...
advice3 after...

可以看到低级切面类先被执行了。

可以通过设置setOrder 或者Order()注解来修改执行顺序

高级切面转换过程

java 复制代码
static class Aspect {
        @Before("execution(* foo())")
        public void before1() {
            System.out.println("before1");
        }

        @Before("execution(* foo())")
        public void before2() {
            System.out.println("before2");
        }

        public void after() {
            System.out.println("after");
        }

        public void afterReturning() {
            System.out.println("afterReturning");
        }

        public void afterThrowing() {
            System.out.println("afterThrowing");
        }

        public Object around(ProceedingJoinPoint pjp) throws Throwable {
            try {
                System.out.println("around...before");
                return pjp.proceed();
            } finally {
                System.out.println("around...after");
            }
        }
    }
    
    static class Target {
        public void foo() {
            System.out.println("target foo");
        }
    }

转换过程

java 复制代码
@SuppressWarnings("all")
    public static void main(String[] args) throws Throwable {

        AspectInstanceFactory factory = new SingletonAspectInstanceFactory(new Aspect());
        // 高级切面转低级切面类
        List<Advisor> list = new ArrayList<>();
        for (Method method : Aspect.class.getDeclaredMethods()) {
            if (method.isAnnotationPresent(Before.class)) {
                // 解析切点
                String expression = method.getAnnotation(Before.class).value();
                AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
                pointcut.setExpression(expression);
                // 通知类
                // 根据注解的不同,选择不同的通知实现
                AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(method, pointcut, factory);
                // 切面
                Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
                list.add(advisor);
            }
        }
        for (Advisor advisor : list) {
            System.out.println(advisor);
        }
        /*
            @Before 前置通知会被转换为下面原始的 AspectJMethodBeforeAdvice 形式, 该对象包含了如下信息
                a. 通知代码从哪儿来
                b. 切点是什么(这里为啥要切点, 后面解释)
                c. 通知对象如何创建, 本例共用同一个 Aspect 对象
           
           类似的通知还有
                1. AspectJAroundAdvice (环绕通知)
                2. AspectJAfterReturningAdvice
                3. AspectJAfterThrowingAdvice
                4. AspectJAfterAdvice (环绕通知)
         */

    }

最终解析出来的结果

css 复制代码
org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice: advice method [public void org.springframework.aop.framework.autoproxy.A17_2$Aspect.before2()]; aspect name '']

org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice: advice method [public void org.springframework.aop.framework.autoproxy.A17_2$Aspect.before1()]; aspect name '']
相关推荐
feilieren8 小时前
信创改造 - TongRDS 替换 Redis
java·spring boot·后端
hani19908 小时前
beikeshop 与swoole结合,让网站打开飞起
后端·swoole
knoci9 小时前
【Go】-go中的锁机制
后端·学习·golang
Mike_188702783519 小时前
深入探索Golang的GMP调度机制:源码解析与实现原理
开发语言·后端·golang
不7夜宵9 小时前
Golang 反射
开发语言·后端·golang
AskHarries9 小时前
Spring Cloud Consul实现选举机制
java·后端·spring cloud·consul
山山而川粤9 小时前
大连环保公益管理系统|Java|SSM|Vue| 前后端分离
java·开发语言·后端·学习·mysql
尘浮生9 小时前
Java项目实战II基于SpringBoot前后端分离的网吧管理系统(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·后端·微信小程序·小程序
哎呦没10 小时前
企业OA管理系统:Spring Boot技术实现与案例研究
android·spring boot·后端
人才程序员10 小时前
详解Qt QStorageInfo 存储信息类
c语言·开发语言·c++·后端·qt·界面