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

文章目录

【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入口

java 复制代码
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@3339ad8e
        System.out.println(proxyObject);
        System.out.println(proxyObject.getClass()); // class jdk.proxy1.$Proxy0  【显然JDK动态代理】
        System.out.println(proxyObject instanceof RobotCallTaskImpl); // false
    }
}

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

【打印日志】

c++ 复制代码
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】目标接口实现类

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

【ICallTask】目标接口

java 复制代码
public interface ICallTask {
    void call(BusiMessage message);
}

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

java 复制代码
public class CallTaskMethodInterceptor implements MethodInterceptor {

    @Override
    public 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入口

java 复制代码
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@13deb50e
        System.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
    } 
}

【打印日志】

c++ 复制代码
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】

java 复制代码
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实现动态代理

java 复制代码
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@6eebc39e
        System.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 ]

java 复制代码
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, 实现了接口 ITrainingSchoolTeacher
        weaver.setInterfaces(ITrainingSchoolTeacher.class);
        TrainingSchoolTeacherIntroducationInterceptorImpl advice = new TrainingSchoolTeacherIntroducationInterceptorImpl();
        // 织入器装配引入型横切逻辑
        weaver.addAdvice(advice);

        // 织入器织入并获取代理对象
        Object proxy = weaver.getProxy();
        ((PublicSchoolTeacher) proxy).teach(); // 代理对象转为目标对象并调用已有方法
        ((ITrainingSchoolTeacher) proxy).trainAfterSchool(); // 代理对象转为横切逻辑接口对象并调用新方法
    }
}

【打印日志】

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

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

java 复制代码
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(...) 方法详情如下:

java 复制代码
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

java 复制代码
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 获取代理对象调用步骤:

java 复制代码
// 第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代理创建工厂

java 复制代码
public interface AopProxyFactory {
    AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException;
}

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

java 复制代码
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】代理创建者助手类

java 复制代码
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】

java 复制代码
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】

java 复制代码
// 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】

java 复制代码
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 复制代码
<?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.xsd
        http://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>

【打印日志】

c++ 复制代码
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】目标类接口

java 复制代码
public interface ICallTask {
    void call(BusiMessage message);
}

【RobotCallTaskImpl】目标类

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

【TimeCostMethodInterceptorImpl】通知

java 复制代码
public class TimeCostMethodInterceptorImpl implements MethodInterceptor {
    @Override
    public 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织入器织入引入型通知

java 复制代码
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();
    }
}

【打印日志】

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

【beans09proxyfactorybeanbasedclassintroduction.xml】

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.xsd
        http://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】统计方法调用次数引用型通知

java 复制代码
public interface IIntroduceMethodInvokeCounter {
    int getCounter();
}

public class IntroduceMethodInvokeCounterImpl implements IIntroduceMethodInvokeCounter {

    private int counter = 0;

    @Override
    public int getCounter() {
        int curCounter = ++counter;
        System.out.printf("方法调用次数=%d\n", curCounter);
        return curCounter;
    }
}

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

java 复制代码
public interface IIntroduceMethodAccessLog {
    void sendAccessLog();
}

public class IntroduceMethodAccessLogImpl implements IIntroduceMethodAccessLog {
    @Override
    public 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自动织入通知

java 复制代码
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", "您有待办任务需要处理"));
    }
}

【打印日志】

c++ 复制代码
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 复制代码
<?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.xsd
        http://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】执行耗时统计环绕通知

java 复制代码
public class TimeCostMethodInterceptorImpl implements MethodInterceptor {
    @Override
    public 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】请求日志环绕通知

java 复制代码
public class AroundLogMethodInterceptorImpl implements MethodInterceptor {
    @Override
    public 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

java 复制代码
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 复制代码
<?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.xsd
        http://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 类层次结构:

java 复制代码
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

java 复制代码
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 复制代码
<?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.xsd
        http://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

java 复制代码
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"));
    }
}

【打印日志】

c++ 复制代码
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 复制代码
<?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.xsd
        http://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】

java 复制代码
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;
    @Nullable
    private ObjectPool pool;

    public CommonsPool2TargetSource() {
        this.setMaxSize(8);
    }
    ...
} 

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

【CommonsPoolTargetSourceMain】

java 复制代码
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", "您有待办任务需要处理"));
    }
}

【打印日志】

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

【beans09commonspooltargetsource.xml】

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.xsd
        http://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定义】目标对象源接口定义

java 复制代码
public interface TargetSource extends TargetClassAware {
    @Nullable
    Class<?> getTargetClass(); // 返回目标对象类型

    default boolean isStatic() { // 用于表明是否返回同一个目标对象实例; SingletonTargetSource返回true,其他情况通常返回false 
        return false;
    }

    @Nullable
    Object getTarget() throws Exception; // 获取目标对象实例 

    default void releaseTarget(Object target) throws Exception { // 是否释放目标对象(如isStatic=false,则自定义释放当前目标对象,设置为null)
    }
}
 // 目标对象类Class装配接口
public interface TargetClassAware {
    @Nullable
    Class<?> getTargetClass();
}

【5.2.1】自定义TargetSource代码实现

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

java 复制代码
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();
    }
}

【打印日志】

c++ 复制代码
connectId=1
connectId=2
connectId=0
connectId=0
connectId=0
connectId=0
connectId=0
connectId=0
connectId=2
connectId=1

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

java 复制代码
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));
        }
    }

    @Override
    public Class<?> getTargetClass() {
        return CustomDBConnection.class;
    }

    @Override
    public Object getTarget() throws Exception {
        while (true) {
            for (CustomDBConnection connection : customDBConnectionList) {
                if (connection.isAvailable()) {
                    connection.setAvailable(false);
                    return connection;
                }
            }
            semaphore.acquire();
        }
    }

    @Override
    public boolean isStatic() {
        return false;
    }

    @Override
    public void releaseTarget(Object target) throws Exception {
        // 重置可用状态为true
        ((CustomDBConnection) target).setAvailable(true);
        semaphore.release();
    }
}

【CustomDBConnection】数据库连接

java 复制代码
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);
    }
}
相关推荐
路在脚下@1 小时前
spring boot的配置文件属性注入到类的静态属性
java·spring boot·sql
啦啦右一1 小时前
Spring Boot | (一)Spring开发环境构建
spring boot·后端·spring
森屿Serien1 小时前
Spring Boot常用注解
java·spring boot·后端
苹果醋32 小时前
React源码02 - 基础知识 React API 一览
java·运维·spring boot·mysql·nginx
Hello.Reader2 小时前
深入解析 Apache APISIX
java·apache
盛派网络小助手3 小时前
微信 SDK 更新 Sample,NCF 文档和模板更新,更多更新日志,欢迎解锁
开发语言·人工智能·后端·架构·c#
菠萝蚊鸭3 小时前
Dhatim FastExcel 读写 Excel 文件
java·excel·fastexcel
旭东怪3 小时前
EasyPoi 使用$fe:模板语法生成Word动态行
java·前端·word
007php0073 小时前
Go语言zero项目部署后启动失败问题分析与解决
java·服务器·网络·python·golang·php·ai编程
∝请叫*我简单先生3 小时前
java如何使用poi-tl在word模板里渲染多张图片
java·后端·poi-tl