事务 proxyTargetClass不生效的解决问题
一个项目中,避免不了使用事务,而在Springboot项目中,我们一般使用@Transactional注解来设置事务控制,@Transactional的详情使用可见博客https://www.cnblogs.com/pengpengdeyuan/p/12737891.html。
在加完@Transactional注解启动项目时,可能会出现以下报错
Description:
The bean 'testService' could not be injected as a 'com.xxx.xxx.service.impl.TestService' because it is a JDK dynamic proxy that implements:
com.pk.kxl.service.ITestService
Action:
Consider injecting the bean as one of its interfaces or forcing the use of CGLib-based proxies by setting proxyTargetClass=true on @EnableAsync and/or @EnableCaching
这类情况主要是jdk自动代理与CGlib代理的注入方式差异造成的。
一、查看是否开启事务支持
首先查看项目是否是SpringBoot项目,SpringBoot项目会自动开启事务支持。
二、查看注入方式
从错误信息中可以看出,它是在我的Controller层中,注入testService这个Bean时失败,(失败的原因就是我导入的是接口实现,而springboot的事务默认是使用jdk的动态代理,是基于接口进行注入的)。意思就是我注入的是service层的实现类,这种操作是非法的,改成注入service层接口就可以解决。
这时可以想到,既然jdk动态代理不行,那我们就改成CGlib动态代理(基于类,即设置proxyTargetClass=True在启动事务管理上@EnableTransactionManagement(proxyTargetClass=True)),此时需要引入相应的cglib库的jar包,在springboot中已经集成了。但在spring3.2之前是需要引入的。
@SpringBootApplication
//启用事务管理(可省略)
@EnableTransactionManagement(proxyTargetClass=True)
第三种方式就可以使用@Scop注解去解决
第三种方式就可以使用@Scop注解去解决,他会将所有方法上的事务都强制改为是CGLib方式代理的。
方法:在service层上添加一个注解
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
@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什么东西会修改默认行为呢?很容易想到自动装配
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();
}
}