Spring原理-7.切点与切面

2024-01-12 11:04
文章标签 java spring 原理 切面 切点

本文主要是介绍Spring原理-7.切点与切面,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

文章目录

  • 切点匹配
    • 根据名字进行匹配
    • 根据注解进行匹配
    • @Transactional注解
    • 总结
  • 从 @Aspect 到 Advisor
    • 注册bean
  • 代理创建时机
    • 总结
    • 循环依赖
      • 为什么要三级缓存?⼆级不⾏吗?
  • 切面加载顺序
  • 高级切面转换过程

切点匹配

根据名字进行匹配

static class T1 {@Transactionalpublic void foo() {}public void bar() {}}
        AspectJExpressionPointcut pt1 = new AspectJExpressionPointcut();pt1.setExpression("execution(* bar())");System.out.println(pt1.matches(T1.class.getMethod("foo"), T1.class));// falseSystem.out.println(pt1.matches(T1.class.getMethod("bar"), T1.class));// true

根据注解进行匹配

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

@Transactional注解

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

static class T1 {@Transactionalpublic void foo() {}public void bar() {}}@Transactionalstatic class T2 {public void foo() {}}@Transactionalinterface I3 {void foo();}static class T3 implements I3 {public void foo() {}}

因此需要换一个别的实现

StaticMethodMatcherPointcut pt3 = new StaticMethodMatcherPointcut() {@Overridepublic 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

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

高级切面类@Aspect

@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...");}}

低级切面类

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

注册bean

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有两个比较重要的方法

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

返回三个

InstantiationModelAwarePointcutAdvisor: expression [execution(* foo())]; advice method [public void org.springframework.aop.framework.autoproxy.A17$Aspect1.before1()];
perClauseKind=SINGLETONInstantiationModelAwarePointcutAdvisor: expression [execution(* foo())]; advice method [public void org.springframework.aop.framework.autoproxy.A17$Aspect1.before2()];
perClauseKind=SINGLETONorg.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.framework.autoproxy.A17$Config$$Lambda$117/0x00000008001ff630@5c530d1e]

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

第二个重要方法 wrapIfNecessary
a. 它内部调用 findEligibleAdvisors, 只要返回集合不空, 则表示需要创建代理
        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();

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

代理创建时机

 GenericApplicationContext context = new GenericApplicationContext();context.registerBean(ConfigurationClassPostProcessor.class);context.registerBean(Config.class);context.refresh();context.close();
@Configurationstatic class Config {@Bean // 解析 @Aspect、产生代理public AnnotationAwareAspectJAutoProxyCreator annotationAwareAspectJAutoProxyCreator() {return new AnnotationAwareAspectJAutoProxyCreator();}@Bean // 解析 @Autowiredpublic AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor() {return new AutowiredAnnotationBeanPostProcessor();}@Bean // 解析 @PostConstructpublic CommonAnnotationBeanPostProcessor commonAnnotationBeanPostProcessor() {return new CommonAnnotationBeanPostProcessor();}@Beanpublic Advisor advisor(MethodInterceptor advice) {AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();pointcut.setExpression("execution(* foo())");return new DefaultPointcutAdvisor(pointcut, advice);}@Beanpublic MethodInterceptor advice() {return (MethodInvocation invocation) -> {System.out.println("before...");return invocation.proceed();};}@Beanpublic Bean1 bean1() {return new Bean1();}@Beanpublic 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()");}}

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

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的代理类

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

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()");}}

对应的结果如下:

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$$b7a50cf3Bean2 init()Bean1 setBean2(bean2) class is: class org.springframework.aop.framework.autoproxy.A17_1$Bean2Bean1 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对象不一致了。

在这里插入图片描述

切面加载顺序

还是拿前面的例子举例

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...");}}@Configurationstatic class Config {@Bean // 低级切面public Advisor advisor3(MethodInterceptor advice3) {AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();pointcut.setExpression("execution(* foo())");DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice3);return advisor;}@Beanpublic 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();

结果:

advice3 before...
aspect1 before1...
target1 foo
aspect1 before2...
advice3 after...

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

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

高级切面转换过程

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");}}

转换过程

@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. AspectJAfterReturningAdvice3. AspectJAfterThrowingAdvice4. AspectJAfterAdvice (环绕通知)*/}

最终解析出来的结果

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 '']

这篇关于Spring原理-7.切点与切面的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/597663

相关文章

Spring Boot整合消息队列RabbitMQ的实现示例

《SpringBoot整合消息队列RabbitMQ的实现示例》本文主要介绍了SpringBoot整合消息队列RabbitMQ的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的... 目录RabbitMQ 简介与安装1. RabbitMQ 简介2. RabbitMQ 安装Spring

springMVC返回Http响应的实现

《springMVC返回Http响应的实现》本文主要介绍了在SpringBoot中使用@Controller、@ResponseBody和@RestController注解进行HTTP响应返回的方法,... 目录一、返回页面二、@Controller和@ResponseBody与RestController

JAVA集成本地部署的DeepSeek的图文教程

《JAVA集成本地部署的DeepSeek的图文教程》本文主要介绍了JAVA集成本地部署的DeepSeek的图文教程,包含配置环境变量及下载DeepSeek-R1模型并启动,具有一定的参考价值,感兴趣的... 目录一、下载部署DeepSeek1.下载ollama2.下载DeepSeek-R1模型并启动 二、J

springboot rocketmq配置生产者和消息者的步骤

《springbootrocketmq配置生产者和消息者的步骤》本文介绍了如何在SpringBoot中集成RocketMQ,包括添加依赖、配置application.yml、创建生产者和消费者,并展... 目录1. 添加依赖2. 配置application.yml3. 创建生产者4. 创建消费者5. 使用在

Spring Retry 实现乐观锁重试实践记录

《SpringRetry实现乐观锁重试实践记录》本文介绍了在秒杀商品SKU表中使用乐观锁和MybatisPlus配置乐观锁的方法,并分析了测试环境和生产环境的隔离级别对乐观锁的影响,通过简单验证,... 目录一、场景分析 二、简单验证 2.1、可重复读 2.2、读已提交 三、最佳实践 3.1、配置重试模板

Spring中@Lazy注解的使用技巧与实例解析

《Spring中@Lazy注解的使用技巧与实例解析》@Lazy注解在Spring框架中用于延迟Bean的初始化,优化应用启动性能,它不仅适用于@Bean和@Component,还可以用于注入点,通过将... 目录一、@Lazy注解的作用(一)延迟Bean的初始化(二)与@Autowired结合使用二、实例解

SpringBoot使用Jasypt对YML文件配置内容加密的方法(数据库密码加密)

《SpringBoot使用Jasypt对YML文件配置内容加密的方法(数据库密码加密)》本文介绍了如何在SpringBoot项目中使用Jasypt对application.yml文件中的敏感信息(如数... 目录SpringBoot使用Jasypt对YML文件配置内容进行加密(例:数据库密码加密)前言一、J

Java中有什么工具可以进行代码反编译详解

《Java中有什么工具可以进行代码反编译详解》:本文主要介绍Java中有什么工具可以进行代码反编译的相关资,料,包括JD-GUI、CFR、Procyon、Fernflower、Javap、Byte... 目录1.JD-GUI2.CFR3.Procyon Decompiler4.Fernflower5.Jav

Spring Boot 中正确地在异步线程中使用 HttpServletRequest的方法

《SpringBoot中正确地在异步线程中使用HttpServletRequest的方法》文章讨论了在SpringBoot中如何在异步线程中正确使用HttpServletRequest的问题,... 目录前言一、问题的来源:为什么异步线程中无法访问 HttpServletRequest?1. 请求上下文与线

在 Spring Boot 中使用异步线程时的 HttpServletRequest 复用问题记录

《在SpringBoot中使用异步线程时的HttpServletRequest复用问题记录》文章讨论了在SpringBoot中使用异步线程时,由于HttpServletRequest复用导致... 目录一、问题描述:异步线程操作导致请求复用时 Cookie 解析失败1. 场景背景2. 问题根源二、问题详细分