首页 > 其他分享 >Spring选择代理

Spring选择代理

时间:2023-01-19 19:33:07浏览次数:33  
标签:Spring System 代理 选择 public println foo class out

jdk和cglib的统一

1. spring的代理选择规则

1.1 两种代理

  1. aspect: 多个通知和多个切面
  2. advisor:一个通知和一个切面

以advisor为例,一个增强包括以下几步骤:

  1. pointcut——定义切点
  2. advice——定义通知(增强)
  3. advisor——创建切面、创建target对象
  4. 创建并执行代理

1.2 切点(org.springframework.aop.Pointcut):用于筛选匹配,匹配成功后增强

  1. 切点实现类:org.springframework.aop.aspectj.AspectJExpressionPointcut
  2. 切点实现类:org.springframework.aop.support.annotation.AnnotationMatchingPointcut

1.3 通知advice(org.aopalliance.intercept.MethodInterceptor 注意,不是spring的cglib的MethodInterceptor):用于功能增强

1.4 切面advisor:org.springframework.aop.support.DefaultPointcutAdvisor

  1. new DefaultPointcutAdvisor(pointcut,advice);
  2. new Target()

1.5 创建并执行代理:

   // 4. 创建代理
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(target1);
        proxyFactory.addAdvisors(advisor);
        proxyFactory.setInterfaces(I1.class); // 设置接口类型
        I1 proxy =(I1) proxyFactory.getProxy();
        // 查看代理类型
        System.out.println(proxy.getClass());
        proxy.foo();
        proxy.bar();

1.6 代理选择

  1. proxyTargetClass = false, 目标实现了接口,用jdk实现
  2. proxyTargetClass = false, 目标没有实现接口,用cglib
  3. proxyTargetClass = true, 总是使用cglib

2. 底层的切点实现

2.1 切点匹配


/**
 * @author ssozh
 * @data 2022/12/30
 */
public class A16 {
    public static void main(String[] args) throws NoSuchMethodException {
        /**
         * case 1 根据方法名进行匹配
         */
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* bar())");
        System.out.println(pointcut.matches(T1.class.getMethod("foo"), T1.class));// 切点匹配 , 要判断的方法是? 目标类是?
        System.out.println(pointcut.matches(T1.class.getMethod("bar"), T1.class));// 匹配成功 返回true

        /**
         * case2 根据annotation进行匹配
         */
        AspectJExpressionPointcut pointcut2 = new AspectJExpressionPointcut();
        pointcut2.setExpression("@annotation(org.springframework.transaction.annotation.Transactional)");
        System.out.println(pointcut2.matches(T1.class.getMethod("foo"), T1.class)); // true
        System.out.println(pointcut2.matches(T1.class.getMethod("bar"), T1.class)); // false

        /**
         * case3
         */
        StaticMethodMatcherPointcut pointcut3 = new StaticMethodMatcherPointcut() {
            @Override
            public boolean matches(Method method, Class<?> targetClass) {
                // 先看方法上是不是有 注解
                MergedAnnotations annotations = MergedAnnotations.from(method);
                if (annotations.isPresent(Transactional.class)) {
                    return true;
                }
                // 第二个参数 默认使用 DIRECT 不会查看实现接口上是否包含该注解
                annotations = MergedAnnotations.from(targetClass, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY);
                return annotations.isPresent(Transactional.class);
            }
        };
        System.out.println(pointcut3.matches(T1.class.getMethod("foo"), T1.class)); // true
        System.out.println(pointcut3.matches(T1.class.getMethod("bar"), T1.class)); // false
        System.out.println(pointcut3.matches(T2.class.getMethod("bar"), T2.class)); // true
        System.out.println(pointcut3.matches(T3.class.getMethod("foo"), T3.class)); // true


    }

    static class T1 {
        @Transactional
        public void foo() {

        }

        public void bar() {

        }
    }

    /**
     * 加在类上,这个类里面所有的方法都需要事务增强
     */
    @Transactional
    static class T2 {
        public void foo() {

        }

        public void bar() {

        }
    }

    @Transactional
    interface I3 {
        void foo();
    }

    /**
     * 接口实现方法也可以实现事务加强
     */
    static class T3 implements I3 {

        @Override
        public void foo() {

        }
    }
}

3. 底层的通知实现

4. 底层的切面实现

4.1 高级的@Aspect切面和低级的Advisor切面

  1. 给代理类添加@Aspect注解,并给通知增加相关注解(@Before、@After等)
  2. ApplicationContext注入BeanPostProcessor=>AnnotationAwareAspectJAutoProxyCreator
  3. AnnotationAwareAspectJAutoProxyCreator对context容器中的Bean对象进行增强
    3.1 通过AbstractAutoProxyCreator接口实现的getAdvicesAndAdvisorsForBean方法( findEligibleAdvisors) 找到切面
    3.2 通过org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#wrapIfNecessary 对找到的切面创建代理
package org.springframework.aop.framework.autoproxy;

/**
 * @author ssozh
 * @data 2022/12/30
 */
public class A17 {

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("config", Config.class);
        context.registerBean("aspect1", Aspect1.class);
        context.registerBean(ConfigurationClassPostProcessor.class);
        // 重点:实现了一个BeanPostProcessor接口:创建-> (创建代理)  -> 依赖注入 —> 初始化 (或者创建代理)
        // org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.findEligibleAdvisors => 寻找切面
        // org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary => 创建代理
        context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);

        // 初始化容器
        context.refresh();

        AnnotationAwareAspectJAutoProxyCreator creator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
        // 第一个重要方法:findEligibleAdvisors 根据目标类型 返回和该目标类型匹配的所有切面,比如
        // 用于org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.getAdvicesAndAdvisorsForBean
        List<Advisor> advisors = creator.findEligibleAdvisors(Target1.class, "target1");
//        List<Advisor> advisors = creator.findEligibleAdvisors(Target2.class, "target2"); // 这个返回的advisors为空
        for (Advisor advisor : advisors) {
            System.out.println(advisor);
        }
        // 第二个重要方法:wrapIfNecessary 根据第一个方法返回的集合,如果不为空,则对目标类进行wrap并返回代理对象(执行这个方法 不需要先执行findEligibleAdvisors)
        Object proxy1 = creator.wrapIfNecessary(new Target1(), "target1", "target1");
        Object proxy2 = creator.wrapIfNecessary(new Target2(), "target2", "target2");
        System.out.println(proxy2.getClass()); // 是本身:A17$Target2
        System.out.println(proxy1.getClass()); // 是代理:class A17$Target1$$EnhancerBySpringCGLIB$$91cd2035
        ((Target1) proxy1).foo();
        ((Target2) proxy2).bar();

//        Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);


    }


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

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

    @Aspect
    static class Aspect1 {
        /**
         * 两个通知,对应两个低级切面
         */
        @Before("execution(* foo())")
        public void before() {
            System.out.println("aspect1 before... ");
        }

        @After("execution(* foo())")
        public void after() {
            System.out.println("aspect1 before... ");
        }
    }

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

        @Bean
        public MethodInterceptor advice3() {
            return invocation -> {
                System.out.println("advice3 before..");
                Object result = invocation.proceed();
                System.out.println("advice3 after..");
                return result;
            };
        }
    }
}

4.2 代理生成时机

代理创建时机:
a. 代理的创建时机

  1. 初始化之后(没有循环依赖时)
  2. 实例创建后,依赖注入前(有循环依赖时),并暂存于二级缓存

b. 依赖注入与初始化不应该被增强,仍赢被施加于初始对象

具体case:

  1. 不存在循环依赖的case:
    1.1 执行Bean1的构造
    1.2 执行Bean1的初始化含税
    1.3 执行Bean1的增强
    1.4 Bean2初始化
    1.5 注入Bean1的增强后对象

  2. 存在循环依赖的case:
    2.1 初始化Bean1
    2.2 初始化Bean2
    2.3 创建Bean1代理
    2.4 Bean2依赖注入Bean1
    2.5 初始化Bean2
    2.6 Bean1依赖注入Bean2
    2.6 初始化Bean1

4.3 吐槽切面的顺序控制

  1. 对于高级切面,可以使用@Order注解来排序,数字小的优先级高。
  2. 对于低级切面DefaultPointAdvisor实现了Order接口,可以setOrder

吐槽:

  1. @Order注解 加在@Aspect的对应的类上有效,加在@Before和@After这类的方法上是无效的
  2. @Order注解 加在@Bean后面也无效

4.4 高级切面转低级切面(@Before注解解析过程)

@Before 前置通知会被转化为下面元素的AspectJMethodBeforeAdvice 形式,该对象包含了如下信息:

  1. 通知代码从哪来
  2. 切点是什么?这里为啥要切点
  3. 通知对象如何创建,本例共用一个Aspect 对象

类似的通知还有:

  1. AspectJAroundAdvice (环绕通知)
  2. AspectJAfterAdvice()
  3. AspectJAfterReturningAdvice
  4. AspectJAfterThrowingAdvice
public class A17_2 {

    static class Aspect {
        @Before("execution(* foo()) ")
        public void before() {
            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");
        }
    }

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

    /**
     * @param args
     */
    public static void main(String[] args) {

        List<Advisor> advisorList = new ArrayList<>();

        // 创建工厂
        AspectInstanceFactory factory = new SingletonAspectInstanceFactory(new Aspect());


        // 假设知道切面类为Aspect->知道方法-
        for (Method method : Aspect.class.getDeclaredMethods()) {

            // 1. 检查是否标注了xx注解,然后获取切点
            AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
            if (method.isAnnotationPresent(Before.class)) {
                // 根据Before的值生成一个切点对象
                String expr = method.getAnnotation(Before.class).value();
                pointcut.setExpression(expr);
            }

            // 2. 通知类
            AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(method, pointcut, factory);

            // 3. 解析低级切面
            Advisor advisor = new DefaultPointcutAdvisor(pointcut,advice);

            // 4. 收集切面集合
            advisorList.add(advisor);
        }
        advisorList.forEach(System.out::println);
    }
}

标签:Spring,System,代理,选择,public,println,foo,class,out
From: https://www.cnblogs.com/SsoZhNO-1/p/15318171.html

相关文章

  • springboot上传下载文件原来这么丝滑
    我使用了hutool的 FileUtil,IdUtil,所以需要引入hutool:<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactI......
  • 恒创科技:选择免备案虚拟主机要避免哪些问题?
    ​选择免备案虚拟主机要避免哪些问题?当市面上大多免备案虚拟主机提供商都声称拥有比彼此更好的服务和解决方案时,对于用户来说,这时候的选择是一项相当复杂的任务。而且,......
  • 【项目实战】从零到一搭建Spring Boot整合Mybatis-plus
    前言2023年想搭建一套属于自己的框架,做一个属于自己想法的项目。这些年工作中一直用公司已有的框架,以前有跟着学习视频搭建过,但自己真正动手搭建时发现问题还是很多,比如没......
  • SpringBoot @Target、@Retention、@Documented注解简介
    jdk1.5起开始提供了4个元注解:@Target、@Retention、@Documented、@Inherited。何谓元注解?就是注解的注解。在程序开发中,有时候我们需要自定义一个注解,这个自定义注解类就......
  • SpringBoot日志框架分析
    本文简介第一部分,介绍spring-jcl适配各种日志框架的方式第二部分,介绍slf4j适配各种日志框架的方式第三部分,介绍下logback框架的使用及原理 一、spring-jcl分析说......
  • springboot整合es
    pom.xml增加依赖(注意版本要和springboot匹配)<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-st......
  • 选择结构
    选择结构if单选择结构我们许多时候需要判断一个东西是否可行,然后我们才会去执行,这样一个过程在程序中用if来表示if语句首先对表达式进行测试,如果表达式结果为真则执行下......
  • springboot 热更 2023.3
    热更使用devtools或者alt+shit+f9ideaFile|Settings|Preferences|Build,Execution,Deployment|Compiler:BuildprojectautomaticallyFile|Setting......
  • 关于#springCloud集成swagger#的问题
    提问:关于#springCloud集成swagger#的问题,如何解决?springCloud整合swagger时Postman请求接口没问题,但用swagger访问时需要认证客户端但后端日志显示已经请求成功。解答:在Sp......
  • springboot 常用项目结构
    servicex//项目名|-admin-ui//管理服务前端代码(一般将UI和SERVICE放到一个工程中,便于管理)|-servicex-auth//模块1......