spring揭秘09-aop03-aop织入器织入横切逻辑与自动织入

文章目录

  • 【README】
  • 【1】spring aop的织入
    • 【1.1】使用ProxyFactory 作为织入器
    • 【1.2】基于接口的代理(JDK动态代理,目标类实现接口)
      • 【补充】
    • 【1.2】基于类的代理(CGLIB动态代理,目标类没有实现接口)
      • 【1.2.1】实现接口的目标类使用CGLIB动态代理
    • 【1.3】Introduction引入型切面织入
      • 【1.3.1】织入Introduction引入型通知
      • 【1.3.2】织入Introduction引入型切面
      • 【1.3.3】Introduction引入型通知织入总结
  • 【2】ProxyFactory织入器底层原理
    • 【2.1】 ProxyFactory底层实现
      • 【2.1.1】 代理AdvisedSupport
      • 【2.1.2】 ProxyFactory兄弟类图(引入第2种织入器)
  • 【3】spring容器中的织入器-ProxyFactoryBean
    • 【3.1】 ProxyFactoryBean基本概念
    • 【3.2】使用ProxyFactoryBean作为织入器
      • 【3.2.1】基于接口的代理(默认JDK动态代理,目标类实现接口)
      • 【3.2.2】基于类的代理(CGLIB动态代理,目标类没有实现接口)
  • 【4】自动织入AutoProxy
    • 【4.1】BeanNameAutoProxyCreator自动织入(半自动)
    • 【4.2】DefaultAdvisorAutoProxyCreator 自动织入(全自动)
    • 【4.3】自定义 AutoProxyCreator
  • 【5】TargetSource目标对象源
    • 【5.1】可用的TargetSource实现类
      • 【5.1.1】 SingletonTargetSource:单例目标对象源
      • 【5.1.2】PrototypeTargetSource: 原型目标对象源
      • 【5.1.3】CommonsPool2TargetSource:普通池化目标对象源
    • 【5.2】自定义TargetSource
      • 【5.2.1】自定义TargetSource代码实现

【README】

本文总结自《spring揭秘》,作者王福强,非常棒的一本书,墙裂推荐;

本文主要介绍了织入器,织入方式(手工织入,半自动织入,全自动织入), 目标对象源TargetSource;

  • 手工织入器: ProxyFactory, ProxyFactoryBean
  • 自动织入器:
    • BeanNameAutoProxyCreator :半自动织入器;根据beanName匹配需要织入的目标bean;
    • DefaultAdvisorAutoProxyCreator :全自动织入器;(但仅对Advisor切面有效)
    • 自定义 AutoProxyCreator ;
  • TargetSource:封装了目标对象;调用方先调用 TargetSource,TargetSource再返回目标对象;以便在调用链上修改逻辑(偷梁换柱),如仅返回有限数量目标对象池中的目标对象(而不是返回单例目标对象,也不会返回原型目标对象),这是数据库连接池底层实现的原理;


【1】spring aop的织入

【1.1】使用ProxyFactory 作为织入器

1)使用织入器ProxyFactory 需要指定2个最基本的对象:

  • 参数1:被织入通知的目标对象target; 可以通过织入器构造器参数传入,也可以通过 setter方法传入;(构造器注入或setter注入)
  • 参数2:切面Advisor
    • 非引入型切面:使用DefaultPointcutAdvisor封装pointcut与advice,然后把DefaultPointcutAdvisor装配到织入器;
      • 此外:也可以仅传入advice到织入器,织入器底层会新建DefaultPointcutAdvisor用于装配advice,DefaultPointcutAdvisor构造器中使用Pointcut.TRUE 作为 pointcut,即匹配所有切点;
    • 引入型切面:新建切面advisor并装配通知advice,然后把切面传给织入器;
      • 此外:也可以仅传入通知advice到织入器;织入器底层会新建 DefaultPointcutAdvisor切面 封装通知advice;

2)回顾: spring使用动态代理实现aop:

  • 采用JDK动态代理(springaop默认代理模式): 应用到目标类实现接口的情况;
  • 采用CGLIB动态代理: 应用到目标类没有实现接口的情况;(如集成第三方库)


【1.2】基于接口的代理(JDK动态代理,目标类实现接口)

1)基于接口的代理: 底层实现是JDK动态代理, 要求目标类实现接口(代理结果: 代理对象与目标对象实现相同接口,它们是兄弟关系 );

2)业务场景:为方法调用织入上下文访问通知

【BasedItfProxyFactoryMain】基于接口代理的main入口

public class BasedItfProxyFactoryMain {public static void main(String[] args) {RobotCallTaskImpl targetWithItf = new RobotCallTaskImpl(); // 目标对象(实现接口)ProxyFactory weaver = new ProxyFactory(targetWithItf); // ProxyFactory作为织入器weaver.setInterfaces(ICallTask.class); // 明确告知 ProxyFactory,要对ICallTask接口类型进行代理NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor(); // 根据名称匹配pointcut的切面advisor.setMappedName("call");  // 设置拦截方法名(代理方法名)advisor.setAdvice(new CallTaskMethodInterceptor()); // 设置通知weaver.addAdvisor(advisor); // 织入器装配切面// 织入器织入,并获取代理对象Object proxy = weaver.getProxy();ICallTask proxyObject = (ICallTask) proxy;
//        RobotCallTaskImpl proxyObject = (RobotCallTaskImpl) proxy; // 代理对象转为接口实现类,类型转换失败,报错proxyObject.call(BusiMessage.build("任务编号1", "您有待办任务需要处理")); // 通过代理对象调用方法// com.tom.springnote.chapter09.springaop.proxyfactory.baseitf.RobotCallTaskImpl@3339ad8eSystem.out.println(proxyObject);System.out.println(proxyObject.getClass()); // class jdk.proxy1.$Proxy0  【显然JDK动态代理】System.out.println(proxyObject instanceof RobotCallTaskImpl); // false}
}

【注意】代理对象转为接口实现类,类型转换失败,报错;因为JDK动态代理仅针对接口,不针对实现类;(这个客观事实,本文提到过多次)

【打印日志】

2024-08-22 07:28:36.666 before execute method.
机器人拨打电话#call(): BusiMessage{msgId='任务编号1', msgText='您有待办任务需要处理'}
2024-08-22 07:28:38.683 after execute method.
com.tom.springnote.chapter09.proxyfactory.baseitfproxy.RobotCallTaskImpl@3c679bde
class jdk.proxy1.$Proxy0
false

【RobotCallTaskImpl】目标接口实现类

public class RobotCallTaskImpl implements ICallTask {@Overridepublic void call(BusiMessage message) {System.out.println("机器人拨打电话#call(): " + message);try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {throw new RuntimeException(e);}}
}

【ICallTask】目标接口

public interface ICallTask {void call(BusiMessage message);
}

【CallTaskMethodInterceptor】通知实现类 ( 这里是环绕通知,所以实现了 MethodInterceptor

public class CallTaskMethodInterceptor implements MethodInterceptor {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {System.out.println(BusiDatetimeUtils.getNowText() + " before execute method.");Object result = invocation.proceed();System.out.println(BusiDatetimeUtils.getNowText() + " after execute method.");return result;}
}

【补充】

  • weaver.setInterfaces(ICallTask.class); 可以省略; 织入器可以自动识别目标类实现的接口;
  • 只要不把ProxyFactory的 optimize 和 proxyTargetClass设置为true,ProxyFactory都会使用JDK动态代理实现织入


【1.2】基于类的代理(CGLIB动态代理,目标类没有实现接口)

1)基于类的代理: 底层实现是CGLIB动态代理, 目标类可以不实现接口(当然,目标类实现了接口,也可以使用CGLIB动态代理);

2)业务场景:为方法调用织入上下文访问通知 ;

【BasedClassProxyFactoryMain】基于类的代理main入口

public class BasedClassProxyFactoryMain {public static void main(String[] args) {// 新建织入器,装配目标对象, 切面ManNoItfCallTask target = new ManNoItfCallTask();ProxyFactory weaver = new ProxyFactory(target);NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();advisor.setMappedName("call");advisor.setAdvice(new CallTaskMethodInterceptor());weaver.addAdvisor(advisor);// 织入器织入通知,并获取代理对象ManNoItfCallTask proxyObject = (ManNoItfCallTask) weaver.getProxy();proxyObject.call(BusiMessage.build("任务编号1", "您有待办任务需要处理"));// com.tom.springnote.common.aop.ManNoItfCallTask@13deb50eSystem.out.println(proxyObject);// class com.tom.springnote.common.aop.ManNoItfCallTask$$SpringCGLIB$$0 【显然通过CGLIB代理】System.out.println(proxyObject.getClass());System.out.println(proxyObject instanceof ManNoItfCallTask); // true} 
}

【打印日志】

2024-08-22 07:47:28.091 before execute method.
人工拨打电话#call(): BusiMessage{msgId='任务编号1', msgText='您有待办任务需要处理'}
2024-08-22 07:47:30.103 after execute method.
com.tom.springnote.common.aop.ManNoItfCallTask@13deb50e
class com.tom.springnote.common.aop.ManNoItfCallTask$$SpringCGLIB$$0
true

【ManNoItfCallTask】

public class ManNoItfCallTask {public void call(BusiMessage message) {try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {System.out.println("抛出异常");throw new RuntimeException(e);}System.out.println("人工拨打电话#call(): " + message);}
}


【1.2.1】实现接口的目标类使用CGLIB动态代理

1)明确使用CGLIB动态代理: 把ProxyFactory的 optimize 或者 proxyTargetClass设置为true,ProxyFactory使用CGLIB代理实现织入

  • 方式1:设置 proxyTargetClass=true,即可使用CGLIB动态代理,无论目标类实现接口与否
  • 方式2:设置 optimize=true,即可使用CGLIB动态代理,无论目标类实现接口与否

【ImplItfBasedClassProxyFactoryMain】实现接口的目标类使用CGLIB实现动态代理

public class ImplItfBasedClassProxyFactoryMain {public static void main(String[] args) {ProxyFactory weaver = new ProxyFactory(new RobotCallTaskImpl());NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();advisor.setMappedName("call");advisor.setAdvice(new CallTaskMethodInterceptor());weaver.addAdvisor(advisor);
//        weaver.setProxyTargetClass(true); // 方式1:设置 proxyTargetClass=true,即可使用CGLIB动态代理,无论目标类实现接口与否weaver.setOptimize(true); // 方式2:设置 optimize=true,即可使用CGLIB动态代理,无论目标类实现接口与否// 获取代理对象 ICallTask proxyObject = (ICallTask) weaver.getProxy();proxyObject.call(BusiMessage.build("任务编号01", "您有待办任务需要处理"));// com.tom.springnote.chapter09.springaop.proxyfactory.baseitfproxy.RobotCallTaskImpl@6eebc39eSystem.out.println(proxyObject);// class com.tom.springnote.chapter09.springaop.proxyfactory.baseitfproxy.RobotCallTaskImpl$$SpringCGLIB$$0 【显然CGLIB代理】System.out.println(proxyObject.getClass());}
}

2)使用CGLIB动态代理的3种配置方式

  • 方式1:设置 ProxyFactory.proxyTargetClass = true;
  • 方式2:设置 ProxyFactory.optimize= true;
  • 方式3:目标类没有实现任何接口;

【1.3】Introduction引入型切面织入

1)Introduction引入型通知回顾:

  • 引入型通知是为已存在的对象织入新的行为(织入新方法),而不是为已存在的方法织入新行为;所以引入型通知织入不会影响目标对象已有方法;
  • 引入型通知是对象级别的织入,而不是方法级别的织入; 所以 织入引入型通知不需要指定 pointcut ,只需要传入引入型通知advice;
  • 引入型通知只能通过接口定义为目标对象织入新方法,所以织入引入型通知还需要传入新方法所属的接口类型;

【1.3.1】织入Introduction引入型通知

1)业务场景: 公办学校老师PublicSchoolTeacher本职工作是在学校上课, 但部分老师会在课外培训学校兼职培训老师;具体实现是仅给兼职课外培训的公办学校老师织入课外辅导横切逻辑(实现自定义接口ITrainingSchoolTeacher);

2)使用静态引入型通知:

【StaticIntroductionAdviceMain】 代码详情参见: [https://blog.csdn.net/PacosonSWJTU/article/details/141407401 ][https://blog.csdn.net/PacosonSWJTU/article/details/141407401 ]

public class StaticIntroductionAdviceMain {public static void main(String[] args) {// 新建织入器PublicSchoolTeacher target = new PublicSchoolTeacher();ProxyFactory weaver = new ProxyFactory(target);// 使用CGLIB实现动态代理,因为目标对象没有实现接口(而不是JDK动态代理)weaver.setProxyTargetClass(true);// 设置横切逻辑(新增逻辑)的接口 ITrainingSchoolTeacher, 新建引入型横切逻辑// 引入型横切逻辑继承了 DelegatingIntroductionInterceptor, 实现了接口 ITrainingSchoolTeacherweaver.setInterfaces(ITrainingSchoolTeacher.class);TrainingSchoolTeacherIntroducationInterceptorImpl advice = new TrainingSchoolTeacherIntroducationInterceptorImpl();// 织入器装配引入型横切逻辑weaver.addAdvice(advice);// 织入器织入并获取代理对象Object proxy = weaver.getProxy();((PublicSchoolTeacher) proxy).teach(); // 代理对象转为目标对象并调用已有方法((ITrainingSchoolTeacher) proxy).trainAfterSchool(); // 代理对象转为横切逻辑接口对象并调用新方法}
}

【打印日志】

PublicSchoolTeacherImpl#teach(): 公办学校老师:课堂教学
TrainingSchoolTeacherImpl#trainAfterSchool: 兼职课后培训老师,辅导课后作业

3)使用动态引入型通知: 代码详情参见: [https://blog.csdn.net/PacosonSWJTU/article/details/141407401 ][https://blog.csdn.net/PacosonSWJTU/article/details/141407401 ]

public class DynamicIntroductionAdviceMain {public static void main(String[] args) {// 新建目标对象PublicSchoolTeacher target = new PublicSchoolTeacher();// 新建织入器ProxyFactory weaver = new ProxyFactory(target);// 使用CGLIB实现动态代理,因为目标对象没有实现接口(而不是JDK动态代理)weaver.setProxyTargetClass(true);// 设置动态引入型通知横切逻辑的接口, 织入器装配动态引入型通知weaver.setInterfaces(ITrainingSchoolTeacher.class);DelegatePerTargetObjectIntroductionInterceptor delegateIntroductionAdvice =new DelegatePerTargetObjectIntroductionInterceptor(TrainingSchoolTeacherImpl.class, ITrainingSchoolTeacher.class);weaver.addAdvice(delegateIntroductionAdvice);// 织入器织入并获取代理对象Object proxy = weaver.getProxy();((PublicSchoolTeacher) proxy).teach(); // 代理对象转为目标对象并调用已有方法((ITrainingSchoolTeacher) proxy).trainAfterSchool(); // 代理对象转为横切逻辑接口对象并调用新方法}
}

4)静态与动态引入型通知总结与代码示例,参见 [https://blog.csdn.net/PacosonSWJTU/article/details/141407401 ][https://blog.csdn.net/PacosonSWJTU/article/details/141407401 ] , 本文不再赘述;

5)StaticIntroductionAdviceMain 与 DynamicIntroductionAdviceMain代码中,仅 传入引入型通知advice到ProxyFactory,没有传入切面;但ProxyFactory底层会自己新建DefaultIntroductionAdvisor切面对象来封装advice

ProxyFactory.addAdvice(…) 方法详情如下:

public void addAdvice(Advice advice) throws AopConfigException {int pos = this.advisors.size();this.addAdvice(pos, advice);
}public void addAdvice(int pos, Advice advice) throws AopConfigException {Assert.notNull(advice, "Advice must not be null");if (advice instanceof IntroductionInfo introductionInfo) {this.addAdvisor(pos, new DefaultIntroductionAdvisor(advice, introductionInfo)); // 新建DefaultIntroductionAdvisor切面对象来封装advice(引入型通知)} else {if (advice instanceof DynamicIntroductionAdvice) {throw new AopConfigException("DynamicIntroductionAdvice may only be added as part of IntroductionAdvisor");}this.addAdvisor(pos, new DefaultPointcutAdvisor(advice)); // 新建 DefaultPointcutAdvisor 切面对象来封装advice(非引入型通知) }}

【1.3.2】织入Introduction引入型切面

1)上文已经剧透, spring使用 DefaultIntroductionAdvisor 抽象引入型切面;

【DefaultIntroductionAdvisorMain】明确使用默认引入型切面的测试main

public class DefaultIntroductionAdvisorMain {public static void main(String[] args) {// 新建目标对象PublicSchoolTeacher target = new PublicSchoolTeacher();// 新建织入器ProxyFactory weaver = new ProxyFactory(target);// 使用CGLIB实现动态代理,因为目标对象没有实现接口(而不是JDK动态代理)weaver.setProxyTargetClass(true);// 新建动态引入型通知DelegatePerTargetObjectIntroductionInterceptor delegateIntroductionAdvice =new DelegatePerTargetObjectIntroductionInterceptor(TrainingSchoolTeacherImpl.class, ITrainingSchoolTeacher.class);// 新建引用型切面,封装引用型通知DefaultIntroductionAdvisor defaultIntroductionAdvisor = new DefaultIntroductionAdvisor(delegateIntroductionAdvice);// 织入器装配引入型切面weaver.addAdvisor(defaultIntroductionAdvisor);// 织入器织入切面并获取代理对象Object proxy = weaver.getProxy();((PublicSchoolTeacher) proxy).teach(); // 代理对象转为目标对象并调用已有方法((ITrainingSchoolTeacher) proxy).trainAfterSchool(); // 代理对象转为横切逻辑接口对象并调用新方法}
}

【1.3.3】Introduction引入型通知织入总结

1)织入引入型通知:只能使用 IntroductionAdvisor 及其子类;

2) IntroductionAdvisor 接口实现类有:DefaultIntroductionAdvisor , DeclareParentsAdvisor ;

3)因为本文仅介绍织入过程及代码实现,关于引入型通知与切面详情,参见 [https://blog.csdn.net/PacosonSWJTU/article/details/141407401][https://blog.csdn.net/PacosonSWJTU/article/details/141407401] 【3.2】与【4.2】节 ;



【2】ProxyFactory织入器底层原理

1)ProxyFactory 获取代理对象调用步骤:

// 第1步 ProxyFactory#getProxy() 获取代理对象 
public Object getProxy() {return this.createAopProxy().getProxy(); // ProxyFactory 继承自 ProxyCreatorSupport,实际是调用ProxyCreatorSupport对应方法 }
// 第2步:ProxyCreatorSupport#createAopProxy() 创建AopProxy对象,即Aop代理对象  
protected final synchronized AopProxy createAopProxy() {if (!this.active) {this.activate();}return this.getAopProxyFactory().createAopProxy(this); // 这个this实际上是 ProxyFactory 自己 ; ProxyFactory自己就是ProxyCreatorSupport,ProxyFactory自己就是 AdvisedSupport (ProxyCreatorSupport继承自AdvisedSupport)}
// 第3步: getAopProxyFactory() 获取 AopProxyFactory 
// 第4步: AopProxyFactory#createAopProxy() 创建AopProxy对象
public interface AopProxyFactory {AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException;
}
// 第5步:AopProxy#getProxy() 获取Aop代理对象封装的代理对象
public interface AopProxy {Object getProxy();Object getProxy(@Nullable ClassLoader classLoader);Class<?> getProxyClass(@Nullable ClassLoader classLoader);
}


【2.1】 ProxyFactory底层实现

1)ProxyFactory织入通知并获取代理对象逻辑: 创建AopProxy对象(Aop代理对象 ),然后获取Aop代理对象内部封装的代理对象;

2)如何创建AopProxy对象? 使用AopProxyFactory工厂模式创建;

【AopProxyFactory】Aop代理创建工厂

public interface AopProxyFactory {AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException;
}

AopProxyFactory只有一个实现类DefaultAopProxyFactory ,如下:

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {public static final DefaultAopProxyFactory INSTANCE = new DefaultAopProxyFactory();private static final long serialVersionUID = 7930414337282325166L;public DefaultAopProxyFactory() {}public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {return new JdkDynamicAopProxy(config);} else {Class<?> targetClass = config.getTargetClass();if (targetClass == null) {throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");} else {return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) && !ClassUtils.isLambdaClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));}}}...... 
}

【代码解说】createAopProxy() 方法根据代理设置(AdvisedSupport)创建2种AopProxy (这也解释了 【1.2.1】章节中使用CGLIB动态代理的3种配置方式的底层原理

  • 创建JdkDynamicAopProxy JDK动态代理对象(spring实现,非jdk自带): 当optimize=falsle,或proxyTargetClass=false,或者hasNoUserSuppliedProxyInterfaces=false(即目标对象实现了接口);
  • 创建ObjenesisCglibAopProxy CGLIB动态代理对象:目标类没有实现接口且目标类不是代理类且目标类不是lambda类;

3)AopProxy类图:
在这里插入图片描述


【2.1.1】 代理AdvisedSupport

1)ProxyCreatorSupport代码结构:

【ProxyCreatorSupport】代理创建者助手类

public class ProxyFactory extends ProxyCreatorSupport {....
}
public class ProxyCreatorSupport extends AdvisedSupport {private AopProxyFactory aopProxyFactory;...... 
}public class AdvisedSupport extends ProxyConfig implements Advised {...... 
}

【代码解说】织入器ProxyFactory就是 ProxyCreatorSupport 或者AdvisedSupport ;

2)ProxyCreatorSupport 中执行this.getAopProxyFactory().createAopProxy(this) 创建Aop代理对象,这个this实际上就是 织入器ProxyFactory 本身; 所以织入器ProxyFactory 有2个职责:

  • ProxyFactory 继承 ProxyConfig: 封装生成代理的配置信息,有5个重要属性:
    • proxyTargetClass: 设置为true,则使用CGLIB代理;【默认false】
    • optimize:用于告知代理对象是否采取优化措施;设置为true,则使用CGLIB代理; 【默认false】
    • opaque:用于控制生成的代理对象是否可以转为 Advised ;【默认false】
    • exposeProxy:是否把生成的代理对象封装到 ThreadLocal ; 【默认false】
    • frozen:代理对象生成的配置信息一旦设置,不允许修改;(若设置为true,则不能对advice做任何变动,已优化代理对象生成性能,如运行时无法修改通知); 【默认false】
  • ProxyFactory 实现Advised接口: 封装目标类,目标类接口,通知,切面等;


【2.1.2】 ProxyFactory兄弟类图(引入第2种织入器)

1)第2种织入器: ProxyFactoryBean
在这里插入图片描述

2)第1种织入器 ProxyFactory 与 第2种织入器 ProxyFactoryBean,都实现了 ProxyCreatorSupport; ProxyCreatorSupport 使用AopProxyFactory Aop代理工厂创建Aop代理,接着通过Aop代理获取其内部封装的代理对象

【ProxyCreatorSupport】

public class ProxyFactory extends ProxyCreatorSupport {....
}
public class ProxyCreatorSupport extends AdvisedSupport {private AopProxyFactory aopProxyFactory;...... 
}public class AdvisedSupport extends ProxyConfig implements Advised {...... 
}

【3】spring容器中的织入器-ProxyFactoryBean

【3.1】 ProxyFactoryBean基本概念

1)ProxyFactoryBean: Proxy FactoryBean,即创建代理对象的FactoryBean,底层使用工厂模式;(简单理解:通过工厂模式创建proxy)

  • 如果spring容器中有对象依赖于 ProxyFactoryBean, 它将会使用 ProxyFactoryBean#getObject() 方法返回的代理对象;
    在这里插入图片描述

2)ProxyFactoryBean#getObject() 获取代理对象步骤清单如下(以获取代理过的单例bean为例-getSingletonInstance()方法)。

  • 第1步:调用 ProxyCreatorSupport#createAopProxy() , 创建Aop代理;
  • 第2步:传入Aop代理到getProxy()方法 获取代理对象;

【ProxyFactoryBean】

// ProxyFactoryBean#getObject()
public Object getObject() throws BeansException {this.initializeAdvisorChain();if (this.isSingleton()) {return this.getSingletonInstance(); // 获取单例} else {if (this.targetName == null) {this.logger.info("Using non-singleton proxies with singleton targets is often undesirable. Enable prototype proxies by setting the 'targetName' property.");}return this.newPrototypeInstance(); // 获取原型bean}}// ProxyFactoryBean#getSingletonInstance() 
private synchronized Object getSingletonInstance() { // 获取单例bean方法 if (this.singletonInstance == null) {this.targetSource = this.freshTargetSource();if (this.autodetectInterfaces && this.getProxiedInterfaces().length == 0 && !this.isProxyTargetClass()) {Class<?> targetClass = this.getTargetClass();if (targetClass == null) {throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy");}this.setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));}super.setFrozen(this.freezeProxy);this.singletonInstance = this.getProxy(this.createAopProxy()); // 调用createAopProxy方法 获取代理后的单例bean }return this.singletonInstance;}
...... 
}// ProxyCreatorSupport#createAopProxy()
protected final synchronized AopProxy createAopProxy() {if (!this.active) {this.activate();}return this.getAopProxyFactory().createAopProxy(this);}


【3.2】使用ProxyFactoryBean作为织入器

1)ProxyFactoryBean的3个属性:

  • proxyInterfaces: 如果采用基于接口的代理方式(使用JDK动态代理实现aop),通过该属性配置目标接口类;(如果没有配置,则spring会自动检测目标对象所实现的接口类型并进行代理);
    • proxyInterfaces属于Collection,使用 <list> 元素 进行配置;
  • InterceptorNames:指定多个将要织入到目标对象的切面,通知或者Interceptor拦截器(环绕通知实现MethodInterceptor);( 通过xml配置实现批量添加 );
    • 替换掉 ProxyFactory#addAdvice() 与 ProxyFactory#addAdvisor() 方法逐个添加;
    • InterceptorNames属于Collection,使用 <list> 元素
    • 可以在 InterceptorNames属性的属性值之后添加 *通配符; 可以让 ProxyFactoryBean在容器中查找符合条件的所有Advisor并织入到目标对象;
  • singleton: 等于true表示单例, 等于false表示原型bean;


【3.2.1】基于接口的代理(默认JDK动态代理,目标类实现接口)

1)基于接口的代理: 目标类实现接口, spring默认使用JDK动态代理实现aop,要求目标类实现接口;

  • 当然,也可以明确使用 CGLIB动态代理,把 ProxyFactoryBean.proxyTargetClass 设置为true;

【BaseItfProxyFactoryBeanMain】

public class BaseItfProxyFactoryBeanMain {public static void main(String[] args) {ClassPathXmlApplicationContext container =new ClassPathXmlApplicationContext("chapter09/beans09proxyfactorybeanbasedItf.xml");ICallTask callTask = (ICallTask) container.getBean("robotCallTaskImplProxy");callTask.call(BusiMessage.build("任务编号01", "您有待办任务需要处理"));}
}

【beans09proxyfactorybeanbasedItf.xml】配置spring容器织入器ProxyFactoryBean,织入通知到目标对象(基于接口,使用JDK动态代理)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><!-- 注册切点表达式 --><bean id="pointcut" class="org.springframework.aop.support.NameMatchMethodPointcut"><property name="mappedName" value="call" /></bean><!-- 注册横切逻辑 --><bean id="timeCostMethodInterceptor" class="com.tom.springnote.common.aop.methodinterceptor.TimeCostMethodInterceptorImpl" /><!-- 注册切面 --><bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"><property name="pointcut" ref="pointcut" /><property name="advice" ref="timeCostMethodInterceptor" /></bean><!-- 注册 Proxy FactoryBean  scope=prototype指定原型bean,singleton指定单例bean--><bean id="robotCallTaskImplProxy" class="org.springframework.aop.framework.ProxyFactoryBean" scope="prototype"><property name="target"><bean id="robotCallTaskImpl" class="com.tom.springnote.chapter09.proxyfactory.baseitfproxy.RobotCallTaskImpl" /></property><property name="proxyTargetClass" value="true" /> <!-- proxyTargetClass=true表示使用CGLIB代理,否则使用JDK动态代理 --><!--proxyInterfaces 指定目标对象接口 可以省略 -->
<!--        <property name="proxyInterfaces">-->
<!--            <list>-->
<!--                <value>com.tom.springnote.common.aop.ICallTask</value>-->
<!--            </list>-->
<!--        </property>--><!--指定多个将要织入到目标对象的切面,通知或者Interceptor拦截器--><property name="interceptorNames"><list><value>advisor</value></list></property></bean>
</beans>

【打印日志】

stopWatch.start()
机器人拨打电话#call(): BusiMessage{msgId='任务编号01', msgText='您有待办任务需要处理'}
stopWatch.stop()
方法执行耗时2.0101723=== 验证单例还是原型bean ===
com.tom.springnote.chapter09.proxyfactory.baseitfproxy.RobotCallTaskImpl@c333c60
com.tom.springnote.chapter09.proxyfactory.baseitfproxy.RobotCallTaskImpl@79da8dc5=== 验证是JDK动态代理还是CGLIB动态代理 ===
class com.tom.springnote.chapter09.proxyfactory.baseitfproxy.RobotCallTaskImpl$$SpringCGLIB$$0

【ICallTask】目标类接口

public interface ICallTask {void call(BusiMessage message);
}

【RobotCallTaskImpl】目标类

public class RobotCallTaskImpl implements ICallTask {@Overridepublic void call(BusiMessage message) {System.out.println("机器人拨打电话#call(): " + message);try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {throw new RuntimeException(e);}}
}

【TimeCostMethodInterceptorImpl】通知

public class TimeCostMethodInterceptorImpl implements MethodInterceptor {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {StopWatch stopWatch = new StopWatch();try {System.out.println("stopWatch.start()");stopWatch.start();return invocation.proceed(); // 继续调用目标对象对应方法 } catch (Exception e) {System.out.println("抛出异常");e.printStackTrace();} finally {System.out.println("stopWatch.stop()");stopWatch.stop();System.out.printf("方法执行耗时%s\n", stopWatch.getTotalTime(TimeUnit.SECONDS));}return null;}
}


【3.2.2】基于类的代理(CGLIB动态代理,目标类没有实现接口)

1)使用ProxyFactoryBean织入器把引入型通知或切面织入到目标对象; 不需要目标对象实现接口;

【BaseClassIntroductionProxyFactoryBeanMain】使用ProxyFactoryBean织入器织入引入型通知

public class BaseClassIntroductionProxyFactoryBeanMain {public static void main(String[] args) {ClassPathXmlApplicationContext container =new ClassPathXmlApplicationContext("chapter09/beans09proxyfactorybeanbasedclassintroduction.xml");Object proxyBean = container.getBean("introducedRobotCallTaskImplProxy");Object proxyBean2 = container.getBean("introducedRobotCallTaskImplProxy");// 转为 ICallTask 类型ICallTask callTask = (ICallTask) proxyBean;callTask.call(BusiMessage.build("任务编号01", "您有待办任务需要处理"));// 转为引入型接口1的对象IIntroduceMethodInvokeCounter introducedMethodInvokeCounter = (IIntroduceMethodInvokeCounter) proxyBean;introducedMethodInvokeCounter.getCounter();introducedMethodInvokeCounter.getCounter();// 第2个bean调用((IIntroduceMethodInvokeCounter) proxyBean2).getCounter();((IIntroduceMethodInvokeCounter) proxyBean2).getCounter();// 转为引入型接口2的对象IIntroduceMethodAccessLog introduceMethodAccessLog = (IIntroduceMethodAccessLog) proxyBean;introduceMethodAccessLog.sendAccessLog();}
}

【打印日志】

stopWatch.start()
机器人拨打电话#call(): BusiMessage{msgId='任务编号01', msgText='您有待办任务需要处理'}
stopWatch.stop()
方法执行耗时2.0040338
方法调用次数=1
方法调用次数=2
方法调用次数=1
方法调用次数=2
MethodAccessLogImpl#sendAccessLog(): 发送访问日志

【beans09proxyfactorybeanbasedclassintroduction.xml】

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><!-- 注册切点表达式 --><bean id="pointcut" class="org.springframework.aop.support.NameMatchMethodPointcut"><property name="mappedName" value="call" /></bean><!-- 注册横切逻辑 --><bean id="timeCostMethodInterceptorImpl" class="com.tom.springnote.common.aop.methodinterceptor.TimeCostMethodInterceptorImpl" /><!-- 注册切面 --><bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"><property name="pointcut" ref="pointcut" /><property name="advice" ref="timeCostMethodInterceptorImpl"/></bean><!-- 注册 introduction ProxyFactoryBean  scope=prototype指定原型bean,singleton指定单例bean--><bean id="introducedRobotCallTaskImplProxy" class="org.springframework.aop.framework.ProxyFactoryBean" scope="prototype"><property name="target"><bean id="robotCallTaskImpl" class="com.tom.springnote.chapter09.proxyfactory.baseitfproxy.RobotCallTaskImpl" scope="prototype" /></property><property name="proxyInterfaces"><list><value>com.tom.springnote.common.aop.ICallTask</value><value>com.tom.springnote.common.aop.IIntroduceMethodInvokeCounter</value><value>com.tom.springnote.common.aop.IIntroduceMethodAccessLog</value></list></property><property name="interceptorNames"><list><value>advisor</value><value>delegatingIntroductionInterceptor</value><value>delegatingIntroductionInterceptor2</value></list></property></bean><!-- 静态方法引入拦截器1 --><bean id="delegatingIntroductionInterceptor" class="org.springframework.aop.support.DelegatingIntroductionInterceptor" scope="prototype"><constructor-arg><bean class="com.tom.springnote.common.aop.IntroduceMethodInvokeCounterImpl" /></constructor-arg></bean><!-- 静态方法引入拦截器2 --><bean id="delegatingIntroductionInterceptor2" class="org.springframework.aop.support.DelegatingIntroductionInterceptor"><constructor-arg><bean class="com.tom.springnote.common.aop.IntroduceMethodAccessLogImpl" /></constructor-arg></bean>
</beans>

【IntroduceMethodInvokeCounterImpl】统计方法调用次数引用型通知

public interface IIntroduceMethodInvokeCounter {int getCounter();
}public class IntroduceMethodInvokeCounterImpl implements IIntroduceMethodInvokeCounter {private int counter = 0;@Overridepublic int getCounter() {int curCounter = ++counter;System.out.printf("方法调用次数=%d\n", curCounter);return curCounter;}
}

【IntroduceMethodAccessLogImpl】发送请求日志引用型通知

public interface IIntroduceMethodAccessLog {void sendAccessLog();
}public class IntroduceMethodAccessLogImpl implements IIntroduceMethodAccessLog {@Overridepublic void sendAccessLog() {System.out.println("MethodAccessLogImpl#sendAccessLog(): 发送访问日志");}
}

【代码解说】 上述代码有3个通知,包括非引用型与引用型通知;

  • 非引用型通知:环绕通知-timeCostMethodInterceptorImpl ;
  • 引用型通知1:IntroduceMethodInvokeCounterImpl ; 统计方法调用次数;
  • 引用型通知2:IntroduceMethodAccessLogImpl ; 发送请求日志;

【注意】对于 IntroductionInterceptor 引用型通知拦截器接口的实现类;无论是自定义,还是spring提供的实现(DelegatingIntroductionInterceptor , DelegatePerTargetObjectIntroductionInterceptor),在使用的时候,需要设置 IntroductionInterceptor 的scope生命周期, 以保证状态的独立性;



【4】自动织入AutoProxy

1)问题:使用ProxyFactoryBean织入通知(横切逻辑),需要为每一个目标对象新建一个 ProxyFactoryBean; 一个系统的目标对象非常多,需要大量的配置工作;

  • 解决方法:使用自动代理AutoProxy织入通知

2)自动代理实现类-AbstractAutoProxyCreator接口常用实现类:

  • BeanNameAutoProxyCreator:通过名字匹配需要织入通知的bean实例 (既然指定了beanName,就不需要pointcut了 ) ;
  • DefaultAdvisorAutoProxyCreator: 默认切面自动代理创建者;
  • AnnotationAwareAspectJAutoProxyCreator: 通过注解捕获代理信息实现自动织入;
  • AspectJAwareAdvisorAutoProxyCreator:AspectJ类库自动织入;
  • InfrastructureAdvisorAutoProxyCreator:基础设施切面自动代理织入;


【4.1】BeanNameAutoProxyCreator自动织入(半自动)

1)BeanNameAutoProxyCreator配置:

  • beanNames属性: 指定需要拦截的目标对象bean名称;(还可以使用 通配符* 来匹配 )
  • interceptorNames: 指定要织入的切面,通知或拦截器;

【BeanNameAutoProxyCreatorMain】 BeanNameAutoProxyCreator自动织入通知

public class BeanNameAutoProxyCreatorMain {public static void main(String[] args) {ClassPathXmlApplicationContext container =new ClassPathXmlApplicationContext("chapter09/beans09beannameautoproxycreator.xml");ManNoItfCallTask target1 = container.getBean("target1", ManNoItfCallTask.class);ManNoItfCallTask target2 = container.getBean("target2", ManNoItfCallTask.class);// 调用代理对象方法target1.call(BusiMessage.build("任务001", "您有待办任务需要处理"));System.out.println("\n=== 我是分割线 ===");target2.call(BusiMessage.build("任务002", "您有待办任务需要处理"));}
}

【打印日志】

stopWatch.start()
收集请求报文
人工拨打电话#call(): BusiMessage{msgId='任务001', msgText='您有待办任务需要处理'}
收集响应报文
stopWatch.stop()
方法执行耗时2.1096692=== 我是分割线 ===
stopWatch.start()
收集请求报文
人工拨打电话#call(): BusiMessage{msgId='任务002', msgText='您有待办任务需要处理'}
收集响应报文
stopWatch.stop()
方法执行耗时2.0005722

【beans09beannameautoproxycreator.xml】

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><!-- 注册横切逻辑 --><bean id="timeCostMethodInterceptorImpl" class="com.tom.springnote.common.aop.methodinterceptor.TimeCostMethodInterceptorImpl" /><bean id="aroundLogMethodInterceptorImpl" class="com.tom.springnote.common.aop.methodinterceptor.AroundLogMethodInterceptorImpl" /><!-- 目标对象 --><bean id="target1" class="com.tom.springnote.common.aop.ManNoItfCallTask" /><bean id="target2" class="com.tom.springnote.common.aop.ManNoItfCallTask" /><bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"><property name="beanNames"><list><value>target1</value><value>target2</value></list></property><property name="interceptorNames"><list><value>timeCostMethodInterceptorImpl</value><value>aroundLogMethodInterceptorImpl</value></list></property></bean>
</beans>

【TimeCostMethodInterceptorImpl】执行耗时统计环绕通知

public class TimeCostMethodInterceptorImpl implements MethodInterceptor {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {StopWatch stopWatch = new StopWatch();try {System.out.println("stopWatch.start()");stopWatch.start();return invocation.proceed();} catch (Exception e) {System.out.println("抛出异常");e.printStackTrace();} finally {System.out.println("stopWatch.stop()");stopWatch.stop();System.out.printf("方法执行耗时%s\n", stopWatch.getTotalTime(TimeUnit.SECONDS));}return null;}
}

【AroundLogMethodInterceptorImpl】请求日志环绕通知

public class AroundLogMethodInterceptorImpl implements MethodInterceptor {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {System.out.println("收集请求报文");Object result = invocation.proceed();System.out.println("收集响应报文");return result;}
}


【4.2】DefaultAdvisorAutoProxyCreator 自动织入(全自动)

1)需要把 DefaultAdvisorAutoProxyCreator 注册到spring容器(因为是第三方库,所以无法通过注解,可以通过xml配置或者手工硬编码注入);

  • DefaultAdvisorAutoProxyCreator 自动搜索容器内所有Advisor,然后根据Advisor中的pointcut找到匹配的切点,最后把通知织入目标对象切点,织入动作通过动态代理实现,返回代理对象;
  • DefaultAdvisorAutoProxyCreator 只针对切面Advisor有效(切面Advisor仅包含一个advice和一个pointcut)
  • 设置DefaultAdvisorAutoProxyCreator的proxyTargetClass为true, 底层才使用CGLIB实现自动织入;

【DefaultAdvisorAutoProxyCreatorMain】 默认切面自动代理创建者测试main

public class DefaultAdvisorAutoProxyCreatorMain {public static void main(String[] args) {ClassPathXmlApplicationContext container =new ClassPathXmlApplicationContext("chapter09/beans09defaultadvisorautoproxycreator.xml");// 获取 DefaultAdvisorAutoProxyCreator 自动织入的代理对象ManNoItfCallTask proxy1 = (ManNoItfCallTask) container.getBean("target1");ManNoItfCallTask proxy2 = (ManNoItfCallTask) container.getBean("target2");// 调用代理对象方法proxy1.call(BusiMessage.build("任务编号001" ,"您有待办任务需要处理"));System.out.println("\n=== 我是分割线 ===");proxy2.call(BusiMessage.build("任务编号002" ,"您有待办任务需要处理"));}
}

【beans09defaultadvisorautoproxycreator.xml】 一个pointcut表达式对应2个advice通知;

底层原理:切面advisor(封装了advice和pointcut)及目标对象注入spring容器后,DefaultAdvisorAutoProxyCreator会扫描容器中所有切面把advice自动注入到匹配pointcut的目标对象

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><!-- 注册切面织入全自动代理创建者, 实现自动织入通知 --><bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"><property name="proxyTargetClass" value="true" /></bean><!-- 注册切点表达式 --><bean id="pointcut" class="org.springframework.aop.support.NameMatchMethodPointcut"><property name="mappedName" value="call" /></bean><!-- 注册通知(横切逻辑) --><bean id="timeCostMethodInterceptorImpl" class="com.tom.springnote.common.aop.methodinterceptor.TimeCostMethodInterceptorImpl" /><bean id="aroundLogMethodInterceptorImpl" class="com.tom.springnote.common.aop.methodinterceptor.AroundLogMethodInterceptorImpl" /><!-- 目标对象 --><bean id="target1" class="com.tom.springnote.common.aop.ManNoItfCallTask" /><bean id="target2" class="com.tom.springnote.common.aop.ManNoItfCallTask" /><!-- 注册切面 --><bean id="timeCostAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"><property name="pointcut" ref="pointcut"/><property name="advice" ref="timeCostMethodInterceptorImpl" /></bean><bean id="aroundLogAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"><property name="pointcut" ref="pointcut"/><property name="advice" ref="aroundLogMethodInterceptorImpl" /></bean>
</beans>


【4.3】自定义 AutoProxyCreator

1)自定义 AutoProxyCreator ,通过继承 AbstractAdvisorAutoProxyCreator 或者 AbstractAutoProxyCreator 来实现;

2)所有的AutoProxyCreator 都是 SmartInstantiationAwareBeanPostProcessor ;

  • 当spring容器检测到有 SmartInstantiationAwareBeanPostProcessor ,会直接通过该BeanPostProcessor中的逻辑构建对象返回,而不是走正常的对象实例化流程;
  • 所以使用SmartInstantiationAwareBeanPostProcessor , AutoProxyCreator 可以根据目标对象构造并返回代理对象,而不是目标对象本身

3)DefaultAdvisorAutoProxyCreator 类层次结构:

public class DefaultAdvisorAutoProxyCreator extends AbstractAdvisorAutoProxyCreator implements BeanNameAware {public abstract class AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyCreator {public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {public class ProxyProcessorSupport extends ProxyConfig implements Ordered, BeanClassLoaderAware, AopInfrastructureBean {


【5】TargetSource目标对象源

1)背景: 为目标对象织入通知时,不是获取单例的代理对象,也不是获取原型的代理对象,而是获取有限数量对象池中的一个对象(当然,这只是其中一个场景);比如数据库连接池;为此引入了 TargetSource; 简单理解: TargetSource是目标对象容器,可以包含一个或多个目标对象

2)TargetSource定义: TargetSource 是插入在调用方与目标对象之间的拦截逻辑抽象;

  • 原先调用链: 调用方 -> 目标对象;
  • 使用TargetSource后的调用链: 调用方 -> TargetSource -> 目标对象;

3)使用TargetSource,程序就可以控制每次方法调用作用到的具体对象实例:

  • 提供一个目标对象池,调用TargetSource获取对象,而TargetSource每次从对象池获取对象;
  • 让一个TargetSource实现类持有多个目标对象实例, 在每次方法调用时,返回相应的目标对象实例;
  • 特别的,让 TargetSource 只持有一个目标对象实例,每次方法调用都会作用到这一个目标对象(这就是 SingletonTargetSource实现类的处理逻辑);


【5.1】可用的TargetSource实现类

1)TargetSource实现类:

  • **SingletonTargetSource:**单例目标对象源;使用最多, 内部仅持有一个目标对象; (通过ProxyFactoryBean的setTarget()方法设置目标对象后, ProxyFactoryBean内部会自行使用 SingletonTargetSource 对目标对象做封装)
  • PrototypeTargetSource: 原型目标对象源;每次都返回新目标对象;
    • 目标对象bean的scope需要声明为 prototype;
  • HotSwappableTargetSource: 可热替换目标对象源;使用HotSwappableTargetSource封装目标对象,调用swap()方法可以在运行时动态替换目标对象类的具体实现;
  • **CommonsPool2TargetSource:**池化目标对象源; 返回有限数量目标对象池中的实例,这些目标对象地位是平等的;如CommonsPool2TargetSource提供持有一定数量目标对象的对象池, CommonsPool2TargetSource 每次都从对象池中获取目标对象; 如数据库连接池;
  • **ThreadLocalTargetSource:**线程级目标对象源;同一个线程多次调用 TargetSource获取目标对象,获得的是同一个目标对象;而线程A与线程B获取的是不同的目标对象;

【5.1.1】 SingletonTargetSource:单例目标对象源

1) SingletonTargetSource:单例目标对象源;SingletonTargetSource仅持有一个目标对象;

【SingletonTargetSourceMain】单例目标对象源测试main

public class SingletonTargetSourceMain {public static void main(String[] args) {ClassPathXmlApplicationContext container =new ClassPathXmlApplicationContext("chapter09targetsource/beans09singletontargetsource.xml");// 获取代理对象Object proxy = container.getBean("singletonTargetSourceProxy");((ManNoItfCallTask)proxy).call(BusiMessage.build("任务编号001", "您有待办任务需要处理"));}
}

【beans09singletontargetsource.xml】

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><bean id="singletonTargetSource" class="org.springframework.aop.target.SingletonTargetSource"><constructor-arg><!-- 目标对象 (当然默认scope就是singleton)--><bean class="com.tom.springnote.common.aop.ManNoItfCallTask" scope="singleton" /></constructor-arg></bean><!--  使用ProxyFactoryBean织入器,通过FactoryBean 基于 SingletonTargetSource 创建代理对象 --><bean id="singletonTargetSourceProxy" class="org.springframework.aop.framework.ProxyFactoryBean"><property name="proxyTargetClass" value="true" />  <!--使用CGLIB动态代理--><property name="targetSource" ref="singletonTargetSource" />  <!--使用单例目标对象源--><property name="interceptorNames"><list><value>timeCostMethodInterceptorImpl</value><value>aroundLogMethodInterceptorImpl</value></list></property></bean><!-- 注册横切逻辑 --><bean id="timeCostMethodInterceptorImpl" class="com.tom.springnote.common.aop.methodinterceptor.TimeCostMethodInterceptorImpl" /><bean id="aroundLogMethodInterceptorImpl" class="com.tom.springnote.common.aop.methodinterceptor.AroundLogMethodInterceptorImpl" />
</beans>


【5.1.2】PrototypeTargetSource: 原型目标对象源

1) PrototypeTargetSource:原型目标对象源;PrototypeTargetSource每次都返回新对象;

【PrototypeTargetSourceMain】原型目标对象源测试main

public class PrototypeTargetSourceMain {public static void main(String[] args) {ClassPathXmlApplicationContext container =new ClassPathXmlApplicationContext("chapter09targetsource/beans09prototypetargetsource.xml");// 获取代理对象Object proxy = container.getBean("prototypeTargetSourceProxy");((ManNoItfCallTask)proxy).call(BusiMessage.build("任务编号001", "您有待办任务需要处理"));System.out.println("\n === 我是分割线,判断是否原型bean ===");System.out.println(container.getBean("prototypeTargetSourceProxy"));System.out.println("\n === 我是分割线,第2次获取prototypeTargetSourceProxy bean ");System.out.println(container.getBean("prototypeTargetSourceProxy"));}
}

【打印日志】

stopWatch.start()
收集请求报文
人工拨打电话#call(): BusiMessage{msgId='任务编号001', msgText='您有待办任务需要处理'}
收集响应报文
stopWatch.stop()
方法执行耗时2.013637=== 我是分割线,判断是否原型bean ===
stopWatch.start()
收集请求报文
收集响应报文
stopWatch.stop()
方法执行耗时1.274E-4
com.tom.springnote.common.aop.ManNoItfCallTask@3e6ef8ad=== 我是分割线,第2次获取prototypeTargetSourceProxy bean 
stopWatch.start()
收集请求报文
收集响应报文
stopWatch.stop()
方法执行耗时9.24E-5
com.tom.springnote.common.aop.ManNoItfCallTask@346d61be // 【显然第2次获取的bean与第1次不是同一个】

【beans09prototypetargetsource.xml】

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><!-- 目标对象 (设置scope=porototype --><bean id="prototypeTarget" class="com.tom.springnote.common.aop.ManNoItfCallTask" scope="prototype" /><!--注册原型目标对象源--><bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource"><property name="targetBeanName" value="prototypeTarget" /></bean><!--  使用ProxyFactoryBean织入器,通过FactoryBean 基于 PrototypeTargetSource 创建代理对象 --><bean id="prototypeTargetSourceProxy" class="org.springframework.aop.framework.ProxyFactoryBean"><property name="proxyTargetClass" value="true" />  <!--使用CGLIB动态代理--><property name="targetSource" ref="prototypeTargetSource" />  <!--使用原型目标对象源--><property name="interceptorNames"><list><value>timeCostMethodInterceptorImpl</value><value>aroundLogMethodInterceptorImpl</value></list></property></bean><!-- 注册横切逻辑 --><bean id="timeCostMethodInterceptorImpl" class="com.tom.springnote.common.aop.methodinterceptor.TimeCostMethodInterceptorImpl" /><bean id="aroundLogMethodInterceptorImpl" class="com.tom.springnote.common.aop.methodinterceptor.AroundLogMethodInterceptorImpl" />
</beans>


【5.1.3】CommonsPool2TargetSource:普通池化目标对象源

1)CommonsPool2TargetSource:池化目标对象源; 返回有限数量目标对象池中的实例,这些目标对象地位是平等的;如数据库连接池;

2)CommonsPool2TargetSource属性:对象池大小;初始对象数据等;

【CommonsPool2TargetSource】

public class CommonsPool2TargetSource extends AbstractPoolingTargetSource implements PooledObjectFactory<Object> {private int maxIdle = 8;private int minIdle = 0;private long maxWait = -1L;private long timeBetweenEvictionRunsMillis = -1L;private long minEvictableIdleTimeMillis = 1800000L;private boolean blockWhenExhausted = true;@Nullableprivate ObjectPool pool;public CommonsPool2TargetSource() {this.setMaxSize(8);}...
} 

3)池化目标对象源测试main

【CommonsPoolTargetSourceMain】

public class CommonsPoolTargetSourceMain {public static void main(String[] args) {ClassPathXmlApplicationContext container =new ClassPathXmlApplicationContext("chapter09targetsource/beans09commonspooltargetsource.xml");// 获取代理对象Object proxy = container.getBean("commonsPool2TargetSourceProxy");((ManNoItfCallTask)proxy).call(BusiMessage.build("任务编号001", "您有待办任务需要处理"));}
}

【打印日志】

stopWatch.start()
收集请求报文
人工拨打电话#call(): BusiMessage{msgId='任务编号001', msgText='您有待办任务需要处理'}
收集响应报文
stopWatch.stop()
方法执行耗时2.013763

【beans09commonspooltargetsource.xml】

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><!-- 目标对象 (设置scope=porototype --><bean id="prototypeTarget" class="com.tom.springnote.common.aop.ManNoItfCallTask" scope="prototype" /><!--注册普通池化目标对象源--><bean id="commonsPool2TargetSource" class="org.springframework.aop.target.CommonsPool2TargetSource"><property name="targetBeanName" value="prototypeTarget" /></bean><!--  使用ProxyFactoryBean织入器,通过FactoryBean 基于 CommonsPool2TargetSource 创建代理对象 --><bean id="commonsPool2TargetSourceProxy" class="org.springframework.aop.framework.ProxyFactoryBean"><property name="proxyTargetClass" value="true" />  <!--使用CGLIB动态代理--><property name="targetSource" ref="commonsPool2TargetSource" />  <!--普通池化目标对象源--><property name="interceptorNames"><list><value>timeCostMethodInterceptorImpl</value><value>aroundLogMethodInterceptorImpl</value></list></property></bean><!-- 注册横切逻辑 --><bean id="timeCostMethodInterceptorImpl" class="com.tom.springnote.common.aop.methodinterceptor.TimeCostMethodInterceptorImpl" /><bean id="aroundLogMethodInterceptorImpl" class="com.tom.springnote.common.aop.methodinterceptor.AroundLogMethodInterceptorImpl" />
</beans>


【5.2】自定义TargetSource

1)自定义TargetSource: 通过实现 TargetSource接口实现;

【TargetSource定义】目标对象源接口定义

public interface TargetSource extends TargetClassAware {@NullableClass<?> getTargetClass(); // 返回目标对象类型default boolean isStatic() { // 用于表明是否返回同一个目标对象实例; SingletonTargetSource返回true,其他情况通常返回false return false;}@NullableObject getTarget() throws Exception; // 获取目标对象实例 default void releaseTarget(Object target) throws Exception { // 是否释放目标对象(如isStatic=false,则自定义释放当前目标对象,设置为null)}
}// 目标对象类Class装配接口
public interface TargetClassAware {@NullableClass<?> getTargetClass();
}

【5.2.1】自定义TargetSource代码实现

【CustomDBConnectionPoolTargetSourceMain】自定义数据库连接池TargetSource测试main

public class CustomDBConnectionPoolTargetSourceMain {public static void main(String[] args) {ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.setTargetSource(new CustomDBConnectionPoolTargetSourceImpl(3));proxyFactory.setProxyTargetClass(true);// 获取代理对象ExecutorService threadPool = Executors.newFixedThreadPool(4);for (int i = 0; i < 10; i++) {threadPool.execute(() -> {((CustomDBConnection) proxyFactory.getProxy()).printConnectId();});}threadPool.shutdown();}
}

【打印日志】

connectId=1
connectId=2
connectId=0
connectId=0
connectId=0
connectId=0
connectId=0
connectId=0
connectId=2
connectId=1

【CustomDBConnectionPoolTargetSourceImpl】连接池TargetSource实现类 (该连接池实现有并发问题,仅用该示例说明TargetSource的应用场景

public class CustomDBConnectionPoolTargetSourceImpl implements TargetSource {private List<CustomDBConnection> customDBConnectionList;private Semaphore semaphore = new Semaphore(1);public CustomDBConnectionPoolTargetSourceImpl(int poolSize) {customDBConnectionList = new ArrayList<>(poolSize);for (int i = 0; i < poolSize; i++) {customDBConnectionList.add(new CustomDBConnection(i));}}@Overridepublic Class<?> getTargetClass() {return CustomDBConnection.class;}@Overridepublic Object getTarget() throws Exception {while (true) {for (CustomDBConnection connection : customDBConnectionList) {if (connection.isAvailable()) {connection.setAvailable(false);return connection;}}semaphore.acquire();}}@Overridepublic boolean isStatic() {return false;}@Overridepublic void releaseTarget(Object target) throws Exception {// 重置可用状态为true((CustomDBConnection) target).setAvailable(true);semaphore.release();}
}

【CustomDBConnection】数据库连接

public class CustomDBConnection {private long connectId;private boolean available;public CustomDBConnection(long connectId) {this.connectId = connectId;this.available = true;}public long getConnectId() {return connectId;}public boolean isAvailable() {return available;}public void setAvailable(boolean available) {this.available = available;}public void printConnectId() {System.out.println("connectId=" + connectId);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/52281.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据缓存软件Redis推出Redis 8 这更改许可证后首个重大版本更新

2024 年 3 月流行的数据库缓存应用 Redis 宣布修改开源许可证&#xff0c;此次修改许可证的目的本质上就是避免大型云计算公司白嫖&#xff0c;例如亚马逊的 AWS 等。AWS 等云计算服务商基于 Redis 推出托管服务并向客户收费&#xff0c;作为开发商 Redis 并没有获得收益&#…

阿里云CentOs ClickHouse安装

&#x1f339;作者主页&#xff1a;青花锁 &#x1f339;简介&#xff1a;Java领域优质创作者&#x1f3c6;、Java微服务架构公号作者&#x1f604; &#x1f339;简历模板、学习资料、面试题库、技术互助 &#x1f339;文末获取联系方式 &#x1f4dd; ClickHouse安装目录 前言…

easypoi模板导出word并且合并行

导出流程 引入依赖制作模板合并导出 引入依赖 <dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-base</artifactId><version>4.1.2</version> </dependency>制作模板 合并行是备注那一列&#xff0c;这一列…

YOLOv8目标检测推理流程及Python代码

在这章中将介绍目标检测推理原理,以及基于onnx模型使用Python语言进行推理。在推理原理章节中,将了解onnx模型的输入和输出,对输入的图片需要进行预处理的操作,对输出的结果需要进行后处理的操作等;在Python代码篇,将给出推理代码。 这里注意一下的是,由于在导出onnx模型…

DRF——pagination分页模块

文章目录 分页继承APIView类用法1.PageNumberPagination2.LimitOffsetPagination3.CursorPagination 继承GenericAPIView派生类用法1.PageNumberPagination2.LimitOffsetPagination3.CursorPagination 分页 在查看数据列表的API中&#xff0c;如果 数据量 比较大&#xff0c;肯…

嵌入式UI开发-lvgl+wsl2+vscode系列:9、控件(Widgets)(二)

一、前言 接下来我们总结第二部分的控件。 二、示例 1、image&#xff08;图像&#xff09; 1.1、示例1 #include "../../lv_examples.h" #if LV_USE_IMAGE && LV_BUILD_EXAMPLESvoid lv_example_image_1(void) {LV_IMAGE_DECLARE(img_cogwheel_argb);lv…

完成课题ssrf实现.SSH未创建写shell,同时完成其他漏洞复现

一、SSRF (Server-Side Request Forgery) 是一种网络安全漏洞&#xff0c;发生在服务器端应用程序中&#xff0c;允许攻击者通过服务器向任意网络资源发送请求&#xff0c;而无需用户直接参与。这种漏洞通常源于程序设计错误&#xff0c;例如当应用程序使用用户的输入作为URL请…

根据json字符串 自动生成 实体类 Model Entity .NET

①访问json2csharp的在线工具&#xff1a;http://json2csharp.com/ ②复制json字符串&#xff0c;粘贴到左边&#xff0c;按下面Convert按钮 ③右边就是 生成的 实体类 &#xff0c;直接复制到 .cs文件内就能使用 ④或者点击 Zip As File 按钮&#xff0c;直接生成 N个.cs文…

基于Springboot和BS架构的宠物健康咨询系统pf

TOC springboot509基于Springboot和BS架构的宠物健康咨询系统pf 第一章 课题背景及研究内容 1.1 课题背景 信息数据从传统到当代&#xff0c;是一直在变革当中&#xff0c;突如其来的互联网让传统的信息管理看到了革命性的曙光&#xff0c;因为传统信息管理从时效性&#x…

使用VRoid Studio二次元建模,创建专属于自己的二次元卡通人物模型,创建完全免费开源且属于自己VRM模型

最终效果 文章目录 最终效果什么是VRoid Studio官网地址下载安装VRoid Studio1、可以去它的官网下载2、steam安装 创建模型配置参数 导出模型使用别人的VRM模型这里我分享几个不错的模型&#xff0c;大家可以自行去下载 完结 什么是VRoid Studio 如果你玩过能捏脸的游戏你就能…

游戏开发设计模式之责任链模式

责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;是一种行为型设计模式&#xff0c;它允许将请求沿着处理者链进行发送。每个处理者对象都有机会处理该请求&#xff0c;直到某个处理者决定处理该请求为止。 概念与定义 责任链模式的核心思想是将多个处理器…

【传输层协议】UDP协议 {端口号的范围划分;UDP数据报格式;UDP协议的特点;UDP的缓冲区;基于UDP的应用层协议}

一、再谈端口号 1.1 端口号标识网络进程 如何通过端口号找到主机上的网络进程&#xff1f; 在socket编程中bind绑定是最为重要的一步&#xff1a;他将套接字与指定的本地 IP 地址和端口号关联起来&#xff0c;这意味着指定的套接字可以接收来自指定 IP 地址和端口号的数据包…

前端宝典十:webpack性能优化最佳实践

Webpack 内置了很多功能。 通常你可用如下经验去判断如何配置 Webpack&#xff1a; 想让源文件加入到构建流程中去被 Webpack 控制&#xff0c;配置 entry&#xff1b;想自定义输出文件的位置和名称&#xff0c;配置 output&#xff1b;想自定义寻找依赖模块时的策略&#xff…

java swagger解析解决[malformed or unreadable swagger supplied]

原创不易&#xff0c;转载请注明出处&#xff1a; https://zhangxiaofan.blog.csdn.net/article/details/141498211 如果你想看完整的 利用swagger-parser解析yaml中的api请求类型、注释、接口名&#xff0c;可以参考这篇文章。 【Spring/Java项目】如何利用swagger-parser解析…

NC 最长上升子序列(三)

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 给定数组 arr…

OpenCV+Python识别机读卡

背景介绍 正常机读卡是通过读卡机读取识别结果的&#xff0c;目前OpenCV已经这么强大了&#xff0c;尝试着用OpenCVPython来识别机读卡。要识别的机读卡长这样&#xff1a; 我们做以下操作&#xff1a; 1.识别答题卡中每题选中项结果。 不做以下操作&#xff1a; 1.不识别准…

宝塔面板配置node/npm/yarn/pm2....相关全局变量 npm/node/XXX: command not found

1.打开终端 , cd 到根目录 cd / 2.跳转至node目录下,我的node版本是v16.14.2 cd /www/server/nodejs/v16.14.2/bin 2.1 如果不知道自己node版本多少就跳转到 cd /www/server/nodejs 然后查找当前目录下的文件 ls 确定自己的node版本 cd /node版本/bin 3.继续查看bin…

秋招突击——8/21——知识补充——计算机网络——cookie、session和token

文章目录 引言正文Cookie——客户端存储和管理Session——服务端存储和管理Token补充签名和加密的区别常见的加密算法和签名算法 面试题1、HTTP用户后续的操作&#xff0c;服务端如何知道属于同一个用户&#xff1f;如果服务端是一个集群机器怎么办&#xff1f;2、如果禁用了Co…

android13隐藏调节声音进度条下面的设置按钮

总纲 android13 rom 开发总纲说明 目录 1.前言 2.情况分析 3.代码修改 4.编译运行 5.彩蛋 1.前言 将下面的声音调节底下的三个点的设置按钮,隐藏掉。 效果如下 2.情况分析 查看布局文件 通过布局我们可以知道这个按钮就是 com.android.keyguard.AlphaOptimizedImageB…

记忆化搜索与状态压缩:优化递归与动态规划的利器

记忆化搜索是解决递归和动态规划问题的一种高效优化技术。它结合了递归的灵活性和动态规划的缓存思想&#xff0c;通过保存已经计算过的子问题结果&#xff0c;避免了重复计算&#xff0c;大幅提升了算法的效率。当问题状态复杂时&#xff0c;状态压缩技术可以进一步优化空间使…