@EnableAspectJAutoProxy注解,在SpringBoot中设置proxyTargetClass不生效?
最近在SpringBoot中使用@EnableAspectJAutoProxy注解,好奇proxyTargetClass的作用,看了API文档说是用来切换jdk代理和cglib代理的,可以实际上设置true或者false都是cglib的代理,难道是SpringBoo的bug?
前置准备
- 一个有接口的实现,理论上默认使用jdk的动态代理
@Service
@Slf4j
public class LoginServiceImpl implements LoginService {
@Override
public void login() {
log.info("Interface Login...");
}
}
- 一个没有接口的实现,用的是cglib代理
@Service
@Slf4j
public class PlainLoginService {
public void login() {
log.info("Class Login...");
}
}
- Aspect切面配置
@Aspect
@Slf4j
public class FooAspect {
@Pointcut("execution(* io.spring.action.aop.service..*.*(..))")
public void pointcut() {
}
@After(value = "pointcut()")
public void after() {
log.info(this.getClass().toString());
log.info("after login");
}
}
SpringBoot环境
@SpringBootApplication
@Slf4j
// 指示是否创建基于子类(CGLIB)的代理,而不是创建基于标准Java接口的代理。 默认值是{@code false}。
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class SpringAopApplicationDemo implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(SpringAopApplicationDemo.class, args);
}
// 定义切面,或者直接使用@Component注解
@Bean
public FooAspect fooAspect() {
return new FooAspect();
}
// 有接口实现
@Autowired
LoginService loginService;
// 无接口实现
@Autowired
PlainLoginService plainLoginService;
@Override
public void run(ApplicationArguments args) throws Exception {
log.info(loginService.getClass().toString()); // class io.spring.action.aop.service.impl.LoginServiceImpl$$EnhancerBySpringCGLIB$$d3171a1f
loginService.login();
log.info(plainLoginService.getClass().toString()); // class io.spring.action.aop.service.PlainLoginService$$EnhancerBySpringCGLIB$$5b51dab3
plainLoginService.login();
}
}
可以看到运行结果,proxyTargetClass设置为true,生成的依旧是cglib的代理类而不是jdk的代理
Spring环境
@Configuration
@ComponentScan
@EnableAspectJAutoProxy(proxyTargetClass = false)
@Slf4j
public class SpringApplicationDemo {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringApplicationDemo.class);
LoginService loginService = context.getBean(LoginService.class); //jdk
PlainLoginService plainLoginService = context.getBean(PlainLoginService.class); // cglib
log.info("jdk class name = {}", loginService.getClass());
log.info("cglib class name = {}", plainLoginService.getClass());
}
@Autowired
private LoginService loginService;
// 定义切面,或者直接使用@Component注解
@Bean
public FooAspect fooAspect() {
return new FooAspect();
}
}
- proxyTargetClass = true
jdk class name = class io.spring.action.aop.service.impl.LoginServiceImpl$$EnhancerBySpringCGLIB$$8808e216
cglib class name = class io.spring.action.aop.service.PlainLoginService$$EnhancerBySpringCGLIB$$1043a2aa
- proxyTargetClass = false
jdk class name = class com.sun.proxy.$Proxy21
cglib class name = class io.spring.action.aop.service.PlainLoginService$$EnhancerBySpringCGLIB$$3b8f4e09
- 可以看出,在Spring的环境下设置为true生成的都是cglib的代理,设置为false,默认有接口实现的是jdk的动态代理,没有接口的是cglib代理
- 说明在Spring环境下是有作用的,应该是SpringBoot做了什么操作,修改了这个行为。
- SpringBoot什么东西会修改默认行为呢?很容易想到自动装配
AopAutoConfiguration
- 可以看到玄机在这个类里面,SpringBoot使用了Aop自动装配,将代理类默认设置成cglib的
- 另一方面,也看到能够修改这个行为的,是通过spring.aop.proxy-target-class 属性来控制,所以,如果期望使用jdk+ cglib这种方式,可以在yml配置文件中修改这个属性
- 这个规则是在SpringBoot2.0后改的,之前的版本和Spring的效果一致
@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class,
AnnotatedElement.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false)
public static class JdkDynamicAutoProxyConfiguration {
}
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
public static class CglibAutoProxyConfiguration {
}
}
为什么SpringBoot都要将代理默认成cglib的,它比jdk有哪些好处呢?
- 实际上,jdk的代理类只能赋值给接口,不能赋值给具体实现,如果有人注入实现类,那么会导致启动报错,默认cglib避免了这种问题的发生
@Configuration
@ComponentScan
@EnableAspectJAutoProxy(proxyTargetClass = true)
@Slf4j
public class SpringApplicationDemo {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringApplicationDemo.class);
LoginService loginService = context.getBean(LoginService.class); //jdk
PlainLoginService plainLoginService = context.getBean(PlainLoginService.class); // cglib
log.info("jdk class name = {}", loginService.getClass());
log.info("cglib class name = {}", plainLoginService.getClass());
}
//Bean named 'loginServiceImpl' is expected to be of type 'io.spring.action.aop.service.impl.LoginServiceImpl' but was actually of type 'com.sun.proxy.$Proxy21'
// AOP代理对象如果使用jdk代理,只能赋值给接口,不能赋值给实现
// SpringBoot中出于这个考虑,将所有代理对象默认为cglib
@Autowired
private LoginServiceImpl loginService;
// 定义切面,或者直接使用@Component注解
@Bean
public FooAspect fooAspect() {
return new FooAspect();
}
}