美文网首页
震惊 ! 用aop对controller代理竟然导致接口404!

震惊 ! 用aop对controller代理竟然导致接口404!

作者: 天还下着毛毛雨 | 来源:发表于2021-10-10 11:24 被阅读0次

    遇到的问题:

    1. springboot 项目配置文件
    spring.aop.proxy-target-class=false
    
    1. aop配置 :
    @Aspect
    @Component
    public class MyAspect {
    
        @Pointcut("execution(* com.lb.springboot_simple_starter.controller.TestController.*(..))")
        public void pc1() {
    
        }
    
        @Before("pc1()")
        public void before() {
            System.out.println("前置通知");
        }
    }
    
    1. Controller
    @RequestMapping("config")
    @RestController
    public class TestController {
    
        @GetMapping("username")
        public String getUsername() {
            return "liuben";
        }
    
    }
    

    最终调用controller 接口,发现404,找不到接口。

    但是如果把

    spring.aop.proxy-target-class=false 配置去掉,并且实现有方法的接口,就又可以了。

    原因

    其实是spring选择动态代理方式的不同导致的。

    jdk动态代理

    jdk动态代理 采用和被代理类实现同一接口的方式, 生成的代理类 没有 被代理的controller的类注解 @Controller以及方法上的@GetMapping,所以springmvc在扫描bean为controller建立起 请求地址和 controller方法 的映射关系时, 判断 这个bean 不需要生成 映射关系 ,所以 之前的请求地址 无法访问到controller的方法了。

    源码 :

    image

    这里判断 是否需要生成映射关系就是看你 类上有没有@Controller和@RequestMapping 注解。

    image

    JDK生成的代理对象 的 类型 没有 这两个注解 所以 不需要处理。

    cglib

    cglib采用继承被代理类的方式, 是可以溯源 到 父类(被代理类) 去找 注解@Controller 注解的。所以这里 生成接口映射 是 正常运行的。

    image

    springboot默认选择jdk 还是 cglib。

    在springboot里,一切都是自动配置的。那么默认的aop代理方式是jdk还是cglib呢。

    其实在平时工作中,如果有实现 过 利用aop实现接口日志的功能的话,就可以发现这种情况下 选择cglib。否则,在无任何自定义配置的情况下,jdk生成的代理对象是无法对controller正常建立映射关系的。

    源码 :

    aop自动配置

    看默认的配置 找spring-boot-autoconfigure包下的jar包下的META-INF下的spring,factories 自动配置工厂。

    image

    找key为 org.springframework.boot.autoconfigure.EnableAutoConfiguration下的value值为org.springframework.boot.autoconfigure.aop.AopAutoConfiguration。

    springboot的spi机制会 加载EnableAutoConfiguration对应的value的所有类。

    image image

    可以看到,

    利用@ConditionalOnProperty注解, 当配置文件中不指定spring.aop.proxy-target-class 或者指定spring.aop.proxy-target-class为true时,默认注册是CglibAutoProxyConfiguration。

    当 设置spring.aop.proxy-target-class为false时,注册的是 JdkDynamicAutoProxyConfiguration。

    CglibAutoProxyConfiguration类将@EnableAspectJAutoProxy的proxyTargetClass设置成true,上面的JdkDynamicAutoProxyConfiguration设置的proxyTargetClass 是false。

    当代理方式的选择

    看AbstractAutoProxyCreator.createProxy 这个方法,为 bean生成代理。

    创建代理工厂ProxyFactory

    ProxyFactory的proxyTargetClass 不手动配置为true

    image

    如果手动配置成false的话,会判断一下, 决定是否把proxyTargetClass 改成true

    image

    evaluateProxyInterfaces方法:

    protected void evaluateProxyInterfaces(Class<?> beanClass, ProxyFactory proxyFactory) {
       // 获取被代理类的接口
       Class<?>[] targetInterfaces = ClassUtils.getAllInterfacesForClass(beanClass, getProxyClassLoader());
       boolean hasReasonableProxyInterface = false;
       // isConfigurationCallbackInterface()判断是否实现的一些特定接口
       // isInternalLanguageInterface 差不多的意思
       // 以上都不是, 并且 接口里的方法 > 0 ,那么hasReasonableProxyInterface = true
       for (Class<?> ifc : targetInterfaces) {
          if (!isConfigurationCallbackInterface(ifc) && !isInternalLanguageInterface(ifc) &&
                ifc.getMethods().length > 0) {
             hasReasonableProxyInterface = true;
             break;
          }
       }
       // 满足上面的判断时, 就ProxyTargetClass 还是false,只是把接口加到了proxyFactory中
       if (hasReasonableProxyInterface) {
          // Must allow for introductions; can't just set interfaces to the target's interfaces only.
          for (Class<?> ifc : targetInterfaces) {
             proxyFactory.addInterface(ifc);
          }
       }
       // 否则 就是ProxyTargetClass
       else {
          proxyFactory.setProxyTargetClass(true);
       }
    }
    

    分析这个方法可以发现, 当ProxyTargetClass被 手动配置为false时, 当没有实现接口,或者接口里没有需要实现的方法, 那么 ProxyTargetClass 同样会被改成true。

    DefaultAopProxyFactory.createAopProxy 方法

    @Override
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
       // 当这个config.isProxyTargetClass() = true时
       if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
          Class<?> targetClass = config.getTargetClass();
          if (targetClass == null) {
             throw new AopConfigException("TargetSource cannot determine target class: " +
                   "Either an interface or a target is required for proxy creation.");
          }
            // 是 接口 或者 是Proxy的子类
          if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                // jdk动态代理
            return new JdkDynamicAopProxy(config);
          }
            // cglib
          return new ObjenesisCglibAopProxy(config);
       }
       else {
            // config.isProxyTargetClass() = false时, jdk
          return new JdkDynamicAopProxy(config);
       }
    }
    

    总结

    proxyTargetClass=true(默认) 
        被代理类是否实现接口并且接口存在方法:
            是  jdk动态代理
            否  cglib动态代理
    proxyTargetClass=true 
        被代理类是否是接口||是否是代理类  =>(一般无法满足) 
            是  : jdk动态代理
            否  : cglib动态代理
    

    所以,当controller不经历任何aop配置的情况下, 或者设置为

    spring.aop.proxy-target-class=false 但是 没实现接口或者 接口里没有方法 ,都是 选择的cglib,aop和接口 是生效的。

    否则 , 用jdk 对controller 生成代理实例 ,接口 会404。

    相关文章

      网友评论

          本文标题:震惊 ! 用aop对controller代理竟然导致接口404!

          本文链接:https://www.haomeiwen.com/subject/dnlholtx.html