开发环境遇到报错:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'XXXXX' available
现将原问题代码简化抽出分析。
部署环境:
springboot版本:1.x
java:1.8
其他:略
问题代码:
service层
接口:
public interface ErrorBeanService {
void transactionalMethod();
void callMethod();
}
实现类:
@Service("errorBeanServiceImpl")
public class ErrorBeanServiceImpl implements ErrorBeanService{
private ApplicationContext applicationContext;
@Autowired
public void setApplicationContext(ApplicationContext applicationContext){
this.applicationContext = applicationContext;
}
@Transactional(rollbackFor = Exception.class)
public void transactionalMethod() {
System.out.println("transactionalMethod run ~ ");
}
public void callMethod() {
// Transactional注解失效
// transactionalMethod();
// 使用applicationContext获取到bean 使事物注解生效
applicationContext.getBean(ErrorBeanServiceImpl.class).transactionalMethod(); // 报错行
}
}
contro层:
@RestController
@RequestMapping("/error")
public class ErrorController {
@Autowired
private ErrorBeanService errorBeanService;
@RequestMapping(value = "/errorBean", method = RequestMethod.GET)
public void errorBean() {
errorBeanService.callMethod();
}
}
查看报错行代码,使用了applicationContext.getBean的方式获取bean使得内部调用事务注解生效,但在获取bean时报错NoSuchBeanDefinitionException异常,报错如下:
排除了包扫描和beanName问题,看上述报错倒数第四行报错:
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
由于使用了事物注解(AOP)从而使用了动态代理,该环境使用的是JDK的动态代理机制,那么代理不会继承而是使用实现接口。所以这个bean是通过接口注入的,就无法通过接口的实例与bean的实例关联。
ps:
- Springboot 1.x AOP默认还是使用 JDK 动态代理的
- 如果目标对象实现了接口默认采用JDK动态代理 (可强制改为CGLIB)
- 如果目标对象没有实现接口默认采用CGLIB动态代理
解决方案:
- 升级SpringBoot版本为2.x,AOP默认使用CGLIB实现。
- properties配置文件增加:spring.aop.proxy-target-class=true ## 强制使用CGLIB代理
- 使用applicationContext.getBean的重载方法获取对应的bean
修复代码
为了探求transactional注解是否生效,现增加AOP 切面类模拟事物。
@Aspect
@Configuration
public class AopConfig {
/**
* 增强transactionalMethod方法 模拟transational注解
*/
@Pointcut("execution(* com.example.springbootdemo.error.test.ErrorBeanServiceImpl.transactionalMethod())")
public void executeAdvice() {
}
/**
* 环绕增强
*/
@Around("executeAdvice()")
public Object aroundAdvice(ProceedingJoinPoint thisJoinPoint) throws Throwable {
System.out.println("aroundAdvice before proceed");
Object obj = thisJoinPoint.proceed();
System.out.println("aroundAdvice after proceed");
return obj;
}
}
去除事务注解:
@Service("errorBeanServiceImpl")
public class ErrorBeanServiceImpl implements ErrorBeanService{
private ApplicationContext applicationContext;
@Autowired
public void setApplicationContext(ApplicationContext applicationContext){
this.applicationContext = applicationContext;
}
public void transactionalMethod() {
System.out.println("transactionalMethod run ~ ");
}
public void callMethod() {
applicationContext.getBean(ErrorBeanServiceImpl.class).transactionalMethod();
}
}
- 升级SpringBoot版本为2.x 略
- 配置配置文件 略
- 修改callMethod方法使用getBean的重载方法
<T> T getBean(String var1, Class<T> var2) throws BeansException;
代码如下:
public void callMethod() {
// applicationContext.getBean(ErrorBeanServiceImpl.class).transactionalMethod();
applicationContext.getBean("errorBeanServiceImpl", ErrorBeanService.class).transactionalMethod();
}
上述三种方式最终输出结果:
aroundAdvice before proceed
transactionalMethod run ~
aroundAdvice after proceed
体悟:八股文还是有用的~