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

2024-08-25 04:44

本文主要是介绍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);}
}

这篇关于spring揭秘09-aop03-aop织入器织入横切逻辑与自动织入的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听