Spring学习笔记:Spring容器中查找Bean的方式

本文是自己学习的一个总结


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之间的关系。
https://blog.csdn.net/sinat_38393872/article/details/105918728

在这里插入图片描述

表面上看,ApplicationContext是继承BeanFactory,但实际上两者的关系是代理模式。ApplicationContext需要实现容器的基本功能时,就会拉出其内部持有的BeanFactory类型的成员变量,让这个beanFactory来实现那些基本的容器功能,然后ApplicationContext再稍微润色一下。

代理模式看这篇文章:https://blog.csdn.net/sinat_38393872/article/details/106316008

真正具有容器层次性的是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);



猜你喜欢

转载自blog.csdn.net/sinat_38393872/article/details/107143853