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();  //拿取切面1
        dao1.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());  //打印hashcode
        try {
    
    
            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 singleton
	at 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 singleton
	at 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

猜你喜欢

转载自blog.csdn.net/Smallc0de/article/details/108143499
今日推荐