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+Mybatis实现Mysql分表

《基于SpringBoot+Mybatis实现Mysql分表》这篇文章主要为大家详细介绍了基于SpringBoot+Mybatis实现Mysql分表的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录基本思路定义注解创建ThreadLocal创建拦截器业务处理基本思路1.根据创建时间字段按年进

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

Elasticsearch 在 Java 中的使用教程

《Elasticsearch在Java中的使用教程》Elasticsearch是一个分布式搜索和分析引擎,基于ApacheLucene构建,能够实现实时数据的存储、搜索、和分析,它广泛应用于全文... 目录1. Elasticsearch 简介2. 环境准备2.1 安装 Elasticsearch2.2 J

Java中的String.valueOf()和toString()方法区别小结

《Java中的String.valueOf()和toString()方法区别小结》字符串操作是开发者日常编程任务中不可或缺的一部分,转换为字符串是一种常见需求,其中最常见的就是String.value... 目录String.valueOf()方法方法定义方法实现使用示例使用场景toString()方法方法

Java中List的contains()方法的使用小结

《Java中List的contains()方法的使用小结》List的contains()方法用于检查列表中是否包含指定的元素,借助equals()方法进行判断,下面就来介绍Java中List的c... 目录详细展开1. 方法签名2. 工作原理3. 使用示例4. 注意事项总结结论:List 的 contain

Java实现文件图片的预览和下载功能

《Java实现文件图片的预览和下载功能》这篇文章主要为大家详细介绍了如何使用Java实现文件图片的预览和下载功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... Java实现文件(图片)的预览和下载 @ApiOperation("访问文件") @GetMapping("