runnable 注入spring service_谈谈 Spring 中的 NoSuchBeanDefinitionException

本文详细介绍了 Spring 中 NoSuchBeanDefinitionException 的原因及解决办法,包括 Bean 注入时找不到定义、多个同类型 Bean 的选择问题、按名称获取 Bean 失败以及代理 Beans 的注入问题。通过对不同场景的分析,提供了相应的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

80708d59ff4527ffdc613911b7b7dbc1.png

概述

org.springframework.beans.factory.NoSuchBeanDefinitionException 是很常见的异常,可以说绝大多数使用过 Spring 的人都曾遇到过它。本文旨在总结下NoSuchBeanDefinitionException(以下简称 NSBDE)的含义,哪些情况下可能抛出 NSBDE,和如何解决(文中配置均用 JavaConfig)。

什么是 NoSuchBeanDefinitionException

从字面其实就很好理解,NoSuchBeanDefinitionException 就是没有找到指定 Bean 的 Definition。NoSuchBeanDefinitionException 的 JavaDoc是这样定义的:

Exception thrown when a BeanFactory is asked for a bean instance for which it cannot find a definition. This may point to a non-existing bean, a non-unique bean, or a manually registered singleton instance without an associated bean definition.

下面看看可能抛出 NSBDE 的一些情况。

情况1: No qualifying bean of type […] found for dependency

最常见的抛出 NSBDE 的情况就是在一个 BeanA 中注入 BeanB 时找不到 BeanB 的定义。例子如下:

@Component
public class BeanA {
    @Autowired
    private BeanB dependency;
    //...
}

当在 BeanA 中注入 BeanB 时,如果在 Spring 上下文中找不到 BeanB 的定义,就会抛出 NSBDE。异常信息如下:

org.springframework.beans.factory.NoSuchBeanDefinitionException: 
No qualifying bean of type [org.baeldung.packageB.BeanB]
  found for dependency: 
    expected at least 1 bean which qualifies as
  autowire candidate for this dependency. 
    Dependency annotations: 
  {@org.springframework.beans.factory.annotation.Autowired(required=true)}

抛异常的原因在异常信息中说的很清楚:expected at least 1 bean which qualifies as autowire candidate for this dependency。所以要么是 BeanB 不存在在 Spring 上下文中(比如没有标注 @ Component,@Repository,@Service, @Controller等注解) ,要么就是 BeanB 所在的包没有被 Spring 扫描到。

解决办法就是先确认 BeanB 有没有被某些注解声明为 Bean:

package org.baeldung.packageB;
@Component
public class BeanB { ...}

如果 BeanB 已经被声明为一个 Bean,就再确认 BeanB 所在的包有没有被扫描。

@Configuration
@ComponentScan("org.baeldung.packageB")
public class ContextWithJavaConfig {
}

情况2: No qualifying bean of type […] is defined

还有一种可能抛出 NSBDE 的情况是在上下文中存在着两个 Bean,比如有一个接口 IBeanB,它有两个实现类 BeanB1 和 BeanB2。

@Component
public class BeanB1 implements IBeanB {
    //
}
@Component
public class BeanB2 implements IBeanB {
    //
}

现在,如果 BeanA 按照下面的方式注入,那么 Spring 将不知道要注入两个实现中的哪一个,就会抛出 NSBDE。

@Component
public class BeanA {
    @Autowired
    private IBeanB dependency;
}

异常信息如下:

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: 
No qualifying bean of type
  [org.baeldung.packageB.IBeanB] is defined: 
    expected single matching bean but found 2: beanB1,beanB2

仔细看异常信息会发现,并不是直接抛出 NSBDE,而是它的子类 NoUniqueBeanDefinitionException,这是 Spring 3.2.1 之后引入的新异常,目的就是为了和第一种找不到 Bean Definition 的情况作区分。

解决办法1就是利用 @Qualifier 注解,明确指定要注入的 Bean 的名字(BeanB2 默认的名字就是 beanB2)。

@Component
public class BeanA {
    @Autowired
    @Qualifier("beanB2")
    private IBeanB dependency;
}

除了指定名字,我们还可以将其中一个 Bean 加上 @Primary的注解,这样会选择加了 Primary 注解的 Bean 来注入,而不会抛异常:

@Component
@Primary
public class BeanB1 implements IBeanB {
    //
}

这样 Spring 就能够知道到底应该注入哪个 Bean 了。

情况3: No Bean Named […] is defined

NSBDE 还可能在从 Spring 上下文中通过名字获取一个 Bean 时抛出。

@Component
public class BeanA implements InitializingBean {
    @Autowired
    private ApplicationContext context;
    @Override
    public void afterPropertiesSet() {
        context.getBean("someBeanName");
    }
}

在这种情况中,如果找不到指定名字 Bean 的 Definition,就会抛出如下异常:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: 
No bean named 'someBeanName' is defined

情况4: 代理 Beans

Spring 通过 AOP 代理 实现了许多高级功能,比如:

  • 通过 @Transactional完成 事务管理
  • 通过 @Cacheable实现缓存
  • 通过 @Async和 @Scheduled实现任务调度和异步执行

Spring 有两种方式实现代理:

  1. 利用 JDK 动态代理机制 ,在运行时为实现了某些接口的类动态创建一个实现了同样接口的代理对象。
  2. 使用 CGLIB,CGLIB 可以在运行期扩展Java类与实现Java接口,也就是说当一个类没有实现接口时,必须用 CGLIB 生成代理对象。

所以,当 Spring 上下文中的一个实现了某个接口的 Bean 通过JDK 动态代理机制被代理时,代理类并不是继承了目标类,而是实现同样的接口。

也正因为如此,如果一个 Bean 通过接口注入时,可以成功被注入。但如果是通过真正的类注入,那么 Spring 将无法找到匹配这个类的 Definition——因为代理类并没有继承这个类。

以 Spring 中比较常见的事务管理为例,假设 ServiceA 中要注入 ServiceB,两个 Service 均标注了 @Transactional注解来进行事务管理,那么下面的注入方式是不会正常 work 的。

@Service
@Transactional
public class ServiceA implements IServiceA{
    @Autowired
    private ServiceB serviceB;
    ...
    }
 
@Service
@Transactional
public class ServiceB implements IServiceB{
}

解决办法就是通过接口来进行注入:

@Service
@Transactional
public class ServiceA implements IServiceA{
    @Autowired
    private IServiceB serviceB;
    }
 
@Service
@Transactional
public class ServiceB implements IServiceB{
}

原作者:Giraffe
原文链接:谈谈 NoSuchBeanDefinitionException
原出处:个人博客
侵删

d95f837902e8635f6923b2f32287b7b1.gif
<think>我们正在解决SpringBoot3.5中自定义线程池导致OpenFeign请求中Token为空的问题。核心在于线程切换导致SecurityContext丢失,因为SecurityContext默认存储在ThreadLocal中。此外,用户还提到找不到类SecurityContextHolder,这通常是因为缺少SpringSecurity依赖。参考引用中的内容:-引用[1]提到@SpringBootApplication是一个组合注解,包含@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan。这提醒我们需要确保SpringBoot应用正确配置。-引用[3]展示了一个SpringSecurity配置示例,它放开了所有权限。这提示我们需要引入SpringSecurity并正确配置,以便使用SecurityContextHolder。解决方案步骤:1.添加SpringSecurity依赖:因为SecurityContextHolder属于SpringSecurity,所以必须添加依赖。2.配置自定义线程池,使用TaskDecorator来传递SecurityContext和RequestContext(因为OpenFeign拦截器可能需要请求上下文)。3.在OpenFeign拦截器中正确获取Token。4.在使用CompletableFuture时,使用自定义的线程池执行异步任务。具体实现:第一步:添加依赖(pom.xml)```xml<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>```第二步:配置自定义线程池(使用TaskDecorator传递上下文)```javaimportorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.core.task.TaskDecorator;importorg.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;importorg.springframework.security.core.context.SecurityContext;importorg.springframework.security.core.context.SecurityContextHolder;importorg.springframework.web.context.request.RequestAttributes;importorg.springframework.web.context.request.RequestContextHolder;@ConfigurationpublicclassThreadPoolConfig{@Bean(name="customThreadPool")publicThreadPoolTaskExecutorcustomThreadPool(){ThreadPoolTaskExecutorexecutor=newThreadPoolTaskExecutor();executor.setCorePoolSize(5);executor.setMaxPoolSize(10);executor.setQueueCapacity(100);executor.setThreadNamePrefix("Custom-Thread-");//设置TaskDecorator用于传递上下文executor.setTaskDecorator(newContextTaskDecorator());executor.initialize();returnexecutor;}staticclassContextTaskDecoratorimplementsTaskDecorator{@OverridepublicRunnabledecorate(Runnablerunnable){//获取当前线程的SecurityContext和RequestAttributesSecurityContextsecurityContext=SecurityContextHolder.getContext();RequestAttributesrequestAttributes=RequestContextHolder.getRequestAttributes();return()->{try{//将父线程的上下文设置到当前线程SecurityContextHolder.setContext(securityContext);RequestContextHolder.setRequestAttributes(requestAttributes);runnable.run();}finally{//清除上下文,防止内存泄漏SecurityContextHolder.clearContext();RequestContextHolder.resetRequestAttributes();}};}}}```第三步:配置OpenFeign拦截器(从SecurityContext中获取Token)```javaimportfeign.RequestInterceptor;importfeign.RequestTemplate;importorg.springframework.security.core.Authentication;importorg.springframework.security.core.context.SecurityContextHolder;importorg.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;importorg.springframework.stereotype.Component;@ComponentpublicclassFeignTokenInterceptorimplementsRequestInterceptor{@Overridepublicvoidapply(RequestTemplatetemplate){//从SecurityContext中获取认证信息Authenticationauthentication=SecurityContextHolder.getContext().getAuthentication();if(authenticationinstanceofJwtAuthenticationToken){JwtAuthenticationTokenjwtToken=(JwtAuthenticationToken)authentication;//获取token字符串StringtokenValue=jwtToken.getToken().getTokenValue();template.header("Authorization","Bearer"+tokenValue);}}}```注意:这里假设使用的是JWT,实际认证对象类型可能不同,请根据项目实际情况调整。第四步:在异步任务中使用自定义线程池```javaimportorg.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;importorg.springframework.stereotype.Service;importjava.util.concurrent.CompletableFuture;@ServicepublicclassMyService{privatefinalThreadPoolTaskExecutorcustomThreadPool;privatefinalSomeFeignClientsomeFeignClient;//你的OpenFeign客户端publicMyService(@Qualifier("customThreadPool")ThreadPoolTaskExecutorcustomThreadPool,SomeFeignClientsomeFeignClient){this.customThreadPool=customThreadPool;this.someFeignClient=someFeignClient;}publicvoidasyncMethod(){CompletableFuture.runAsync(()->{//这里调用OpenFeign客户端someFeignClient.someMethod();},customThreadPool);}}```第五步(可选):如果不需要安全控制,可以配置SecurityPermitAllConfig(如引用[3])来放开所有权限,但注意这仅用于测试或内部服务。在正式环境中应该配置正确的安全规则。```javaimportorg.springframework.context.annotation.Configuration;importorg.springframework.security.config.annotation.web.builders.HttpSecurity;importorg.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;@ConfigurationpublicclassSecurityPermitAllConfigextendsWebSecurityConfigurerAdapter{@Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{http.authorizeRequests().anyRequest().permitAll().and().csrf().disable();}}```注意:在SpringBoot3.5中,如果使用的是SpringSecurity6.x,WebSecurityConfigurerAdapter已被废弃,可以使用以下方式:```javaimportorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.security.config.annotation.web.builders.HttpSecurity;importorg.springframework.security.web.SecurityFilterChain;@ConfigurationpublicclassSecurityConfig{@BeanpublicSecurityFilterChainsecurityFilterChain(HttpSecurityhttp)throwsException{http.authorizeHttpRequests(authorize->authorize.anyRequest().permitAll()).csrf().disable();returnhttp.build();}}```总结:1.添加SpringSecurity依赖,解决找不到SecurityContextHolder的问题。2.自定义线程池时,使用TaskDecorator传递SecurityContext和RequestContext。3.在OpenFeign拦截器中从SecurityContext获取Token。4.异步任务使用自定义线程池执行。这样,在异步线程中调用OpenFeign时,Token就可以正确传递了。**验证方法**:在异步任务中打印SecurityContextHolder.getContext(),查看是否有认证信息。**注意事项**:-确保主线程(触发异步任务的线程)已经设置了SecurityContext(例如,用户已登录)。-在TaskDecorator中一定要在finally块中清除上下文,避免内存泄漏。如果问题仍然存在,请检查:-SpringSecurity配置是否正确。-是否在异步任务中使用了正确的线程池(通过@Qualifier指定)。-OpenFeign客户端是否被正确扫描(使用@EnableFeignClients)。§§相关问题§§1.SpringSecurity6中如何实现跨线程的SecurityContext传播?2.如何在SpringBoot3.5中配置OpenFeign的超时和重试?3.使用ThreadLocal时如何避免内存泄漏?4.如何在SpringBoot3.5中集成OAuth2资源服务器?</think>### 解决 Spring Boot 3.5 中自定义线程池导致 OpenFeign Token 为空问题 #### 问题核心原因 1. **线程上下文丢失** Spring Security 的 `SecurityContext` 存储在 `ThreadLocal` 中,自定义线程池切换线程时,子线程无法继承父线程的安全上下文[^1]。 2. **OpenFeign 依赖上下文** Feign 拦截器从 `SecurityContextHolder` 获取 Token,异步线程中 `SecurityContextHolder.getContext()` 返回空值[^2]。 3. **类找不到问题** `SecurityContextHolder` 属于 Spring Security 包,缺少依赖会导致类找不到错误[^3]。 --- ### 完整解决方案 #### 1. 添加必要依赖(解决类找不到问题) ```xml <!-- pom.xml --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <!-- 提供 SecurityContextHolder --> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> ``` #### 2. 配置上下文感知线程池(核心) ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.task.TaskDecorator; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; @Configuration public class ThreadPoolConfig { @Bean(name = "secureThreadPool") public ThreadPoolTaskExecutor secureThreadPool() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setThreadNamePrefix("secure-pool-"); // 上下文传播装饰器 executor.setTaskDecorator(new ContextPropagatingTaskDecorator()); executor.initialize(); return executor; } static class ContextPropagatingTaskDecorator implements TaskDecorator { @Override public Runnable decorate(Runnable runnable) { // 捕获父线程上下文 SecurityContext securityContext = SecurityContextHolder.getContext(); RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); return () -> { try { // 注入到子线程 SecurityContextHolder.setContext(securityContext); RequestContextHolder.setRequestAttributes(requestAttributes); runnable.run(); } finally { // 清理防止内存泄漏 SecurityContextHolder.clearContext(); RequestContextHolder.resetRequestAttributes(); } }; } } } ``` #### 3. 配置 OpenFeign 拦截器 ```java import feign.RequestInterceptor; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; @Component public class FeignAuthInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth != null && auth.isAuthenticated()) { // 实际根据认证类型获取token (e.g., OAuth2AuthenticationToken) String token = (String) auth.getCredentials(); template.header("Authorization", "Bearer " + token); } } } ``` #### 4. 安全配置(放开权限) ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.web.SecurityFilterChain; @Configuration public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(authorize -> authorize .anyRequest().permitAll() // 根据实际需求配置 ) .csrf().disable(); return http.build(); } } ``` #### 5. 正确使用异步调用 ```java @Service public class OrderService { private final ThreadPoolTaskExecutor secureThreadPool; private final PaymentClient paymentClient; public OrderService( @Qualifier("secureThreadPool") ThreadPoolTaskExecutor secureThreadPool, PaymentClient paymentClient ) { this.secureThreadPool = secureThreadPool; this.paymentClient = paymentClient; } public CompletableFuture<Void> processPaymentAsync() { return CompletableFuture.runAsync(() -> { // 此时能正确获取Token paymentClient.processPayment(); }, secureThreadPool); // 关键:使用自定义线程池 } } ``` --- ### 关键注意事项 1. **线程池类型** - 必须使用 `ThreadPoolTaskExecutor`(支持 `TaskDecorator`) - 禁止使用 `ForkJoinPool` 或原生 `ThreadPoolExecutor` 2. **安全上下文获取** ```java // Spring Security 6.x 推荐方式 SecurityContext context = SecurityContextHolder.getContext(); ``` 3. **内存泄漏防护** - 务必在 `finally` 块调用 `clearContext()` - 使用 `RequestContextHolder.setRequestAttributes(attrs, true)` 启用继承模式 4. **启动类配置** ```java @SpringBootApplication @EnableFeignClients // 启用Feign客户端扫描 public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` --- ### 验证与诊断 ```java CompletableFuture.runAsync(() -> { log.info("SecurityContext: {}", SecurityContextHolder.getContext()); log.info("RequestAttributes: {}", RequestContextHolder.getRequestAttributes()); paymentClient.processPayment(); }, secureThreadPool); ``` **常见问题排查表**: | 现象 | 原因 | 解决方案 | |------|------|----------| | `NoSuchBeanDefinitionException` | 未指定线程池名称 | 使用 `@Qualifier("secureThreadPool")` | | Token 仍为空 | 未正确配置 TaskDecorator | 检查上下文捕获逻辑 | | `ClassNotFound` | 缺少 Spring Security 依赖 | 添加 `spring-boot-starter-security` | | 间歇性上下文丢失 | 线程池未清理上下文 | 在 `finally` 块添加清理逻辑 | > **最佳实践**:在网关层统一处理认证(参考引用[2]的 Zuul 方案),避免业务服务直接处理 Token[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值