现象描述
- 以下的SpringBoot 工程在启动的时候会启动失败。(SpringBoot 1.5.7-RELEASE)
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@EnableTransactionManagement
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
package com.example.demo.controller;
import com.example.demo.service.DemoServiceImpl;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DemoController {
@Resource
private DemoServiceImpl demoService;
@RequestMapping("/test")
public String test() {
return demoService.demo();
}
}
package com.example.demo.service;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service("demoService")
public class DemoServiceImpl implements DemoService{
@Override
@Transactional
public String demo() {
return "DemoServiceImpl";
}
}
package com.example.demo.service;
public interface DemoService {
String demo();
}
报错信息
***************************
APPLICATION FAILED TO START
***************************
Description:
The bean 'demoService' could not be injected as a 'com.example.demo.service.DemoServiceImpl' because it is a JDK dynamic proxy that implements:
com.example.demo.service.DemoService
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.
如果只是解决
- 如果不去深究原因,只是需要解决这个异常。那么有以下几种方式。只需完成其中任意一个即可。
1. 修改EnableTransactionManagement注解参数
DemoApplication 上的注解 @EnableTransactionManagement 变成 @EnableTransactionManagement(proxyTargetClass = false)
2. 在application.properties配置文件中增加配置
spring.aop.proxy-target-class=true
3. 注入bean 的时候变更为使用其接口(最推荐)
@Resource
private DemoServiceImpl demoService;
改成:
@Resource
private DemoService demoService;
4. 升级 SpringBoot 版本至 2.0.0 以上
- 这个其实不是很推荐,因为改的可能很多很多。但是因为后续会提到SpringBoot 的一些默认设置,所以这里还是写出来了。
原因分析
一些前置知识
- AOP的动态代理有 JDK dynamic proxy 和 CGLIB 两种。
- JDK dynamic proxy 要求代理的类至少实现一个接口。CGLIB 不用。
问题的分析
- 当我们使用
@Transactional
修饰了DemoServiceImpl
类中的方法时,Spring会创建一个代理。而且在我们的SpringBoot版本(1.5.7)中, 默认的是jdk proxy,所以Bean 的真正的type 是DemoServiceImpl
的接口类型DemoService
,所以当我们使用
@Resource
private DemoServiceImpl demoService;
来注入的时候就无法找到 DemoServiceImpl
类型的bean。
解决方案的原理
1. 修改EnableTransactionManagement注解参数
- 这种方式将动态代理的方法强制指定为CGLIB,所以避免了只有接口类型bean 的问题。
2. 在application.properties配置文件中增加配置
- 同上。
3. 注入bean 的时候变更为使用其接口
- 这种方式
@Resource
的时候用的也是接口的类型,所以可以正常的注入bean,也比较推荐这种方式来解决问题。如果interface存在多个实现类,那么可以考虑使用注解中的name参数来进行控制。
4. 升级 SpringBoot 版本至 2.0.0 以上
- 这个其实大部分人应该都不会这么做,这里列出来主要是为了说明,在SpringBoot 2.0.0 之后,动态代理由默认的jdk proxy变成了 CGLIB ,所以升级SpringBoot 版本可以解决这个问题。
参考的资料
- Stack Overflow 上的回答:https://stackoverflow.com/que...
- 另外一篇文章比较详尽的测试:https://blog.csdn.net/weixin_...
- JDK 代理和CGLIB 的区别:https://www.cnblogs.com/doubl...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。