调用getBean获取@Component Bean时触发NoSuchBeanDefinitionException问题
问题描述
调用SpringContextUtil.getBean(TestJob.class)时抛出NoSuchBeanDefinitionException,异常提示找不到TestJob类型的bean。相关代码如下:
自定义Bean
@Slf4j @Component public class TestJob implements Job {}
配置类
@Bean public SpringContextUtil springContextUtil() { return new SpringContextUtil(); }
Spring上下文工具类
@Component public class SpringContextUtil implements ApplicationContextAware { private static ApplicationContext context; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { context = applicationContext; } public static <T> T getBean(Class<T> clazz) throws BeansException { return context.getBean(clazz); } }
主启动类
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
异常栈:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.xxx.yyy.dayu.service.job.PeentoTaskTestJob' available at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351) ~[spring-beans-5.3.12.jar:5.3.12] at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342) ~[spring-beans-5.3.12.jar:5.3.12] at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1172) ~[spring-context-5.3.12.jar:5.3.12] at com.xxx.yyy.peento.task.util.SpringContextUtil.getBean(SpringContextUtil.java:31) ~[yaochi-peento-task-2022.08.03-SNAPSHOT.jar:?] at com.xxx.yyy.peento.task.JobConfigListener.tryPutJobBean(JobConfigListener.java:180) ~[yaochi-peento-task-2022.08.03-SNAPSHOT.jar:?] at com.xxx.yyy.peento.task.JobConfigListener.writeJobs(JobConfigListener.java:151) ~[yaochi-peento-task-2022.08.03-SNAPSHOT.jar:?] at com.xxx.yyy.peento.task.JobConfigListener.watchConfig(JobConfigListener.java:91) ~[yaochi-peento-task-2022.08.03-SNAPSHOT.jar:?] at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [?:1.8.0_291] at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308) [?:1.8.0_291] at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180) [?:1.8.0_291] at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294) [?:1.8.0_291] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_291] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_291] at java.lang.Thread.run(Thread.java:748) [?:1.8.0_291]
调试发现Spring通过动态代理生成bean,导致直接用TestJob.class无法匹配到容器中的代理类实例。
问题原因
TestJob实现了Job接口,Spring默认使用JDK动态代理生成代理类(仅实现目标接口,而非继承目标类)。此时容器中实际存在的bean类型是代理类(如com.sun.proxy.$ProxyXXX),而非TestJob本身,因此调用getBean(TestJob.class)时无法找到匹配的bean。
解决方案
方案1:通过接口类型获取并筛选
利用代理类实现Job接口的特性,先获取所有Job类型的bean,再筛选出TestJob实例:
// 给工具类扩展获取同类型所有bean的方法 public static <T> Map<String, T> getBeansOfType(Class<T> clazz) throws BeansException { return context.getBeansOfType(clazz); } // 调用示例 Map<String, Job> jobMap = SpringContextUtil.getBeansOfType(Job.class); TestJob testJob = jobMap.values().stream() .filter(job -> job instanceof TestJob) .map(job -> (TestJob) job) .findFirst() .orElseThrow(() -> new NoSuchBeanDefinitionException(TestJob.class));
方案2:强制使用CGLIB代理
让Spring生成目标类的子类代理,这样代理类属于TestJob的子类,可直接通过TestJob.class匹配:
- 在启动类或配置类添加注解:
@SpringBootApplication @EnableAspectJAutoProxy(proxyTargetClass = true) // 强制CGLIB代理 public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } - 或者在
application.properties中配置:spring.aop.proxy-target-class=true
方案3:扩展工具类方法兼容代理类型
修改SpringContextUtil,通过Spring的AOP工具类获取代理的真实目标类,实现按目标类查找bean:
import org.springframework.aop.framework.AopProxyUtils; @Component public class SpringContextUtil implements ApplicationContextAware { private static ApplicationContext context; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { context = applicationContext; } public static <T> T getBean(Class<T> clazz) throws BeansException { // 先尝试直接查找 try { return context.getBean(clazz); } catch (NoSuchBeanDefinitionException e) { // 遍历所有bean,检查代理的真实目标类是否匹配 for (String beanName : context.getBeanDefinitionNames()) { Object bean = context.getBean(beanName); Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean); if (clazz.isAssignableFrom(targetClass)) { return clazz.cast(bean); } } throw e; } } }
内容的提问来源于stack exchange,提问作者SN.Tao
相关产品推荐
相关产品推荐

