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

相关文章

JSON字符串转成java的Map对象详细步骤

《JSON字符串转成java的Map对象详细步骤》:本文主要介绍如何将JSON字符串转换为Java对象的步骤,包括定义Element类、使用Jackson库解析JSON和添加依赖,文中通过代码介绍... 目录步骤 1: 定义 Element 类步骤 2: 使用 Jackson 库解析 jsON步骤 3: 添

Java中注解与元数据示例详解

《Java中注解与元数据示例详解》Java注解和元数据是编程中重要的概念,用于描述程序元素的属性和用途,:本文主要介绍Java中注解与元数据的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参... 目录一、引言二、元数据的概念2.1 定义2.2 作用三、Java 注解的基础3.1 注解的定义3.2 内

Java中使用Java Mail实现邮件服务功能示例

《Java中使用JavaMail实现邮件服务功能示例》:本文主要介绍Java中使用JavaMail实现邮件服务功能的相关资料,文章还提供了一个发送邮件的示例代码,包括创建参数类、邮件类和执行结... 目录前言一、历史背景二编程、pom依赖三、API说明(一)Session (会话)(二)Message编程客

Java中List转Map的几种具体实现方式和特点

《Java中List转Map的几种具体实现方式和特点》:本文主要介绍几种常用的List转Map的方式,包括使用for循环遍历、Java8StreamAPI、ApacheCommonsCollect... 目录前言1、使用for循环遍历:2、Java8 Stream API:3、Apache Commons

JavaScript中的isTrusted属性及其应用场景详解

《JavaScript中的isTrusted属性及其应用场景详解》在现代Web开发中,JavaScript是构建交互式应用的核心语言,随着前端技术的不断发展,开发者需要处理越来越多的复杂场景,例如事件... 目录引言一、问题背景二、isTrusted 属性的来源与作用1. isTrusted 的定义2. 为

Java循环创建对象内存溢出的解决方法

《Java循环创建对象内存溢出的解决方法》在Java中,如果在循环中不当地创建大量对象而不及时释放内存,很容易导致内存溢出(OutOfMemoryError),所以本文给大家介绍了Java循环创建对象... 目录问题1. 解决方案2. 示例代码2.1 原始版本(可能导致内存溢出)2.2 修改后的版本问题在

Java CompletableFuture如何实现超时功能

《JavaCompletableFuture如何实现超时功能》:本文主要介绍实现超时功能的基本思路以及CompletableFuture(之后简称CF)是如何通过代码实现超时功能的,需要的... 目录基本思路CompletableFuture 的实现1. 基本实现流程2. 静态条件分析3. 内存泄露 bug

Java中Object类的常用方法小结

《Java中Object类的常用方法小结》JavaObject类是所有类的父类,位于java.lang包中,本文为大家整理了一些Object类的常用方法,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. public boolean equals(Object obj)2. public int ha

SpringBoot项目中Maven剔除无用Jar引用的最佳实践

《SpringBoot项目中Maven剔除无用Jar引用的最佳实践》在SpringBoot项目开发中,Maven是最常用的构建工具之一,通过Maven,我们可以轻松地管理项目所需的依赖,而,... 目录1、引言2、Maven 依赖管理的基础概念2.1 什么是 Maven 依赖2.2 Maven 的依赖传递机

SpringBoot实现动态插拔的AOP的完整案例

《SpringBoot实现动态插拔的AOP的完整案例》在现代软件开发中,面向切面编程(AOP)是一种非常重要的技术,能够有效实现日志记录、安全控制、性能监控等横切关注点的分离,在传统的AOP实现中,切... 目录引言一、AOP 概述1.1 什么是 AOP1.2 AOP 的典型应用场景1.3 为什么需要动态插