現象描述
- 以下的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...