解决ApplicationContext获取不到Bean的问题
开发环境遇到报错
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
体悟:八股文还是有用的~
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
相关文章
关于SpringMVC的数据绑定@InitBinder注解的使用
这篇文章主要介绍了关于SpringMVC的数据绑定@InitBinder注解的使用,在SpringMVC中,数据绑定的工作是由 DataBinder 类完成的,DataBinder可以将HTTP请求中的数据绑定到Java对象中,需要的朋友可以参考下2023-07-07
最新评论