Springboot源码分析之代理三板斧


摘要:

Spring的版本变迁过程中,注解发生了很多的变化,然而代理的设计也发生了微妙的变化,从Spring1.xProxyFactoryBean的硬编码岛Spring2.xAspectj注解,最后到了现在广为熟知的自动代理。

Springboot源码分析之代理三板斧

说明:

  • ProxyConfig代理的相关配置类
  • AdvisedSupport实现了Advised,封装了对AdviceAdvisor的操作
  • ProxyCreatorSupport该类及其子类主要是利用代理工厂帮助创建jdk或者cglib的代理对象
  • ProxyProcessorSupport该类及其子类才是我们目前用得做多的,利用后置处理器来进行自动代理处理

ProxyFactoryBean

    package com.github.dqqzj.springboot.aop;          import org.springframework.aop.MethodBeforeAdvice;     import org.springframework.aop.TargetSource;     import org.springframework.aop.framework.ProxyFactoryBean;     import org.springframework.aop.target.SingletonTargetSource;     import org.springframework.context.annotation.Bean;     import org.springframework.stereotype.Component;          import java.lang.reflect.Method;          /**      * @author qinzhongjian      * @date created in 2019-08-24 11:05      * @description: TODO      * @since JDK 1.8.0_212-b10      */     @Component     public class MyMethodBeforeAdvice implements MethodBeforeAdvice {         @Override         public void before(Method method, Object[] args, Object target) throws Throwable {             if (!method.getName().equals("toString")) {                 System.out.println(target.getClass().getName() + "#" + method.getName());             }         }         /**          * 代理的目标对象  效果同setTargetSource(@Nullable TargetSource targetSource)          * TargetSource targetSource = new SingletonTargetSource(aopService);          * 可以从容器获取,也可以类似下面这样直接new,使用区别需要熟悉spring机制。          * factoryBean.setTarget(new AopService());          *          * 设置需要被代理的接口  效果同factoryBean.setProxyInterfaces(new Class[]{AopService.class});          * 若没有实现接口,那就会采用cglib去代理          * 如果有接口不指定的话会代理所有的接口,否则代理指定的接口          *          *  setInterceptorNames方法源代码中有这样的一句话:Set the list of Advice/Advisor bean names. This must always be set          *  to use this factory bean in a bean factory.          */         @Bean         public ProxyFactoryBean proxyFactoryBean(AopService aopService) {             ProxyFactoryBean factoryBean = new ProxyFactoryBean();             factoryBean.setTarget(aopService);             //factoryBean.setInterfaces(AopService.class);                  factoryBean.setInterceptorNames("myMethodBeforeAdvice");             //是否强制使用cglib,默认是false的             //factoryBean.setProxyTargetClass(true);             return factoryBean;         }          }

Springboot源码分析之代理三板斧

源码分析:

        @Override         @Nullable         public Object getObject() throws BeansException {             //根据我们配置的interceptorNames来获取对应的Advisor并加入通知器执行链中             initializeAdvisorChain();             if (isSingleton()) {                 //生成singleton的代理对象,会利用DefaultAopProxyFactory去生成代理           //在内部如果你手动没有去设置需要被代理的接口,Spring会代理你所有的实现接口。                 return getSingletonInstance();             }             else {                 if (this.targetName == null) {                     logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " +                             "Enable prototype proxies by setting the 'targetName' property.");                 }           //和单利非常类似 只不过没有缓存了                 return newPrototypeInstance();             }         }         private synchronized void initializeAdvisorChain() throws AopConfigException, BeansException {             if (this.advisorChainInitialized) {                 return;             }             if (!ObjectUtils.isEmpty(this.interceptorNames)) {                 // 最后一个不能是全局的suffix *,除非我们指定了targetSource之类的                 if (this.interceptorNames[this.interceptorNames.length - 1].endsWith(GLOBAL_SUFFIX) &&                         this.targetName == null && this.targetSource == EMPTY_TARGET_SOURCE) {                     throw new AopConfigException("Target required after globals");                 }                 for (String name : this.interceptorNames) {                     // 如国拦截器的名称是以*结尾的,说明它要去全局里面都搜索出来                     // 全局:去自己容器以及父容器中找,类型为Advisor.class的,名称是以这个名称为开头的prefix的Bean.                     if (name.endsWith(GLOBAL_SUFFIX)) {                         addGlobalAdvisor((ListableBeanFactory) this.beanFactory,                                 name.substring(0, name.length() - GLOBAL_SUFFIX.length()));                     }                     // 一般的情况下我们都是精确匹配                     else {                         Object advice;                         if (this.singleton || this.beanFactory.isSingleton(name)) {                             // 从容器里获取该bean                             advice = this.beanFactory.getBean(name);                         }                         // 原型处理                         else {                             advice = new PrototypePlaceholderAdvisor(name);                         }                         addAdvisorOnChainCreation(advice, name);                     }                 }             }             this.advisorChainInitialized = true;         }       // 将advice对象添加到通知器链中         private void addAdvisorOnChainCreation(Object next, String name) {             // 这里调用namedBeanToAdvisor做了一下适配:成统一的Advisor              Advisor advisor = namedBeanToAdvisor(next);             addAdvisor(advisor);         }     //方法中首先会调用namedBeanToAdvisor(next)方法,将从ioc容器获取的普通对象转换成通知器Advisor对象         private Advisor namedBeanToAdvisor(Object next) {             try {                 return this.advisorAdapterRegistry.wrap(next);             }         }

DefaultAdvisorAdapterRegistry

Springboot源码分析之代理三板斧

这个类还允许我们自定义适配器,然后注册到里面就行。

      @Override         public void registerAdvisorAdapter(AdvisorAdapter adapter) {             this.adapters.add(adapter);         }

ProxyFactoryBean脱离IoC容器使用

Springboot源码分析之代理三板斧

ProxyFactory

Springboot源码分析之代理三板斧

说明:这个类一般是spring自己内部使用的,我们自定义的话很难与容器进行整合,它一般都是返回的原型模式代理

AspectJProxyFactory

Springboot源码分析之代理三板斧

小结:

根据以上案例可以发现 都是首先进行AdvisedSupport的准备,然后交给子类ProxyCreatorSupport根据条件 得到JDK或者CGLIB的AopProxy,当代理对象被调用的时候在invoke或者intercept方法中会调用ProxyCreatorSupport的getInterceptorsAndDynamicInterceptionAdvice方法去初始化advice和各个方法之间的映射关系并缓存
同类方法代理不生效原因?

很多时候会发现代理方法和非代理方法在同一个类中调用不生效和调用顺序有关系,我们进行重构代码来分析一下原因

    public class AspectJProxyFactoryApplication {         public static void main(String[] args) {             AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new AopService());             // 注意:此处得MyAspect类上面的@Aspect注解必不可少             proxyFactory.addAspect(MyAspect.class);             //proxyFactory.setProxyTargetClass(true);//是否需要使用CGLIB代理             AopService proxy = proxyFactory.getProxy();             proxy.test();         }     }
    @Aspect     public class MyAspect {         //@Pointcut("execution(* com.github..aop.*.*(..))")         @Pointcut("execution(* com.github..aop.AopService.hello(..))")         private void pointcut() {         }              @Before("pointcut()")         public void before() {             System.out.println("-----------MyAspect#before-----------");         }     }
    @Service     public class AopService {         public String hello() {             System.out.println("hello, AopService");             return "hello, AopService";         }         public String test() {             System.out.println("test");             return hello();         }     }

答案就是不会生效,究竟是什么引起的呢?其实就是我上面的小结的最后一个知识点。

Springboot源码分析之代理三板斧

这个时候chain没有我们的通知器在里面,
Springboot源码分析之代理三板斧

Springboot源码分析之代理三板斧

最终按照我们的程序执行,下面进行修改切点表达式,如果上面的例子看的咨询的话下面就可以忽略了,主要就是是否增强就是第一个入口函数能否匹配上我们的切点表达式后续的根本不会关心你是否能匹配上。

    @Aspect     public class MyAspect {         @Pointcut("execution(* com.github..aop.*.*(..))")         //@Pointcut("execution(* com.github..aop.AopService.hello(..))")         private void pointcut() {         }              @Before("pointcut()")         public void before() {             System.out.println("-----------MyAspect#before-----------");         }     }

Springboot源码分析之代理三板斧

Springboot源码分析之代理三板斧

处理完后就会按照下面代码正常流程执行完

if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {    return invokeJoinpoint(); }

如果同一个类的方法调用都想让通知器生效怎么办?这个就必须要让通知添加到执行链中才行,根据上面所讲的内容就可以达到这个目的。

未经允许不得转载:杂烩网 » Springboot源码分析之代理三板斧

评论 0

#快捷签到点我#

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

置顶文章