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

相关文章

Java中JSON格式反序列化为Map且保证存取顺序一致的问题

《Java中JSON格式反序列化为Map且保证存取顺序一致的问题》:本文主要介绍Java中JSON格式反序列化为Map且保证存取顺序一致的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未... 目录背景问题解决方法总结背景做项目涉及两个微服务之间传数据时,需要提供方将Map类型的数据序列化为co

Java Lambda表达式的使用详解

《JavaLambda表达式的使用详解》:本文主要介绍JavaLambda表达式的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、前言二、Lambda表达式概述1. 什么是Lambda表达式?三、Lambda表达式的语法规则1. 无参数的Lambda表

java中Optional的核心用法和最佳实践

《java中Optional的核心用法和最佳实践》Java8中Optional用于处理可能为null的值,减少空指针异常,:本文主要介绍java中Optional核心用法和最佳实践的相关资料,文中... 目录前言1. 创建 Optional 对象1.1 常规创建方式2. 访问 Optional 中的值2.1

Spring Boot 整合 Apache Flink 的详细过程

《SpringBoot整合ApacheFlink的详细过程》ApacheFlink是一个高性能的分布式流处理框架,而SpringBoot提供了快速构建企业级应用的能力,下面给大家介绍Spri... 目录Spring Boot 整合 Apache Flink 教程一、背景与目标二、环境准备三、创建项目 & 添

Spring组件实例化扩展点之InstantiationAwareBeanPostProcessor使用场景解析

《Spring组件实例化扩展点之InstantiationAwareBeanPostProcessor使用场景解析》InstantiationAwareBeanPostProcessor是Spring... 目录一、什么是InstantiationAwareBeanPostProcessor?二、核心方法解

深入解析 Java Future 类及代码示例

《深入解析JavaFuture类及代码示例》JavaFuture是java.util.concurrent包中用于表示异步计算结果的核心接口,下面给大家介绍JavaFuture类及实例代码,感兴... 目录一、Future 类概述二、核心工作机制代码示例执行流程2. 状态机模型3. 核心方法解析行为总结:三

Spring @RequestMapping 注解及使用技巧详解

《Spring@RequestMapping注解及使用技巧详解》@RequestMapping是SpringMVC中定义请求映射规则的核心注解,用于将HTTP请求映射到Controller处理方法... 目录一、核心作用二、关键参数说明三、快捷组合注解四、动态路径参数(@PathVariable)五、匹配请

Java -jar命令如何运行外部依赖JAR包

《Java-jar命令如何运行外部依赖JAR包》在Java应用部署中,java-jar命令是启动可执行JAR包的标准方式,但当应用需要依赖外部JAR文件时,直接使用java-jar会面临类加载困... 目录引言:外部依赖JAR的必要性一、问题本质:类加载机制的限制1. Java -jar的默认行为2. 类加

Java进程CPU使用率过高排查步骤详细讲解

《Java进程CPU使用率过高排查步骤详细讲解》:本文主要介绍Java进程CPU使用率过高排查的相关资料,针对Java进程CPU使用率高的问题,我们可以遵循以下步骤进行排查和优化,文中通过代码介绍... 目录前言一、初步定位问题1.1 确认进程状态1.2 确定Java进程ID1.3 快速生成线程堆栈二、分析

Swagger在java中的运用及常见问题解决

《Swagger在java中的运用及常见问题解决》Swagger插件是一款深受Java开发者喜爱的工具,它在前后端分离的开发模式下发挥着重要作用,:本文主要介绍Swagger在java中的运用及常... 目录前言1. Swagger 的主要功能1.1 交互式 API 文档1.2 客户端 SDK 生成1.3