本文是自己学习的一个总结
这里写目录标题
- 1、Spring容器中查找Bean的方式
- 1.1、查找单一的bean
- 1.1.1、根据bean名称实时查找bean
- 1.1.2、根据bean类型查找
- 1.1.3、根据ObjectFactory查找bean(延迟查找)
- 1.1.4、延迟查找(ObjectProvider)
- 1.2、查找多个bean
- 1.2.1、根据bean类型查找
- 1.2.1.1、获取同类型bean名称列表
- 1.2.1.2、获取同类型bean的实例列表
- 1.2.2、根据注解查找
- 1.2.2.1、根据注解查找bean名称
- 1.2.2.2、根据注解查找bean实例
- 1.2.3、同时根据名称和注解查找
- 1.3、层次性依赖查找
- 1.3.1、什么是层次性依赖查找
- 1.3.2、HierarchicalBeanFactory
- 1.3.3、设置父容器
1、Spring容器中查找Bean的方式
1.1、查找单一的bean
1.1.1、根据bean名称实时查找bean
getBean(String)这个方法很常见,所以的容器都具有这个方法。容器中可直接通过bean的名称查找bean
User user = (User) applicationContext.getBean("user");
1.1.2、根据bean类型查找
getBean(String)有个重载形式是getBean(Class),这个可以根据类型查找bean。只是要注意,如果容器内没有这种类型的bean,或者容器内这种类型的bean不止一个,那这个方法会报错。
User user = applicationContext.getBean(User.class);
1.1.3、根据ObjectFactory查找bean(延迟查找)
我们可以借助ObjectFactory来间接获取bean。ObjectFactory是一个接口,这个接口定义了工厂查找bean的一个规范。其中有一个getObject()方法,这个方法就是用于获取bean的。
我们在xml中定义ObjectFactory的一个实现类,令targetName指向容器内的一个bean。
<bean id="objectFactory" class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean" p:targetBeanName="user"/>
其中user是容器中已经定义好的一个bean的id。
之后我们可以这样获取user。
ObjectFactory<User> objectFactory = (ObjectFactory<User>) beanFactory.getBean("objectFactory");
User user = objectFactory.getObject();
ObjectFactory之所以是延迟查找,是因为一开始容器中是只有objectFactory这么一个bean,我们真正想要的bean(user)并没有被实例化。一直等到objectFactory.getObject()执行的时候才开始实例化user。
1.1.4、延迟查找(ObjectProvider)
这里的延迟查找是通过ObjectProvider这个接口。ObjectProvider和ObjectFactory有些类似,先生成一个中间对象,这个对象托管者我们真正想要的bean。两者都是很相似,毕竟ObjectProvider是直接继承于ObjectFactory的。
在ApplicationContext中可以通过下面这个接口获取到ObjectProvider。
- ObjectProvider getBeanProvider(Class requiredType);
这个接口是获取到一个ObjectProvider,这个ObjectProvider中托管着容器中类型为requiredType的bean。
得到ObjectProvider之后,我们就可以通过getObject(),getIfUnique()等方法获取到对应的bean。
需要注意的一点是,因为ObjectProvider是通过类型获取的,和其他通过类型获取bean的方法一样,获取的时候要保证该类型的bean在容器中是唯一存在的,或者容器中同一类型的bean有多个,但是都有规定好qualifier或者primary来保证唯一性。
我们看看例子。
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Test.class);
ObjectProvider<String> objectProvider = applicationContext.getBeanProvider(String.class);
System.out.println(objectProvider.getObject());
}
@Bean
public String helloWorld() {
return "Hello world!";
}
}
这段代码可以运行,会输出Hello world!。
但是如果根据类型不能确定唯一的bean,结果可能会达不到我们的预期。比如下面的代码。
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Test.class);
ObjectProvider<String> objectProvider = applicationContext.getBeanProvider(String.class);
System.out.println(objectProvider.getObject());
}
@Bean
public String helloWorld() {
return "Hello world!";
}
@Bean
public String HelloEveryone() {
return "Hello everyOne";
}
}
这段代码运行会报错。
1.2、查找多个bean
查找多个bean需要依赖ListableBeanFactory。从名字上看,Listable,可罗列的可列表化的,就表明这个beanFactory在查找的时候可以应用在集合接口。
1.2.1、根据bean类型查找
1.2.1.1、获取同类型bean名称列表
获取容器内特定类型的所有bean的名称,有个接口可以实现这个效果。
- String[] getBeanNamesForType(Class)
1.2.1.2、获取同类型bean的实例列表
- Map<String, T> getBeansOfType(Class)
我们可以通过getBeansOfType来查找特定类型的多个bean。该方法会查找到容器中类型为指定类型的bean,返回只是一个map,key为bean的id,value为bean实例。
ListableBeanFactory listableBeanFactory = (ListableBeanFactory) beanFactory;
Map<String, User> users = listableBeanFactory.getBeansOfType(User.class);
1.2.2、根据注解查找
与通过类型查找一样,有获取bean名称的,也有获取bean实例的
1.2.2.1、根据注解查找bean名称
我们可以查找容器内所有被某个注解标注的所有的bean的名词,使用getBeanNamesForAnnotation这个方法。这个方法也是在ListableBeanFactory中才有。
1.2.2.2、根据注解查找bean实例
我们可以查找容器内所有被某个注解标注的所有的bean,使用getBeansWithAnnotation这个方法。这个方法也是在ListableBeanFactory中才有。
ListableBeanFactory listableBeanFactory = (ListableBeanFactory) beanFactory;
Map<String, User> users = (Map) listableBeanFactory.getBeansWithAnnotation(Super.class);
users中存储的就是所有被@Super这个注解标注的bean。
1.2.3、同时根据名称和注解查找
- findAnnotationOnBean(String, Class)
1.3、层次性依赖查找
1.3.1、什么是层次性依赖查找
层次性依赖查找,也就是双亲委派。这个双亲委派的意义和类加载机制中的双亲委派一样——简单来说就是当一个类加载器准备加载一个类时,会将这个加载任务交给它的父类加载器。如果这个父类加载器还有父类,那它也会将这个任务交给它的父类去做,就这样不断递归······直到任务委派到根父类,这时候如果父类无法完成加载任务,那子类才会去尝试加载。
容器中也有父子关系。双亲委派查找就是子类会优先将查找任务交给父类容器,父类容器找不到对应bean后,子类容器才会去查找。
1.3.2、HierarchicalBeanFactory
首先我们看看HierarchicalBeanFactory这个接口。它继承BeanFactory,有两个方法。
第一个getParentBeanFactory()很明显是用来获取父类容器的。
第二个containsLocalBean(String name)是用来查看当前容器中是否包含名称为name的bean。localBean的意思就是当前容器中的bean,不包括父类容器的bean。注释中也说明了只在当前容器中查找,忽略父类容器中的bean。
有一点可以放心,所有的容器实现类都直接或者间接继承了HierarchicalBeanFactory。比如ApplicationContext就是直接继承了这个接口,那可以推断,所有的ApplicationContext都支持上面的两个方法。
1.3.3、设置父容器
设置父容器的方法是setParentBeanFactory(BeanFactory parentBeanFactory)。要注意这个方法是在ConfigurableBeanFactory中定义的,只有其继承者才有这个方法来设置父容器。
ApplicationContext没有继承ConfigurableBeanFactory这个接口,所以ApplicationContext和它的实现类都不能设置父容器。
这里就会有疑问了,既然ApplicationContext都不支持设置父容器,那它继承HierarchicalBeanFactory干嘛?
其实ApplicationContext是支持父容器的,并且也是通过ConfigurableBeanFactory定义的setParentBeanFactory来设置。要理解这句话那就要先了解ApplicationContext和BeanFactory之间的关系。
在这篇文章的开头介绍了ApplicationContext和BeanFactory之间的关系。
表面上看,ApplicationContext是继承BeanFactory,但实际上两者的关系是代理模式。ApplicationContext需要实现容器的基本功能时,就会拉出其内部持有的BeanFactory类型的成员变量,让这个beanFactory来实现那些基本的容器功能,然后ApplicationContext再稍微润色一下。
代理模式看这篇文章:
真正具有容器层次性的是BeanFactory而不是ApplicationContext。当我们说给ApplicationContext设置父容器时,是指给ApplicationContext内部持有的BeanFactory成员变量设置父容器。
下面代码分别展示了给ApplicationContext和BeanFactory设置父容器
//给BeanFactory设置父容器
BeanFactory parentBeanFactory = new XmlBeanFactory(resource1);
DefaultListableBeanFactory sonBeanFactory = new XmlBeanFactory(resource2);
//设置好之后sonBeanFactory就可以查找到parentBeanFactory中的bean
sonBeanFactory.setParentBeanFactory(parentBeanFactory);
//给ApplicationContext设置父容器
ConfigurableApplicationContext sonApplicationContext = new ClassPathXmlApplicationContext("路径");
//设置好之后sonApplicationContext就可以查找到parentBeanFactory中的bean
sonApplicationContext.getBeanFactory().setParentBeanFactory(parentBeanFactory);