1. spring容器查找bean存在的问题?
spring容器中对外暴露一个 getBean(Class var1) throws BeansException;方法,意味着根据传入的指定类型,在IOC容器中获取该指定类型的bean对象。
实际上这个获取过程要求,当前传入的类型和被获取的对象必须满足isAssignableFrom关系,即传入的类型必须是目标对象的类型或者是其父类型。
因此,当我们传入的类型在整个Spring容器中存在多个子对象时,此时通过该方式获取对象将失败。
观察如下案例.
package test;
// 定义一个接口,同时定义两个实现类
public class NormalBean {
public interface IService{}
public static class ServiceA implements IService{}
public static class ServiceB implements IService{}
}
spring的相关配置文件如下:
<bean id="serviceA" class="test.NormalBean.ServiceA"/>
<bean id="serviceB" class="test.NormalBean.ServiceB"/>
来个用例获取上面定义的bean对象:
package test;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
@Test
public void testDependOn() {
String beanXml = "classpath:/test/applicationContext.xml";
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(beanXml);
// 获取容器中符合 NormalBean.IService.class.isAssignableFrom(xxx);关系的目标bean xxx。
NormalBean.IService service = context.getBean(NormalBean.IService.class);
System.out.println(service);
}
}
上面代码是从IOC容器中获取类型为NormalBean.IService类型的bean对象。
测试结果如下:
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [test.NormalBean$IService] is defined: expected single matching bean but found 2: serviceB,serviceA
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:366)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:332)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1066)
at test.Main.testDependOn(Main.java:13)
从上面异常信息可以知道,spring容器中定义了2个bean,分别是serviceA和serviceB,这两个bean对象都实现了IService接口,而用例中我们要获取IService接口对应的bean,此时容器中有2个候选者,serviceA和serviceB都满要求。
因此,spring此时不知道该如何选择,到底是该返回serviceA实例还是返回serviceB实例呢?spring也一脸懵逼,所以就抛出异常了。
我们再看一个通过setter方法自动注入属性的案例:
package test;
public class NormalBean {
public interface IService{}
public static class ServiceA implements IService{}
public static class ServiceB implements IService{}
private IService service;
public void setService(IService service){
this.service = service;
}
}
xml如下:
<bean id="serviceA" class="test.NormalBean.ServiceA"/>
<bean id="serviceB" class="test.NormalBean.ServiceB"/>
<bean id="setBean" class="test.NormalBean" autowire="byType"/>
上面配置文件中,setBean对象设置了byType,即按照类型自动注入依赖,spring会调用NormalBean的setService方法,在容器中查找和该方法参数满足isAssignableFrom关系的bean对象,然后将该bean对象通过setService方法注入进去。
执行测试用例,结果如下:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'setBean' defined in class path resource [test/applicationContext.xml]: Unsatisfied dependency expressed through bean property 'service': No qualifying bean of type [test.NormalBean$IService] is defined: expected single matching bean but found 2: serviceA,serviceB; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [test.NormalBean$IService] is defined: expected single matching bean but found 2: serviceA,serviceB
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireByType(AbstractAutowireCapableBeanFactory.java:1307)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1199)
按照需要的类型去容器中查找匹配的bean对象,期望只有一个匹配的,实际上却找到了2个匹配的,不知道如何选择,所以报错了。
当我们希望从容器中获取一个bean对象时,容器中却找到了多个符合条件的bean,此时spring就开始懵逼,不知道如何选择了,就会报上述异常。再详细扩展一下知识点。
spring容器中根据类型获取bean实例,它根据的是接口的类型还是具体实现类的类型呢?
在 Spring 容器中,根据类型获取 Bean 实例时,可以根据接口类型或具体实现类类型来获取。以下是一些具体的场景和示例,说明如何通过不同类型获取 Bean 实例。
根据接口类型获取 Bean
如果一个 Bean 实现了某个接口,可以通过接口类型获取该 Bean 实例。这种方式在解耦和面向接口编程中非常常见。
示例: 假设有一个接口 MyService
和它的实现类 MyServiceImpl
:
public interface MyService {
void doSomething();
}
@Service
public class MyServiceImpl implements MyService {
@Override
public void doSomething() {
System.out.println("Doing something...");
}
}
通过接口类型获取 Bean:
@Autowired
private MyService myService; // 注入的是 MyServiceImpl 的实例
根据具体实现类类型获取 Bean
在某些情况下,您可能需要获取某个具体实现类的 Bean 实例。这种方式更具体,有时用于测试或在需要具体实现行为的情况下。
示例: 继续使用上面的 MyService
和 MyServiceImpl
:
通过具体实现类类型获取 Bean:
@Autowired
private MyServiceImpl myServiceImpl; // 注入的是 MyServiceImpl 的实例
使用 ApplicationContext 动态获取 Bean
有时,您需要在运行时根据类型动态获取 Bean 实例,这可以通过 ApplicationContext
来实现:
示例:
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// 根据接口类型获取 Bean
MyService myService = context.getBean(MyService.class);
myService.doSomething();
// 根据具体实现类类型获取 Bean
MyServiceImpl myServiceImpl = context.getBean(MyServiceImpl.class);
myServiceImpl.doSomething();
}
}
在以上示例中,无论是通过接口类型还是具体实现类类型,都可以获取到 MyServiceImpl
的实例并调用其方法。
总结
- 接口类型:通过接口类型获取 Bean 更加通用,适用于面向接口编程。
- 具体实现类类型:通过具体实现类类型获取 Bean 适用于需要具体实现的行为场景。
Spring 容器能够处理这两种情况,您可以根据实际需求选择合适的方式获取 Bean 实例。
@ConditionalOnBean中的class,指的是接口的类型,还是具体实现类的类型呢?
@ConditionalOnBean
注解可以用来在 Spring 应用上下文中条件性地加载一个 Bean。当使用 @ConditionalOnBean
注解时,您可以通过 value
属性指定一个或多个类型,这些类型可以是接口或具体的实现类。
具体来说,@ConditionalOnBean
注解的 value
属性可以是接口的类型或具体实现类的类型。只要 Spring 应用上下文中存在该类型的 Bean,该条件就会满足,标注了 @ConditionalOnBean
的配置类或 Bean 就会被加载。
下面是一些示例:
使用接口类型
@Configuration
@ConditionalOnBean(MyService.class)
public class MyServiceConfig {
// 只有当 MyService 的 Bean 存在时,这个配置类才会被加载
}
使用具体实现类类型
@Configuration
@ConditionalOnBean(MyServiceImpl.class)
public class MyServiceConfig {
// 只有当 MyServiceImpl 的 Bean 存在时,这个配置类才会被加载
}
在上述两个示例中,只要应用上下文中存在 MyService
或 MyServiceImpl
类型的 Bean,MyServiceConfig
配置类就会被加载。
如果您想确保某个接口的任意实现类存在时加载配置,通常使用接口类型更加灵活和通用。如果您只想在特定实现类存在时加载配置,则可以使用具体实现类的类型。
2. 解决容器中匹配多个bean对象的问题
如何解决上述异常呢?
spring中可以通过bean元素的primary属性来解决这个问题,使用这个属性来指定当前bean为主要候选者,当容器查询一个bean的时候,如果容器中有多个候选者匹配的话,此时spring会返回主要候选者。我们来使用primary来解决上述问题:
package test;
public class NormalBean {
public interface IService{}
public static class ServiceA implements IService{}
public static class ServiceB implements IService{}
private IService service;
public void setService(IService service){
this.service = service;
}
}
spring配置文件如下:
<bean id="serviceA" class="test.NormalBean.ServiceA" primary="true"/>
<bean id="serviceB" class="test.NormalBean.ServiceB"/>
<bean id="setBean" class="test.NormalBean" autowire="byType"/>
测试结果如下:
test.NormalBean$ServiceA@5113ff65
分析上述过程:
从容器中查找IService类型匹配的bean,这个接口存在两个实现类,ServiceA和ServiceB,这两个实现类都交给spring容器进行管理了,但是serviceA被设置作为主要的候选者bean,所以从容器中获取IService类型的bean对象会返回serviceA。
3. 总结
- 当从容器中查找一个bean对象时,如果容器中出现多个bean候选者时,可以通过primary=”true”将当前bean设置为首选者,这样在查找时就会返回该主要候选者,否则将抛出异常。
- 如果候选者bean中有多个bean都被指定为primary的话,那么查找这些bean对象时同样会报错。
文档信息
- 本文作者:Marshall