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

2021-10-10  本文已影响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。

上一篇下一篇

猜你喜欢

热点阅读