遇到的问题:
- springboot 项目配置文件
spring.aop.proxy-target-class=false
- 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("前置通知");
}
}
- 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 注解。
imageJDK生成的代理对象 的 类型 没有 这两个注解 所以 不需要处理。
cglib
cglib采用继承被代理类的方式, 是可以溯源 到 父类(被代理类) 去找 注解@Controller 注解的。所以这里 生成接口映射 是 正常运行的。
imagespringboot默认选择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
imageevaluateProxyInterfaces方法:
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。
网友评论