摘要:

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源码分析之代理三板斧的更多相关文章

  1. Springboot源码分析之代理对象内嵌调用

    摘要: 关于这个话题可能最多的是@Async和@Transactional一起混用,我先解释一下什么是代理对象内嵌调用,指的是一个代理方法调用了同类的另一个代理方法.首先在这儿我要声明事务直接的嵌套调 ...

  2. Spring AOP 源码分析 - 创建代理对象

    1.简介 在上一篇文章中,我分析了 Spring 是如何为目标 bean 筛选合适的通知器的.现在通知器选好了,接下来就要通过代理的方式将通知器(Advisor)所持有的通知(Advice)织入到 b ...

  3. SpringBoot源码分析之SpringBoot的启动过程

    SpringBoot源码分析之SpringBoot的启动过程 发表于 2017-04-30   |   分类于 springboot  |   0 Comments  |   阅读次数 SpringB ...

  4. Springboot源码分析之项目结构

    Springboot源码分析之项目结构 摘要: 无论是从IDEA还是其他的SDS开发工具亦或是https://start.spring.io/ 进行解压,我们都会得到同样的一个pom.xml文件 4. ...

  5. 从SpringBoot源码分析 配置文件的加载原理和优先级

    本文从SpringBoot源码分析 配置文件的加载原理和配置文件的优先级     跟入源码之前,先提一个问题:   SpringBoot 既可以加载指定目录下的配置文件获取配置项,也可以通过启动参数( ...

  6. Struts2 源码分析——Action代理类的工作

    章节简言 上一章笔者讲到关于如何加载配置文件里面的package元素节点信息.相信读者到这里心里面对struts2在启动的时候加载相关的信息有了一定的了解和认识.而本章将讲到关于struts2启动成功 ...

  7. springboot源码分析-SpringApplication

    SpringApplication SpringApplication类提供了一种方便的方法来引导从main()方法启动的Spring应用程序 SpringBoot 包扫描注解源码分析 @Spring ...

  8. 源码分析——Action代理类的工作

     Action代理类的新建 通过<Struts2 源码分析——调结者(Dispatcher)之执行action>章节我们知道执行action请求,最后会落到Dispatcher类的serv ...

  9. Springboot源码分析之jar探秘

    摘要: 利用IDEA等工具打包会出现springboot-0.0.1-SNAPSHOT.jar,springboot-0.0.1-SNAPSHOT.jar.original,前面说过它们之间的关系了, ...

随机推荐

  1. Intel FPGA 专用时钟引脚是否可以用作普通输入,输出或双向IO使用?

    原创 by DeeZeng FPGA 的 CLK pin 是否可以用作普通输入 ,输出或双向IO 使用?    这些专用Clock input pin 是否可以当作 inout用,需要看FPGA是否支 ...

  2. MapRedue详细工作流程

    MapRedue详细工作流程 简述 (1)客户端submit之前获取待处理的数据信息,根据参数配置,形成一个任务分配的规划. (2)提交切片信息到YARN(split.xml,job.split,wc ...

  3. Python学习6——再谈抽象(面对对象编程)

    1.对象魔法 在面对对象编程中,术语对象大致意味着一系列数据(属性)以及一套访问和操作这些数据的方法. 使用对象而非全局变量以及函数的原因有多个,而最重要的好处不过以下几点: 多态:可对不同类型的对象 ...

  4. 摘录C#代码片段

    1.保存数据到指定文件String response = "";//数据 byte[] bytes = response.getBytes();//转化Byte string fi ...

  5. UPC Contest RankList – 2019年第二阶段我要变强个人训练赛第十五场

    传送门 A: Colorful Subsequence •题意 给一个长为n的小写字母序列,从中选出字母组成子序列 问最多能组成多少种每个字母都不相同的子序列 (不同位置的相同字母也算是不同的一种) ...

  6. 学习LayUI时自研的表单参数校验框架

    开发背景&痛点:每次写前端的表单的时候需要对表单里用户填写的内容进行校验,减少服务器压力,提前对已知错误对用户提示.每次会要写很多的if else等等对输入框中的内容进行判断,并对为空.格式不 ...

  7. Pivotal:15分钟部署你的应用

    “ 本篇文章介绍的是PaaS平台Pivotal Cloud Foundry(以下简称PCF)的初步使用,相比于传统的IaaS平台(比如阿里云),PCF可实现快速迭代开发与部署,让您专注于业务开发.” ...

  8. SSM框架实现原理图(转)

  9. 【译】Hello Kubernetes快速交互实验手册

    原文:https://kubernetes.io/docs/tutorials 翻译:Edison Zhou 一.基本介绍 此交互实验可以让你不用搭建K8S环境就可以轻松地尝试管理一个简单的容器化应用 ...

  10. drf初体验

    快速开始 安装 pip install djangorestframework 创建django项目 django-admin startproject mydrf 创建APP cd mydrf py ...