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

news2024/11/15 18:53:11

文章目录

  • 【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@3339ad8e
        System.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 {
    @Override
    public 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 {

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

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
    } 
}

【打印日志】

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@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 ]

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(); // 代理对象转为横切逻辑接口对象并调用新方法
    }
}

【打印日志】

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.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>

【打印日志】

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 {
    @Override
    public 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 {
    @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织入器织入引入型通知

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

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】发送请求日志引用型通知

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自动织入通知

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.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】执行耗时统计环绕通知

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】请求日志环绕通知

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

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.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 类层次结构:

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.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

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.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】

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】

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

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

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));
        }
    }

    @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】数据库连接

public class CustomDBConnection {
    private long connectId;
    private boolean available;

    public CustomDBConnection(long connectId) {
        this.connectId = connectId;
        this.available = true;
    }

    public long getConnectId() {
        return connectId;
    }

    public boolean isAvailable() {
        return available;
    }

    public void setAvailable(boolean available) {
        this.available = available;
    }

    public void printConnectId() {
        System.out.println("connectId=" + connectId);
    }
}

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

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

相关文章

Nginx: 配置项之autoIndex模块与Nginx变量

autoIndex模块 autoindex模块它所实现的一个基本功能&#xff0c;是当用户请求以 / 结尾式的URL&#xff0c;它会列出对应的目录结构比如说, 在实际的生态环境中&#xff0c;内部系统可能经常需要为用户提供一些下载功能。可能需要列出来某一个磁盘上的一个文件&#xff0c; 比…

【D-DCVRP】求解DCVRP改进贪婪算法(三)

一、Held-Harp模型 海尔德和卡尔普在1970年提出景点模型,用于求解TSP问题的最优解下界 该模型同样可以用于DCVRP问题,既有定理1成立。 定理1:根据Held-Karp模型使用向量 π = ( 0 , π 1 , π 2 , ⋯   , π n ) \pi=(0,\pi_1,\pi_2,\cdots,\pi_n) π=(0,π1​,π2​,⋯…

Datawhale第五期夏令营-CV竞赛

CV竞赛 0.赛事报名租用4090 1.开始运行下载文件提交结果 2.内容解释赛题背景赛题目标社会价值评分规则baseline精读代码什么是YOLO 主要代码内容精读使用Ultraalytics运行代码 0.赛事报名 赛事官网:https://www.marsbigdata.com/competition/details?id3839107548872 租用40…

【Redis】RDB和AOF持久化

RDB和AOF持久化 一、什么是持久化&#xff1f;二、RDB三、AOF 一、什么是持久化&#xff1f; 数据一般写在内存上&#xff0c;但是当重新启动计算机内存数据是会丢失的&#xff0c;而硬盘中的数据是不会丢失的&#xff0c;所以&#xff0c;当我们把数据从内存放到硬盘中的话就…

解决Windows下载完anaconda之后,在pycharm中使用anaconda

怎么下载anaconda我就不详细讲了&#xff0c;就是官方下载基本嫩都是下一步下一步你就可以 一、首先配置环境变量如图 二、查看anaconda情况 三、打开pycharm,如下图操作 ## 注意这里的.bat文件需要在你下载到的anaconda中去找 完毕

6款ai智能文章改写软件,轻松实现文章自动改写

在内容创作领域&#xff0c;改写文章是一项费时费力的工作。为了让创作者从繁琐的改写任务中解脱出来&#xff0c;本文将为你详细介绍六款ai智能文章改写软件&#xff0c;助你轻松实现文章自动改写&#xff0c;提升创作效率。 一、创作者的痛点&#xff1a;文章改写的挑战 作为…

【C++ Primer Plus习题】5.5

问题: 解答: #include <iostream> using namespace std;#define MONTHSCOUNT 12int main() {string months[MONTHSCOUNT] { "January","February","March","April","May","June","July","…

高斯混合模型原理及Python实践

高斯混合模型&#xff08;Gaussian Mixture Model&#xff0c;简称GMM&#xff09;是一种统计学中的概率模型&#xff0c;用于表示由多个高斯分布&#xff08;正态分布&#xff09;混合组成的数据集合。其核心原理基于假设数据集中的每个数据点都是由多个潜在的高斯分布之一生成…

SAP商业地产管理(RE-FX)

SAP 提供了多个模块来支持租赁业务流程和会计处理&#xff0c;这些模块包括但不限于&#xff1a; SAP Leasing&#xff1a;这是一个为租赁公司提供的行业解决方案&#xff0c;支持从租赁起源到中期变更和租赁结束选项的所有阶段的业务流程。SAP Leasing 集成了 SAP CRM 和 SAP …

Java 中的 BIO, NIO, AIO 原理以及示例代码

本文参考&#xff1a; https://blog.csdn.net/yhl_jxy/article/details/79335692 https://www.cnblogs.com/cuzzz/p/17290070.html https://www.cnblogs.com/cuzzz/p/17473398.html https://pdai.tech/md/java/io/java-io-nio-select-epoll.html 最近准备看 Kafka 源码&#xf…

2-76 基于matlab的加权平均融合算法

基于matlab的加权平均融合算法&#xff0c;进行灰度或彩色多模态医学图像融合&#xff0c;程序具体很好的通用性&#xff0c;提供图像融合客观评价指标&#xff0c;还给出3组珍贵的已配准的图像。程序已调通&#xff0c;可直接运行。 2-76 多模态医学图像融合 - 小红书 (xiaoho…

C语言 之 浮点数在内存中的存储 详细讲解

文章目录 浮点数浮点数的存储浮点数的存储浮点数的读取例题 浮点数 常见的浮点数&#xff1a;3.14159、1E10&#xff08;表示1*10^10&#xff09;等 浮点数家族包括&#xff1a; float、double、long double 类型。 浮点数表示的范围在float.h 中有定义 浮点数的存储 浮点数…

一个证券交易系统的设计

时间&#xff1a;2024年08月25日 作者&#xff1a;小蒋聊技术 邮箱&#xff1a;wei_wei10163.com 微信&#xff1a;wei_wei10 音频&#xff1a;https://xima.tv/1_JAlD44?_sonic0 希望大家帮个忙&#xff01;如果大家有工作机会&#xff0c;希望帮小蒋内推一下&#xff0c…

墨者学院 手工注入题解(oracle数据库)

简介 Oracle 数据库系统&#xff0c;是美国ORACLE公司&#xff08;甲⻣⽂&#xff09;提供的以分布式数据库为核⼼的⼀组软件 产品。是⽬前世界上使⽤最为⼴泛的&#xff0c;数据库管理系统。 以下是手工注入的流程&#xff1a; 1、判断注入点 使用 and 11 进行拼接 2、确定…

C语言-实参和形参数据传递的特点及使用技巧

&#x1f30f;个人博客&#xff1a;尹蓝锐的博客 引言&#xff1a; 在我们介绍实参和形参数据传递的特点及使用技巧之前&#xff0c;我们先拿一个有关值传递的典型错误案例来剖析一下。 典型错误举例&#xff1a; #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h&g…

学习笔记——TypeScript

文章目录 介绍TS增加类型支持的原因&#xff1a;安装typescript运行TS文件TS常用类型类型标注位置字符串类型、数字类型、布尔类型字面量类型interface类型class类 总结 开发阶段规范代码&#xff0c;使代码更严谨 介绍 TypeScript&#xff08;简称&#xff1a;TS&#xff09;…

《操作系统---PV操作》(同步与互斥)

一、练习题 面包师有很多面包&#xff0c;由n名销售人员推销。每名顾客进店后按序取一个号&#xff0c;并且等待叫号&#xff0c;当一名销售人员空闲时&#xff0c;就按序叫下一个号。可以用两个整型变量来记录当前的取号值和叫号值&#xff0c;试设计一个使销售人员和顾客同步…

PyTorch深度学习模型训练流程的python实现:回归

回归的流程与分类基本一致&#xff0c;只需要把评估指标改动一下就行。回归输出的是损失曲线、R^2曲线、训练集预测值与真实值折线图、测试集预测值散点图与真实值折线图。输出效果如下&#xff1a; 注意&#xff1a;预测值与真实值图像处理为按真实值排序&#xff0c;图中呈现…

聚合智链已获道富环球投资,正式上线AI合约策略资金托管平台

全球最大的托管银行之一道富环球首次进军加密货币领域&#xff0c;聚合智链获得其投资支持&#xff0c;打造出全球领先的AI合约策略资金托管平台&#xff0c;将在2024年8月 28 日正式上线。 道富环球集团的总部位于美国&#xff0c;其成立于1792年&#xff0c;是一家专注于托管…

easypoi模板导出word多页导出加强版

说明 上一篇文章提到多页导出&#xff0c;但是后边发现一个问题&#xff0c;如果用同一个模板导出多页内容&#xff0c;我们去获取多页内容的时候&#xff0c;会发现全部都一样&#xff0c;举个例子&#xff1a; XWPFDocument document WordExportUtil.exportWord07(outputU…