Spring AOP Aspect Instantiation Models 切面实例化模型

2024-01-10 10:08

本文主要是介绍Spring AOP Aspect Instantiation Models 切面实例化模型,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

在填【详解什么是Spring AOP】里面的坑的过程中,发现一个小知识点给大家分享一下。默认情况下,在Spring AOP里面的切面类是单例模式(Singleton),也就是说我们声明的Aspect类只能实例化一个对象出来使用。但是假设我们使用的切面类里面有公共对象或者变量需要操作,或者应用于多线程环境,单例模式的切面类显然就不能满足我们的要求了。那么我们应该怎么做才能给每一个切面都创建一个单独的实例呢?在Spring官网上,也是有解决方案的【Aspect Instantiation Models】这一章节就是在介绍这些内容。更多Spring内容进入【Spring解读系列目录】。

Aspect Instantiation Models

这一章节的直接翻译就是切面实例化模型,首先还是看看官网怎么说的,以下引用来自官网:

By default, there is a single instance of each aspect within the application context. AspectJ calls this the singleton instantiation model. It is possible to define aspects with alternate lifecycles. Spring supports AspectJ’s perthis and pertarget instantiation models ( percflow, percflowbelow, and pertypewithin are not currently supported).

大意:默认来说,在程序中每一个切面都是一个单独的实例,AspectJ把这种模式称作单例实例化模型。这就使得切面有不同的生命周期成为一种可能。Spring支持AspectJ的perthispertarget 实例化模型(percflow, percflowbelow, 和 pertypewithin 暂时不支持,潜台词但是未来可能支持。)

但是我们还是要划重点,怎么搞起来这个所谓的实例化模型呢?就是通过perthispertarget 这两个语义去开启这个支持。其实不只是要这两个语义就够,我们知道默认情况下,Spring的bean其实也是单例的,所以我们还要给bean引入“prototype”才能够突破单例模式的限制。官网上例子给的十分的简单,基本没有什么参考价值,所以我们自己写一个例子做演示。

perthis & pertarget

看这两个语义有没有熟悉的感觉?我们在@Pointcut中分别有thistarget,这两个的功能和语法也是类似的,都是在自己的语义后面加上对应的切点(pointcut)位置。我们之前说this表示的是代理对象,而perthis呢,顾名思义就是每一个符合条件的代理对象声明一个切面实例。同理pertarget就是每一个符合条件的目标对象声明一个切面实例。但是使用的地方有些不同,perthispertarget是使用在@Aspect这个注解中的。

perthis例子

老规矩,我们还是要先构造一个小的demo出来。一个接口TargetDao,一个实现类DemoDao,一个测试类DemoTest,一个切面类Demo2Aspect,别忘了加上必要的依赖。

public interface TargetDao {public void print();
}
@Repository("demodao")
@Scope("prototype")    //添加prototype
public class DemoDao implements TargetDao{public void print(){System.out.println("print empty");}
}
public class DemoTest {public static void main(String[] args) {AnnotationConfigApplicationContext anno = new AnnotationConfigApplicationContext(Appconfig.class);TargetDao dao = (TargetDao) anno.getBean("demodao");TargetDao dao1 = (TargetDao) anno.getBean("demodao");dao.print();  //拿取切面1dao1.print(); //拿去切面2}
}
@Component
@Aspect("perthis(myAspectModelThis())")
@Scope("prototype")    //添加prototype
public class Demo2Aspect {@Pointcut("this(com.demo.dao.TargetDao)")public void myAspectModelThis(){ }@Around("myAspectModelThis()")    public void aroundModel(ProceedingJoinPoint pjp){System.out.println("arround start ------ ");System.out.println(this.hashCode());  //打印hashcodetry {pjp.proceed();             //执行连接点} catch (Throwable throwable) {throwable.printStackTrace();}System.out.println("arround end------ ");}
}

在这个例子里面,我们采用打印hashcode的方法来验证。如果我们测试类中dao和dao1调用方法执行后生成的hashcode一样,那么必然就是一个实例。如果不一样说明我们确实给每一个方法声明了一个新的切面对象。

执行,发现确实hashcode不一样,就说明构造了两个实例
arround start ------ 
1923634801
print empty
arround end------ 
arround start ------ 
1730337646
print empty
arround end------

对照

为了对比结果,我们再用正常的流程走一遍。所以我们就把切面中的perthis@Scope("prototype") 去掉,再次执行。

@Component
@Aspect
public class Demo2Aspect {@Pointcut("this(com.demo.dao.TargetDao)")public void myAspectModelThis(){ }@Around("myAspectModelThis()")    public void aroundModel(ProceedingJoinPoint pjp){System.out.println("arround start ------ ");System.out.println(this.hashCode());try {pjp.proceed();             //执行连接点} catch (Throwable throwable) {throwable.printStackTrace();}System.out.println("arround end------ ");}
}
对比执行结果,很明显这次两个使用的是同一个切面类实例对象。
arround start ------ 
483525032
print empty
arround end------ 
arround start ------ 
483525032
print empty
arround end------

pertarget

那么我们接着验证pertarget,我们把@Aspect注解中的perthis替换掉成为pertarget,再运行。

@Aspect("pertarget(myAspectModelThis())")
@Scope("prototype")
public class Demo2Aspect {@Pointcut("this(com.demo.dao.TargetDao)")public void myAspectModelThis(){ }@Around("myAspectModelThis()")    public void aroundModel(ProceedingJoinPoint pjp){System.out.println("arround start ------ ");System.out.println(this.hashCode());try {pjp.proceed();} catch (Throwable throwable) {throwable.printStackTrace();}System.out.println("arround end------ ");}
}
执行,也是两个hashcode,说明这个pertarget也能生成两个对象。
arround start ------ 
1923634801
print empty
arround end------ 
arround start ------ 
1730337646
print empty
arround end------

看到这里大家一定有个疑问,既然表象都一样,这两个语义啥区别呢?

区别

没错,这俩咋大多数情况下是一样的效果。但是区别还是有的,perthis&pertarget的区别同this&target的区别一样。perthis是生成的是代理对象的切面对象,而pertarget生成的是目标对象的切面对象。为了更加明白的展示区别,我们继续改造一下切面类Demo2Aspect,再次运行。

@Aspect("perthis(myAspectModelThis())")
@Scope("prototype")
public class Demo2Aspect {@Pointcut("this(com.demo.dao.DemoDao)")   //注意这里public void myAspectModelThis(){ }@Around("myAspectModelThis()")public void aroundModel(ProceedingJoinPoint pjp){System.out.println("arround start ------ ");System.out.println(this.hashCode());try {pjp.proceed();} catch (Throwable throwable) {throwable.printStackTrace();}System.out.println("arround end------ ");}
}打印结果
print empty
print empty

欸~这回居然就通知不到了。因为this在这里取到的是DemoDao的代理类,并不是DemoDao这个类。而perthis是为了给每一个代理对象生成对应的切面类。而我们的切点里匹配的却是DemoDao这个类的类型,因此perthis不会给DemoDao这个类生成对应的切面类。为什么没有匹配到通知,因为this这个语义就没有获取到任何的代理。那么我们接下来换成pertarget。

@Aspect("pertarget(myAspectModelThis())")
@Scope("prototype")
public class Demo2Aspect {@Pointcut("target(com.demo.dao.DemoDao)")public void myAspectModelThis(){ }@Around("myAspectModelThis()")public void aroundModel(ProceedingJoinPoint pjp){System.out.println("arround start ------ ");System.out.println(this.hashCode());try {pjp.proceed();} catch (Throwable throwable) {throwable.printStackTrace();}System.out.println("arround end------ ");}
}打印结果
arround start ------ 
418513504
print empty
arround end------ 
arround start ------ 
1256405521
print empty
arround end------

这次为什么又有了呢?因为target语句就是为了找到目标类用的,而DemoDao就是我们的目标实现类,所以target语句找到了,自然pertarget就可以给每一个目标类声明一个切面实例。看到这里大家有没有明白点什么呢?

结论:

其实perthis&pertarget只是为了生成多个切面实例用的,并没有什么别的东西,至于能不能生成和是否被通知到有关系。这里用thistarget做例子是为了突出这个事实。如果我们把切点换成within,那么这俩的区别完全看不到,想了解切点的同学请移步【SpringAOP @PointCut 切点解析】。

@Pointcut("within(com.demo.dao.DemoDao)")
public void myAspectModelThis(){ }
附:如果不使用@Scope(“prototype”)会发生什么

直接就报错了:Bean with name 'demo2Aspect' is a singleton, but aspect instantiation model is not singleton,这就是说在强行用一个singleton的模式构建多个实例,可不就得报错么。

Aug 21, 2020 3:29:32 PM org.springframework.context.support.AbstractApplicationContext refresh
WARNING: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'appconfig': BeanPostProcessor before instantiation of bean failed; nested exception is java.lang.IllegalArgumentException: Bean with name 'demo2Aspect' is a singleton, but aspect instantiation model is not singleton
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'appconfig': BeanPostProcessor before instantiation of bean failed; nested exception is java.lang.IllegalArgumentException: Bean with name 'demo2Aspect' is a singleton, but aspect instantiation model is not singletonat org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:511)at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324)at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226)at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:897)at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:879)at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:551)at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:89)at com.demo.main.DemoTest.main(DemoTest.java:10)
Caused by: java.lang.IllegalArgumentException: Bean with name 'demo2Aspect' is a singleton, but aspect instantiation model is not singletonat org.springframework.aop.aspectj.annotation.BeanFactoryAspectJAdvisorsBuilder.buildAspectJAdvisors(BeanFactoryAspectJAdvisorsBuilder.java:122)at org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator.findCandidateAdvisors(AnnotationAwareAspectJAutoProxyCreator.java:95)at org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator.shouldSkip(AspectJAwareAdvisorAutoProxyCreator.java:101)at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessBeforeInstantiation(AbstractAutoProxyCreator.java:251)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInstantiation(AbstractAutowireCapableBeanFactory.java:1140)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.resolveBeforeInstantiation(AbstractAutowireCapableBeanFactory.java:1113)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:505)... 9 more

这篇关于Spring AOP Aspect Instantiation Models 切面实例化模型的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

SpringBoot多环境配置数据读取方式

《SpringBoot多环境配置数据读取方式》SpringBoot通过环境隔离机制,支持properties/yaml/yml多格式配置,结合@Value、Environment和@Configura... 目录一、多环境配置的核心思路二、3种配置文件格式详解2.1 properties格式(传统格式)1.

Apache Ignite 与 Spring Boot 集成详细指南

《ApacheIgnite与SpringBoot集成详细指南》ApacheIgnite官方指南详解如何通过SpringBootStarter扩展实现自动配置,支持厚/轻客户端模式,简化Ign... 目录 一、背景:为什么需要这个集成? 二、两种集成方式(对应两种客户端模型) 三、方式一:自动配置 Thick

Spring WebClient从入门到精通

《SpringWebClient从入门到精通》本文详解SpringWebClient非阻塞响应式特性及优势,涵盖核心API、实战应用与性能优化,对比RestTemplate,为微服务通信提供高效解决... 目录一、WebClient 概述1.1 为什么选择 WebClient?1.2 WebClient 与

Java.lang.InterruptedException被中止异常的原因及解决方案

《Java.lang.InterruptedException被中止异常的原因及解决方案》Java.lang.InterruptedException是线程被中断时抛出的异常,用于协作停止执行,常见于... 目录报错问题报错原因解决方法Java.lang.InterruptedException 是 Jav

深入浅出SpringBoot WebSocket构建实时应用全面指南

《深入浅出SpringBootWebSocket构建实时应用全面指南》WebSocket是一种在单个TCP连接上进行全双工通信的协议,这篇文章主要为大家详细介绍了SpringBoot如何集成WebS... 目录前言为什么需要 WebSocketWebSocket 是什么Spring Boot 如何简化 We

java中pdf模版填充表单踩坑实战记录(itextPdf、openPdf、pdfbox)

《java中pdf模版填充表单踩坑实战记录(itextPdf、openPdf、pdfbox)》:本文主要介绍java中pdf模版填充表单踩坑的相关资料,OpenPDF、iText、PDFBox是三... 目录准备Pdf模版方法1:itextpdf7填充表单(1)加入依赖(2)代码(3)遇到的问题方法2:pd

Java Stream流之GroupBy的用法及应用场景

《JavaStream流之GroupBy的用法及应用场景》本教程将详细介绍如何在Java中使用Stream流的groupby方法,包括基本用法和一些常见的实际应用场景,感兴趣的朋友一起看看吧... 目录Java Stream流之GroupBy的用法1. 前言2. 基础概念什么是 GroupBy?Stream

SpringBoot监控API请求耗时的6中解决解决方案

《SpringBoot监控API请求耗时的6中解决解决方案》本文介绍SpringBoot中记录API请求耗时的6种方案,包括手动埋点、AOP切面、拦截器、Filter、事件监听、Micrometer+... 目录1. 简介2.实战案例2.1 手动记录2.2 自定义AOP记录2.3 拦截器技术2.4 使用Fi

最新Spring Security的基于内存用户认证方式

《最新SpringSecurity的基于内存用户认证方式》本文讲解SpringSecurity内存认证配置,适用于开发、测试等场景,通过代码创建用户及权限管理,支持密码加密,虽简单但不持久化,生产环... 目录1. 前言2. 因何选择内存认证?3. 基础配置实战❶ 创建Spring Security配置文件

Spring Security 单点登录与自动登录机制的实现原理

《SpringSecurity单点登录与自动登录机制的实现原理》本文探讨SpringSecurity实现单点登录(SSO)与自动登录机制,涵盖JWT跨系统认证、RememberMe持久化Token... 目录一、核心概念解析1.1 单点登录(SSO)1.2 自动登录(Remember Me)二、代码分析三、