摘要:

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. NOIP2018提高/普及成绩

    明天就要出了,不忍看到自己爆零,现在很慌. 大家都考的如何呢?欢迎留言自己的分数或预估分数.

  2. Excel催化剂开源第6波-Clickonce部署之自动升级瘦身之术

    Clickonce无痛自动更新是我最喜欢使用VSTO开发并Clickonce部署的特性之一,但这个自动更新,通常会更新整个程序文件,包含所有的引用dll和一些资源文件等. 一般来说,我们更新的都是主程 ...

  3. 7月新的开始 - LayUI的基本使用 - Tab选项卡切换显示对应数据

    LayUI tab选项卡+page展示 要求:实现tab选项卡改变的同时展示数据也跟着改变 实现条件: 1. 选项卡 [官网 – 文档/示例 – 页面元素 – 选项卡] 2.数据表格 [官网 – 文档 ...

  4. sql上传木马

    第十周笔记—SQLmap和sql注入上传木马 目录 第十周笔记—SQLmap和sql注入上传木马 SQL注入 上传木马 SQLmap 六大模块 命令 参数 三种请求方式 取得系统shell sqlma ...

  5. JavaScript Boolean(逻辑)对象

    Boolean(逻辑)对象用于将非逻辑值转换为逻辑值(true 或者 false). 实例 检查逻辑值 检查逻辑对象是 true 还是 false. 完整的 Boolean 对象参考手册 我们提供 J ...

  6. web设计_9_CSS常用布局,响应式

    一个完整的页面和其中的组件该如何具备灵活性. 怎么样利用CSS来实现无论屏幕.窗口以及字体的大小如何变化,都可以自由扩展和收缩的分栏式页面. 要决定使用流动布局.弹性布局还是固定宽度的布局,得由项目的 ...

  7. Java类的加载 链接 初始化

    原文地址 Java类的加载.链接和初始化.Java字节代码的表现形式是字节数组(byte[]),而Java类在JVM中的表现形式是java.lang.Class类的对象.一个Java类从字节代码到能够 ...

  8. hexo的环境变量被删除怎么办

    这篇文章主要讲在path这一环境变量被删除的情况下,补上哪些环境变量才可以使hexo重新使用. 前两天配置opencv的时候,不小心将环境变量中"path"这一项中的内容给覆盖掉了 ...

  9. Flask+APScheduler定时任务

    1.安装依赖 pip install flask_apscheduler 2.使用定时任务 ```python from flask import Flask from flask_apschedul ...

  10. Android 9.0 关机流程分析

    极力推荐文章:欢迎收藏 Android 干货分享 阅读五分钟,每日十点,和您一起终身学习,这里是程序员Android 本篇文章主要介绍 Android 开发中的部分知识点,通过阅读本篇文章,您将收获以 ...