Springboot源码分析之代理三板斧

Posted qinzj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Springboot源码分析之代理三板斧相关的知识,希望对你有一定的参考价值。

摘要:

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

技术图片

说明:

  • 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;
        
    
    

技术图片

源码分析:

        @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

技术图片

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

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

ProxyFactoryBean脱离IoC容器使用

技术图片

ProxyFactory

技术图片

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

AspectJProxyFactory

技术图片

小结:

根据以上案例可以发现 都是首先进行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();
        
    

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

技术图片

这个时候chain没有我们的通知器在里面,
技术图片

技术图片

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

    @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-----------");
        
    

技术图片

技术图片

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

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

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

以上是关于Springboot源码分析之代理三板斧的主要内容,如果未能解决你的问题,请参考以下文章

1Springboot之事务&aop的核心功能源码分析

SpringBoot源码分析之SpringApplication构造方法核心源码分析

springBoot学习笔记源码分析之@springbootApplication

SpringBoot源码分析之---SpringBoot项目启动类SpringApplication浅析

设计模式之JDK动态代理源码分析

SpringBoot源码之Tomcat加载原理分析