【探索Spring底层】12.谈谈代理创建器与代理创建时机

news2025/5/15 2:56:07

文章目录

  • 1. 前言
  • 2. 谈谈代理创建器
  • 3. 代理创建时机是什么时候
  • 4. 浅谈@Order的失效场景
  • 4. 浅谈@Order的失效场景
  • 5. 高级切面如何转为低级切面

1. 前言

Spring中有两种切面,一种是@Aspect,另一种是Advisor

其中@Aspect是高级切面,Advisor是低级切面

这里的高级和低级并不代表其功能强弱,而是低级切面比较适合框架内部使用,而高级切面比较适合编码开发使用。因为低级切面的功能比较基本。

@Aspect切面里面可以包含一组或多组通知与切面。

而Advisor仅支持一组通知和切面。

@Aspect虽然是一种高级切面,但是Spring处理这种高级切面的时候,依然会把高级切面转化成低级切面。因为只有转化为低级切面才能被Spring内部所使用。


2. 谈谈代理创建器

代理创建器,指的是AnnotationAwareAspectJAutoProxyCreator类

AnnotationAwareAspectJAutoProxyCreator是用来处理被@AspectJ注解标注的切面类和Spring Advisors的。

这里面有两个比较重要的方法:

  • findEligibleAdvisors:这个方法是用来找有资格的Advisors,这里说的有资格的Advisor一部分是低级切面,一部分是高级切面
  • wrapIfNecessary:其内部调用的findEligibleAdvisors, 只要返回集合不空, 则表示需要创建代理

下面测试一下这两个方法。

这里先准备高级切面和低级切面

static class Target1 {
    public void foo() {
        System.out.println("target1 foo");
    }
}

static class Target2 {
    public void bar() {
        System.out.println("target2 bar");
    }
}

@Aspect // 高级切面类
@Order(1)
static class Aspect1 {
    @Before("execution(* foo())")
    public void before1() {
        System.out.println("aspect1 before1...");
    }

    @Before("execution(* foo())")
    public void before2() {
        System.out.println("aspect1 before2...");
    }
}

@Configuration
static class Config {
    @Bean // 低级切面
    public Advisor advisor3(MethodInterceptor advice3) {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* foo())");
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice3);
        return advisor;
    }
    @Bean
    public MethodInterceptor advice3() {
        return invocation -> {
            System.out.println("advice3 before...");
            Object result = invocation.proceed();
            System.out.println("advice3 after...");
            return result;
        };
    }
}

注意:这两个方法均为protected,因此其他包并不能直接调用这两个方法,可以用反射来调用,但是这里为了简便,因此把包名设置成这个方法的类的所在包名一样

package org.springframework.aop.framework.autoproxy;

image-20221216153452748

image-20221216153510617

编写测试方法

public static void main(String[] args) {
    GenericApplicationContext context = new GenericApplicationContext();
    context.registerBean("aspect1", Aspect1.class);
    context.registerBean("config", Config.class);
    context.registerBean(ConfigurationClassPostProcessor.class);
    context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);
    // BeanPostProcessor

    context.refresh();

    /*
       第一个重要方法 findEligibleAdvisors 找到有【资格】的 Advisors
         */
    AnnotationAwareAspectJAutoProxyCreator creator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
    List<Advisor> advisors = creator.findEligibleAdvisors(Target1.class, "target1");
    for (Advisor advisor : advisors) {
        System.out.println(advisor);
    }

    System.out.println("-------------------------------------------");

    /*
           第二个重要方法 wrapIfNecessary
         */
    Object o1 = creator.wrapIfNecessary(new Target1(), "target1", "target1");
    System.out.println(o1.getClass());
    Object o2 = creator.wrapIfNecessary(new Target2(), "target2", "target2");
    System.out.println(o2.getClass());

    ((Target1) o1).foo();

}

image-20221216154000264

creator.findEligibleAdvisors(Target1.class, "target1")

findEligibleAdvisors有两个参数,第一个是目标的类型,第二个参数是这个类在容器中的名字(并不是很重要,随便写就行)

当执行这个方法的时候,就会根据目标的类型与在容器中的每一个Advisors进行对比,将可以的Advisors收集到集合中

这里输出了四个切面

  • org.springframework.aop.interceptor.ExposeInvocationInterceptor.ADVISOR:这是是Spring给所有代理都要加的切面
  • org.springframework.aop.support.DefaultPointcutAdvisor:自己编写的低级切面
  • InstantiationModelAwarePointcutAdvisor:剩下两个是高级切面转化后的两个低级切面
creator.wrapIfNecessary(new Target1(), "target1", "target1")

wrapIfNecessary有三个参数

  1. 目标对象,因为现在是框架外部所以需要自己创建,在框架内部的话则是在容器中寻找

image-20221216155742002

输出的结果可见,o1为代理对象而o2不是代理对象。

这里因为如果目标类型为Target2,那么执行了findEligibleAdvisors方法后返回的集合为空,那么调用wrapIfNecessary就不会创建代理类


3. 代理创建时机是什么时候

对于Bean来讲有三个比较重要的时机,那就是创建、依赖注入和初始化

代理创建的时机一般来说在两个位置

  1. 创建之后,依赖注入之前
  2. 初始化之后

两个位置二选一

准备一个案例

public class A17_1 {

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean(ConfigurationClassPostProcessor.class);
        context.registerBean(Config.class);
        context.refresh();
        context.close();
    }

    @Configuration
    static class Config {
        @Bean // 解析 @Aspect、产生代理
        public AnnotationAwareAspectJAutoProxyCreator annotationAwareAspectJAutoProxyCreator() {
            return new AnnotationAwareAspectJAutoProxyCreator();
        }

        @Bean // 解析 @Autowired
        public AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor() {
            return new AutowiredAnnotationBeanPostProcessor();
        }

        @Bean // 解析 @PostConstruct
        public CommonAnnotationBeanPostProcessor commonAnnotationBeanPostProcessor() {
            return new CommonAnnotationBeanPostProcessor();
        }

        @Bean
        public Advisor advisor(MethodInterceptor advice) {
            AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
            pointcut.setExpression("execution(* foo())");
            return new DefaultPointcutAdvisor(pointcut, advice);
        }

        @Bean
        public MethodInterceptor advice() {
            return (MethodInvocation invocation) -> {
                System.out.println("before...");
                return invocation.proceed();
            };
        }

        @Bean
        public Bean1 bean1() {
            return new Bean1();
        }

        @Bean
        public Bean2 bean2() {
            return new Bean2();
        }
    }

    static class Bean1 {
        public void foo() {

        }
        public Bean1() {
            System.out.println("Bean1()");
        }
        //@Autowired public void setBean2(Bean2 bean2) {
        //    System.out.println("Bean1 setBean2(bean2) class is: " + bean2.getClass());
        //}
        @PostConstruct public void init() {
            System.out.println("Bean1 init()");
        }
    }

    static class Bean2 {
        public Bean2() {
            System.out.println("Bean2()");
        }
        @Autowired public void setBean1(Bean1 bean1) {
            System.out.println("Bean2 setBean1(bean1) class is: " + bean1.getClass());
        }
        @PostConstruct public void init() {
            System.out.println("Bean2 init()");
        }
    }
}

这个案例有个特点,依赖关系是单向的,也就是Bean2依赖于Bean1

执行测试类可以看出

image-20221216160850682

首先调用了Bean1的构造方法,然后调用了Bean1的初始化方法

接下来创建Bean1的代理对象,也就是在Bean1初始化之后创建了Bean1的初始化对象

然后调用Bean2的构造方法

因为Bean2依赖于Bean,需要设置Bean1,从打印结果不难看出,这里的Bean1是一个增强后的代理方法

最后Bean2初始

如果Bean2也依赖于Bean1,那么会出现什么呢?(也就是循环依赖)

static class Bean1 {
    public void foo() {

    }
    public Bean1() {
        System.out.println("Bean1()");
    }
    @Autowired public void setBean2(Bean2 bean2) {
        System.out.println("Bean1 setBean2(bean2) class is: " + bean2.getClass());
    }
    @PostConstruct public void init() {
        System.out.println("Bean1 init()");
    }
}

image-20221216161303301

首先调用了Bean1的构造,按理说就应该调用Bean1的setBean2方法,但是这时候Bean2还没有创建

因此调用了Bean2的构造方法。

接着调用Bean2中的setBean1方法,前面说过这里需要增强后的Bean1代理对象,因此Bean1代理对象的创建应该在调用Bean2中的setBean1方法之前

接下来初始化Bean2

Bean2初始化之后回到Bean1,给Bean1设置Bean2,接下来Bean1初始化

这就是循环依赖的情况下,代理创建的时机了


4. 浅谈@Order的失效场景

@Aspect // 高级切面类
@Order(1)
static class Aspect1 {
    @Before("execution(* foo())")
    public void before1() {
        System.out.println("aspect1 before1...");
    }

    @Before("execution(* foo())")
    public void before2() {
        System.out.println("aspect1 before2...");
    }
}

@Configuration
static class Config {
    @Bean // 低级切面
    public Advisor advisor3(MethodInterceptor advice3) {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* foo())");
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice3);
        //设置切面执行顺序
        advisor.setOrder(2);
        return advisor;
    }
    @Bean
    public MethodInterceptor advice3() {
        return invocation -> {
            System.out.println("advice3 before...");
            Object result = invocation.proceed();
            System.out.println("advice3 after...");
            return result;
        };
    }
}

image-20221216162324165

这里给高级切面设置优先级为1,低级切面设置优先级为2,因此高级切面先完成,低级切面后完成

这里需要注意几个@Order注解失效的情况

  1. 注解加在了错误的位置

    1. @Configuration
      static class Config {
          @Bean // 低级切面
          @Order(2) //不可以加在这里
          public Advisor advisor3(MethodInterceptor advice3) {
              AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
              pointcut.setExpression("execution(* foo())");
              DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice3);
              return advisor;
          }
          @Bean
          public MethodInterceptor advice3() {
              return invocation -> {
                  System.out.println("advice3 before...");
                  Object result = invocation.proceed();
                  System.out.println("advice3 after...");
                  return result;
              };
          }
      }
      
  2. 不可以给高级切面里面的切面进行控制优先级

    1. @Aspect // 高级切面类
      @Order(1)
      static class Aspect1 {
          @Before("execution(* foo())")
          @Order(1)
          public void before1() {
              System.out.println("aspect1 before1...");
          }
      
          @Before("execution(* foo())")
          @Order(2)
          public void before2() {
              System.out.println("aspect1 before2...");
          }
      }
      

4. 浅谈@Order的失效场景

@Aspect // 高级切面类
@Order(1)
static class Aspect1 {
    @Before("execution(* foo())")
    public void before1() {
        System.out.println("aspect1 before1...");
    }

    @Before("execution(* foo())")
    public void before2() {
        System.out.println("aspect1 before2...");
    }
}

@Configuration
static class Config {
    @Bean // 低级切面
    public Advisor advisor3(MethodInterceptor advice3) {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* foo())");
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice3);
        //设置切面执行顺序
        advisor.setOrder(2);
        return advisor;
    }
    @Bean
    public MethodInterceptor advice3() {
        return invocation -> {
            System.out.println("advice3 before...");
            Object result = invocation.proceed();
            System.out.println("advice3 after...");
            return result;
        };
    }
}

image-20221216162324165

这里给高级切面设置优先级为1,低级切面设置优先级为2,因此高级切面先完成,低级切面后完成

这里需要注意几个@Order注解失效的情况

  1. 注解加在了错误的位置

    1. @Configuration
      static class Config {
          @Bean // 低级切面
          @Order(2) //不可以加在这里
          public Advisor advisor3(MethodInterceptor advice3) {
              AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
              pointcut.setExpression("execution(* foo())");
              DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice3);
              return advisor;
          }
          @Bean
          public MethodInterceptor advice3() {
              return invocation -> {
                  System.out.println("advice3 before...");
                  Object result = invocation.proceed();
                  System.out.println("advice3 after...");
                  return result;
              };
          }
      }
      
  2. 不可以给高级切面里面的切面进行控制优先级

    1. @Aspect // 高级切面类
      @Order(1)
      static class Aspect1 {
          @Before("execution(* foo())")
          @Order(1)
          public void before1() {
              System.out.println("aspect1 before1...");
          }
      
          @Before("execution(* foo())")
          @Order(2)
          public void before2() {
              System.out.println("aspect1 before2...");
          }
      }
      

5. 高级切面如何转为低级切面

Spring底层是如何将高级切面转成低级切面的呢?请看下面案例

首先准备一个切面

static class Aspect {
    @Before("execution(* foo())")
    public void before1() {
        System.out.println("before1");
    }

    @Before("execution(* foo())")
    public void before2() {
        System.out.println("before2");
    }

    public void after() {
        System.out.println("after");
    }

    public void afterReturning() {
        System.out.println("afterReturning");
    }

    public void afterThrowing() {
        System.out.println("afterThrowing");
    }

    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        try {
            System.out.println("around...before");
            return pjp.proceed();
        } finally {
            System.out.println("around...after");
        }
    }
}

static class Target {
    public void foo() {
        System.out.println("target foo");
    }
}

就以@Before为例

首先遍历Aspect类中所有方法

接着判断方法上是否有@Before注解

如果有,则通过method.getAnnotation(Before.class).value()获取@Before的值

新建一个切点AspectJExpressionPointcut

给这个切点设置表达式pointcut.setExpression(expression);

最后需要一个通知类AspectJMethodBeforeAdvice

新建这个通知类需要三个参数,第一个是方法对象,第二个是切点,第三个是切面实例工厂

切面实例工厂也就是指new SingletonAspectInstanceFactory(new Aspect())

最后new一个低级切面new DefaultPointcutAdvisor(pointcut, advice)

这样高级切面就转为低级切面了;

public static void main(String[] args) throws Throwable {

    AspectInstanceFactory factory = new SingletonAspectInstanceFactory(new Aspect());
    // 高级切面转低级切面类
    List<Advisor> list = new ArrayList<>();
    for (Method method : Aspect.class.getDeclaredMethods()) {
        if (method.isAnnotationPresent(Before.class)) {
            // 解析切点
            String expression = method.getAnnotation(Before.class).value();
            AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
            pointcut.setExpression(expression);
            // 通知类
            AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(method, pointcut, factory);
            // 切面
            Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
            list.add(advisor);
        }
    }
    for (Advisor advisor : list) {
        System.out.println(advisor);
    }

}

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

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

相关文章

【网站架构】网站系统怎么才是安全的?安全验收?等保、网络安全、SQL盲注、https、鉴权

大家好&#xff0c;欢迎来到停止重构的频道。 本期我们讨论网站系统的安全性。 安全的重要性不言而喻&#xff0c;大部分安全问题确实是安全扫描后根据指引修改就可以了。 但是仍有一些问题修改起来是特别麻烦的&#xff0c;这些问题会严重影响上线时间。 本期我们的重点不…

共享购模式简单又好玩,撑起市场的半边天,推动实体产业改造上级

在2022年1月18日&#xff0c;国家发展改革委等七部局下发《促进消费实施方案》的通知&#xff0c;确立了”消费送积分”的新形式。该政策的出台&#xff0c;表明了政府探索实施全国绿色消费积分制度&#xff0c;鼓励地方结合实际建立本地绿色消费积分制度&#xff0c;以兑换商品…

S3 Drive支持以及FIPS 140-2兼容性

S3 Drive支持以及FIPS 140-2兼容性 在Windows Arm64上运行-添加了在Microsoft Windows for Arm64上的功能。无需额外下载&#xff0c;安装程序将为您的系统选择正确的驱动程序和库。 现在符合FIPS 140-2。 现在&#xff0c;您可以使用新的CacheOnlyFiles设置阻止上载临时(或其他…

基于高分辨率时频分析的单通道地震数据自动噪声衰减方法(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 记录的地震信号常常被噪声破坏。本文使用了一种基于高分辨率时频分析的单通道地震数据自动噪声衰减方法。同步压缩是一种时频重…

MySQL——保证主从一致

binlog 可以用来归档&#xff0c;也可以用来做主备同步&#xff0c;备库执行了 binlog 就可以跟主库保持一致。 MySQL 主备的基本原理 如图 1 所示就是基本的主备切换流程。 在状态 1 中&#xff0c;客户端的读写都直接访问节点 A&#xff0c;而节点 B 是 A 的备库&#xff…

Java安全--CC4

CC4 环境提一小嘴&#xff1a; CC4利用的是commons-collections4&#xff0c;所以我们需要导入新的依赖&#xff0c;地址&#xff1a;https://mvnrepository.com/artifact/org.apache.commons/commons-collections4/4.0 我们先来关注一下利用链&#xff1a; 后半段是一样的&am…

本地运行好好的 Java 程序, 一发布到线上就报错的灵异事件终于让我碰到了

说明 本文涉及的相关软件版本如下&#xff1a; mybatis 3.4.xHotSpot JDK1.8Windows 11IDEA 2022.3 先看一段 mybatis 相关的代码 今天一个朋友丢给我如下一段代码&#xff1a; 然后跟我讲为什么本地是好好的&#xff0c; 发布到线上执行就报错。 BlogMapper.java public…

【python机器学习】K-Means算法详解及给坐标点聚类实战(附源码和数据集 超详细)

需要源码和数据集请点赞关注收藏后评论区留言私信~~~ 人们在面对大量未知事物时&#xff0c;往往会采取分而治之的策略&#xff0c;即先将事物按照相似性分成多个组&#xff0c;然后按组对事物进行处理。机器学习里的聚类就是用来完成对事物进行分组的任务 一、样本处理 聚类…

技术原理|Hologres Binlog技术原理揭秘

作者&#xff1a;张高迪&#xff08;花名杳天&#xff09;&#xff0c;Hologres研发。 同传统MySQL数据库&#xff0c;Hologres支持Hologres binlog&#xff0c;记录数据库中所有数据的变化事件日志。通过Hologres binlog&#xff0c;可以非常方便灵活的实现数据之间的复制、同…

“电池黑马”瑞浦兰钧增速惊人,动储双起飞

撰稿 | 多客 来源 | 贝多财经 12月14日&#xff0c;“电池黑马”瑞浦兰钧能源股份有限公司&#xff08;以下简称“瑞浦兰钧”&#xff09;向港交所主板提交上市申请&#xff0c;摩根士丹利和中信证券为其联席保荐人。至此&#xff0c;国内动力电池装机量排名前十的企业均已上…

DB Optimizer Multiplatform SQL评测和调优IDE

DB Optimizer Multiplatform SQL评测和调优IDE 增加了对最新版本Log4j的支持。 改进了分析会话功能&#xff0c;可提前提醒用户可能有问题的SQL。 DB Optimizer可以快速发现、诊断和优化性能较差的SQL。DBOptimizer使DBA和开发人员能够在整个开发生命周期中优化SQL性能&#xf…

合并多个有序数组

合并多个有序数组题目描述思想代码实现变形题目题目描述 我们现在有多个已经有序的数组&#xff0c;我们知道每个有序数组的元素个数和总共的有序数组的个数&#xff0c;现在请设计一个算法来实现这多个有序数组的合并&#xff08;合并成一个数组&#xff09;; 例如&#xff1a…

Chrome浏览器可以用ChatGPT了?

程序员宝藏库&#xff1a;https://gitee.com/sharetech_lee/CS-Books-Store 最近这段时间想必 和我一样&#xff0c;都被chatGPT刷屏了。 在看到网上给出的一系列chatGPT回答问题的例子和自己亲自体验之后&#xff0c;的确发现它效果非常令人惊艳。 chatGPT的火热程度在开源社…

turbo编码原理

一、原理 Turbo的编码器由两个并行的分量编码器组成。分量编码器的选择一般是卷积码。在Turbo码中&#xff0c;输入序列在进入第二个编码器时须经过一个交织器 &#xff0c;用于将序列打乱。两个编码器的输出共同作为冗余信息添加到信息序列之后&#xff0c;对抗信道引起的错误…

实战SupersetBI报表之数据集图表配置

上集已经安装完Superset -实战SupersetBI报表之安装 本集开始讲解 根据数据集配置图表&#xff1a;以简单的员工花名册 为例 1、首先配置数据库 上次安装的时候也提到过 如果服务之间都是docker 安装。必须保证能够通信 下面根据实际参数配置即可 当我们配置好数据库之后 就可以…

如何保证TCP传输的可靠性

重传机制&#xff0c;流量控制&#xff0c;拥塞控制 1.重传机制&#xff1a; 序列号确认应答 当发送端的数据到达接收主机的时候&#xff0c;接收端主机会返回一个确认应答消息&#xff0c;表示已经收到消息 当数据发生丢包时&#xff0c;用重传机制解决 重传机制有好几种…

【Anime.js】——用Anime.js实现动画效果

目录 目标&#xff1a; ​编辑1、确定思路 2、创建网格 3、设置随机位置 4、创建时间轴动画 完整代码&#xff1a; 目标&#xff1a; 实现自动选点&#xff0c;对该点进行先缩小后放大如何回到比其他点大一点的状态&#xff0c;并以该点从外向内放大 1、确定思路 2、创建网…

第12届嵌入式蓝桥杯真题-停车场管理系统的设计与实现

目录 实验要求&#xff1a; 实验思路&#xff1a; 核心代码&#xff1a; &#xff08;1&#xff09;主函数 &#xff08;2&#xff09;lcd显示 &#xff08;3&#xff09;按键函数 &#xff08;4&#xff09;LED显示函数 &#xff08;5&#xff09;业务处理函数 &…

深度理解取模

深度理解取模一.取模概念二.负数取模三.进一步的解释四.取模和取余是一样的吗&#xff1f;一.取模概念 二.负数取模 上面的代码一目了然就不再多少啦&#xff0c;但如果是负数取模又该怎么办呢&#xff1f; 以上a/b-3是很好理解的&#xff0c;那为什么取模后的值是-1呢&#xf…

useEffect 和 useLayoutEffect 的源码解读

文章目录useEffect 和 useLayoutEffect 的源码解读useEffect源码解读mountEffectImplpushEffectupdateEffectImpl疑惑&#xff1a;useLayOutEffect源码解读mountLayoutEffectupdateLayoutEffect总结useEffect 和 useLayoutEffect 的源码解读 useEffect 文件在 packages/react…