创建原型作用域Bean引发UnsatisfiedDependencyException的解决咨询
问题场景
定义了一个原型作用域的Bean生成方法:
@Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public A createScopedBeans(String s) { return new A(s); }
该方法每次调用会生成新的A实例。同时定义了Manager的Bean:
@Bean public Manager manager(List<A> list) { return new Manager(list); }
原本期望Spring自动收集所有A类型Bean来创建Manager,但触发了UnsatisfiedDependencyException,完整异常信息如下:
2023-04-19 15:09:31.100 [main] [WARN ] o.s.c.a.AnnotationConfigApplicationContext - 上下文初始化期间遇到异常 - 取消刷新尝试: org.springframework.beans.factory.UnsatisfiedDependencyException: 创建名称为'manager'的Bean时出错,定义在xxx.xxx: 通过方法'manager'的参数0表达的不满足依赖;嵌套异常是org.springframework.beans.factory.UnsatisfiedDependencyException: 创建名称为'createScopedBeans'的Bean时出错,定义在yyy.yyy: 通过方法'createScopedBeans'的参数0表达的不满足依赖;嵌套异常是org.springframework.beans.factory.NoSuchBeanDefinitionException: 没有找到符合条件的java.lang.String类型Bean: 期望至少有1个符合自动装配候选的Bean。依赖注解: {}
错误原因
并非Spring在寻找单例的A Bean,核心问题是:
Spring尝试自动收集所有A类型Bean时,会触发createScopedBeans这个Bean方法的实例化,但该方法需要传入一个String类型参数,而Spring容器中没有对应的String类型Bean可以自动注入。因为Spring的@Bean方法如果带有参数,默认会尝试从容器中自动装配这些参数,此处没有为String参数指定来源,导致依赖缺失。
解决方法
根据不同的使用场景,可选择以下方案:
场景1:手动创建A实例,无需Spring管理
如果A实例是通过业务代码手动传入参数创建,不需要Spring自动维护这些Bean,直接去掉@Bean和@Scope注解,将方法改为普通工厂方法:
// 改为普通方法,不再作为Spring Bean定义 public A createScopedBeans(String s) { return new A(s); }
之后在Manager的Bean定义中手动收集所需的A实例:
@Bean public Manager manager() { List<A> aList = new ArrayList<>(); aList.add(createScopedBeans("param1")); aList.add(createScopedBeans("param2")); // 根据需求添加更多实例 return new Manager(aList); }
场景2:需要Spring管理多个不同参数的A原型Bean
如果需要Spring自动管理多个带不同String参数的A原型Bean,可通过以下方式实现:
方式一:为每个参数单独定义A Bean
为每个需要的参数值单独定义一个A类型的Bean,Spring会自动收集所有A类型Bean注入到Manager的List中:
@Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public A a1() { return new A("param1"); } @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public A a2() { return new A("param2"); } // 可添加更多不同参数的A Bean定义 @Bean public Manager manager(List<A> list) { return new Manager(list); }
方式二:通过@Value指定参数来源(单参数场景)
如果A的String参数是固定值或可通过配置文件获取,给Bean方法的参数添加@Value注解指定参数来源:
@Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public A createScopedBeans(@Value("${custom.a.param:defaultValue}") String s) { return new A(s); }
这种方式仅适用于只需要一种参数的A实例场景,多参数场景优先选择方式一。
场景3:运行时动态创建带参数的A原型Bean
如果Manager需要在运行时动态创建不同参数的A实例,可注入BeanFactory手动获取实例:
@Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public A createScopedBeans(String s) { return new A(s); } @Bean public Manager manager(BeanFactory beanFactory) { List<A> aList = new ArrayList<>(); // 手动传入参数获取原型Bean实例 aList.add(beanFactory.getBean(A.class, "param1")); aList.add(beanFactory.getBean(A.class, "param2")); return new Manager(aList); }
内容的提问来源于stack exchange,提问作者CS1999

