你真的了解Spring的依赖查找吗?
创始人
2024-02-05 13:35:36
0

1.写在前面

前面的博客我们介绍了Spring的总览,今天我们来了解下Spring的依赖查找的相关的内容,我们会从它的前世今生来带你了解下,以及各种类型的查找的方式,同时介绍对应的相对比较安全的查找的方式。以及会介绍一些比较常见的面试题,供大家参考。

2.依赖查找的今世前生

那么提到依赖查找,很多人第一个想到的就是Spring中的依赖查找,但是在Spring出来之前,Java中其实已经就有了依赖查找的API了,具体的如下,同时Spring也参考其中的一部分。

单一类型依赖查找

  • JNDI - javax.naming.Context#lookup(javax.naming.Name)
  • JavaBeans - java.beans.beancontext.BeanContext

集合类型依赖查找

  • java.beans.beancontext.BeanContext

层次性依赖查找

  • java.beans.beancontext.BeanContext

单一类型的依赖查找

那么我这儿先带大家看下对应的源码,后面再带着大家写几个例子,首先我们先看对应的Java中的Context类,具体的如下:

在这里插入图片描述

上面的两个方法都是javax.naming.Context提供的Bean的查找的方法。

接下来我们继续看一下BeanContext,这个BeanContextSpring参考对应的实现的方式。具体的内容如下:

在这里插入图片描述

可以看到这接口继承的了Collection接口,我们可以知道这个接口下面所有的Bean都是存在这个集合下面的,对这个集合进行增删改查。

集合类型依赖查找

集合类型查找是根据一个key去查找出来一个集合的类型。

在这里插入图片描述

对应的方法如上图所示,返回的是一个object对象,也就是集合的对象也是可以返回的

层次性依赖查找BeanContext的源码写的比较混乱,这儿我们不做介绍了。

3.单一类型依赖查找

单一类型依赖查找接口 - BeanFactory

  • 根据 Bean 名称查找
    • getBean(String)
    • Spring 2.5 覆盖默认参数: getBean(String,Object…)
  • 根据 Bean 类型查找
    • Bean 实时查找
      • Spring 3.0 getBean(Class)
      • Spring 4.1 覆盖默认参数: getBean(Class,Object…)
    • Spring 5.1 Bean 延迟查找
      • getBeanProvider(Class)
      • getBeanProvider(ResolvableType)
  • 根据 Bean 名称 + 类型查找: getBean(String,Class)

上面的有些的查找的方式我们前面的博客已经介绍过了,上面的覆盖默认参数的方式,我们只要知道有这个方法就行了,不需要做过多的了解,因为这个方法比较危险,它会覆盖默认的参数。

在这里插入图片描述

在这里插入图片描述

我们看一下对应的5.1的延迟查找的API,具体的如下:

在这里插入图片描述

这儿我们可以看到返回的是ObjectProvider,我们点开这个实现类的内容,具体的如下:

在这里插入图片描述

可以发现这个类是继承了ObjectFactory类的,那么一切都那么好理解了。

我们写了一个简单的例子就行,具体的代码如下:

package org.learn.spring.dependency.lookup;import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;// 通过 ObjectProvider 进行依赖查找
public class ObjectProviderDemo {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();// 将当前类ObjectProviderDemo 作为配置类(configuration class)applicationContext.register(ObjectProviderDemo.class);// 启动应用上下文applicationContext.refresh();lookupByObjectProvider(applicationContext);applicationContext.close();}private static void lookupByObjectProvider(AnnotationConfigApplicationContext applicationContext) {ObjectProvider objectProvider = applicationContext.getBeanProvider(String.class);System.out.println(objectProvider.getObject());}// 方法的名称就是Bean的名称, Bean的名称helloWorld@Beanpublic String helloWorld(){return "Hello World";}
}

还有一个getBeanProvider(ResolvableType)这个方法我们后面再说,ResolvableType这个类型主要是用来处理泛型的。等到后面我们介绍泛型的时候,我们再来详细的介绍。

4.集合类型依赖查找

集合类型依赖查找接口 - ListableBeanFactory

  • 根据 Bean 类型查找
  • 获取同类型 Bean 名称列表
    • getBeanNamesForType(Class)
    • Spring 4.2 getBeanNamesForType(ResolvableType)
  • 获取同类型 Bean 实例列表
    • getBeansOfType(Class) 以及重载方法
  • 通过注解类型查找
    • Spring 3.0 获取标注类型 Bean 名称列表
      • getBeanNamesForAnnotation(Class)
    • Spring 3.0 获取标注类型 Bean 实例列表
      • getBeansWithAnnotation(Class)
    • Spring 3.0 获取指定名称 + 标注类型 Bean 实例
      • findAnnotationOnBean(String,Class)

上面的API我前面都有或多或少的介绍过,我这儿不做赘述了,从上面的API我们可以得到一个结论就是ListableBeanFactory主要用来查找集合的。而方式有两种一种是通过Bean的类型查找,一种是通过注解的类型去查找。集合的列表主要有两种情况,一种查询Bean的名称的集合,一种是查询Bean的实例的集合。

5.层次性依赖查找

层次性依赖查找接口 - HierarchicalBeanFactory 将单一类型的查找和集合类型查找合并到一起去

  • 双亲 BeanFactory: getParentBeanFactory()
  • 层次性查找
    • 根据 Bean 名称查找
      • 基于 containsLocalBean 方法实现
    • 根据 Bean 类型查找实例列表
      • 单一类型: BeanFactoryUtils#beanOfType
      • 集合类型: BeanFactoryUtils#beansOfTypeIncludingAncestors
    • 根据 Java 注解查找名称列表
      • BeanFactoryUtils#beanNamesForTypeIncludingAncestors

具体的代码如下:

package org.learn.spring.dependency.lookup;import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;// HierarchicalBeanFactory 层次查找依赖实例
public class HierarchicalDependencyLookup {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();// 将当前类ObjectProviderDemo 作为配置类(configuration class)applicationContext.register(ObjectProviderDemo.class);// 1.获取HierarchicalBeanFactory <- ConfigurableBeanFactory <- ConfigurableListableBeanFactoryConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();System.out.println("当前BeanFactory 的 Parent BeanFactory:" + beanFactory.getParentBeanFactory());// 2.设置ParentBeanFactoryHierarchicalBeanFactory parentBeanFactory = createParentBeanFactory();beanFactory.setParentBeanFactory(parentBeanFactory);System.out.println("当前BeanFactory 的 Parent BeanFactory:" + beanFactory.getParentBeanFactory());// 启动应用上下文applicationContext.refresh();displayContainsLocalBean(beanFactory, "user");displayContainsLocalBean(parentBeanFactory, "user");displayContainsBean(beanFactory, "user");displayContainsBean(parentBeanFactory, "user");applicationContext.close();}private static ConfigurableListableBeanFactory createParentBeanFactory() {// 创建BeanFactory容器DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);// XML 配置文件的路径String location = "classpath:META-INF/dependency-lookup-context.xml";// 加载配置reader.loadBeanDefinitions(location);return beanFactory;}private static void displayContainsLocalBean(HierarchicalBeanFactory beanFactory, String beanName) {System.out.printf("当前 BeanFactory[%s] 是否包含 Local Bean [name : %s] : %s\n", beanFactory, beanName, beanFactory.containsLocalBean(beanName));}private static void displayContainsBean(HierarchicalBeanFactory beanFactory, String beanName) {System.out.printf("当前 BeanFactory[%s] 是否包含 Bean [name : %s] : %s\n", beanFactory, beanName, containsBean(beanFactory, beanName));}private static boolean containsBean(HierarchicalBeanFactory beanFactory, String beanName) {BeanFactory parentBeanFactory = beanFactory.getParentBeanFactory();if (parentBeanFactory instanceof HierarchicalBeanFactory) {HierarchicalBeanFactory parentHierarchicalBeanFactory = HierarchicalBeanFactory.class.cast(parentBeanFactory);if (containsBean(parentHierarchicalBeanFactory, beanName)) {return true;}}return beanFactory.containsLocalBean(beanName);}
}

上面的代码就是实现了层次的查找,这个时候是有两个工厂,一个是父工厂,一个子工厂,containsBean方法就是通过递归的方式来查找所有的工厂中包含的对应的Bean。

其实BeanFactoryUtils有相应的实现,具体的的代码如下:

在这里插入图片描述

6.延迟依赖查找

Bean 延迟依赖查找接口

  • org.springframework.beans.factory.ObjectFactory
  • org.springframework.beans.factory.ObjectProvider
    • Spring 5 对 Java 8 特性扩展
    • 函数式接口
      • getIfAvailable(Supplier)
      • ifAvailable(Consumer)
    • Stream 扩展 - stream()

具体的代码如下:

package org.learn.spring.dependency.lookup;import org.learn.spring.ioc.overview.domain.User;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;// 通过 ObjectProvider 进行依赖查找
public class ObjectProviderDemo {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();// 将当前类ObjectProviderDemo 作为配置类(configuration class)applicationContext.register(ObjectProviderDemo.class);// 启动应用上下文applicationContext.refresh();lookupByAvailable(applicationContext);lookupByStreamOps(applicationContext);applicationContext.close();}private static void lookupByStreamOps(AnnotationConfigApplicationContext applicationContext) {ObjectProvider objectProvider = applicationContext.getBeanProvider(String.class);Iterable stringIterable = objectProvider;for (String s : stringIterable) {System.out.println(s);}objectProvider.stream().forEach(System.out::println);}private static void lookupByAvailable(AnnotationConfigApplicationContext applicationContext) {ObjectProvider userObjectProvider = applicationContext.getBeanProvider(User.class);User user = userObjectProvider.getIfAvailable(User::createUser);System.out.println("当前 User 对象:" + user);}@Bean@Primarypublic String helloWorld() {return "Hello World";}@Beanpublic String message() {return "Message";}
}

7.安全依赖查找

依赖查找安全性对比

在这里插入图片描述

注意: 层次性依赖查找的安全性取决于其扩展的单一或集合类型的 BeanFactory 接口

具体的代码如下:

package org.learn.spring.dependency.lookup;import org.learn.spring.ioc.overview.domain.User;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;// 类型安全查找的示例
public class TypeSafetyDependencyLookupDemo {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();// 将当前类ObjectProviderDemo 作为配置类(configuration class)applicationContext.register(ObjectProviderDemo.class);// 启动应用上下文applicationContext.refresh();// 演示BeanFactory#getBean方法的安全性 不安全的displayBeanFactoryGetBean(applicationContext);// 演示ObjectFactory#getObject方法的安全性 不安全的displayObjectFactoryGetObject(applicationContext);// 演示ObjectProvider#getIfAvailable方法的安全性 安全的displayObjectProviderGetIfAvailable(applicationContext);// 演示 ListableBeanFactory#getBeansOfType方法的安全性 安全的 也是会抛出BeansException 但是这个只有在Bean创建失败的时候抛出displayListableBeanFactoryGetBeansOfType(applicationContext);//演示ObjectProvider#StreamOps方法的安全性 安全的displayObjectProviderStreamOps(applicationContext);// 关闭应用上下文applicationContext.close();}// 不安全的public static void displayBeanFactoryGetBean(BeanFactory beanFactory) {printBeansException("displayBeanFactoryGetBean", () -> beanFactory.getBean(User.class));}private static void displayObjectFactoryGetObject(AnnotationConfigApplicationContext applicationContext) {// ObjectProvider is ObjectFactoryObjectFactory userObjectProvider = applicationContext.getBeanProvider(User.class);printBeansException("displayObjectFactoryGetObject", userObjectProvider::getObject);}private static void displayObjectProviderGetIfAvailable(AnnotationConfigApplicationContext applicationContext) {ObjectProvider userObjectProvider = applicationContext.getBeanProvider(User.class);printBeansException("displayObjectProviderGetIfAvailable", userObjectProvider::getIfAvailable);}// 也是会抛出BeansException 但是这个只有在Bean创建失败的时候抛出private static void displayListableBeanFactoryGetBeansOfType(ListableBeanFactory beanFactory) {printBeansException("displayListableBeanFactoryGetBeansOfType", () -> beanFactory.getBeansOfType(User.class));}private static void displayObjectProviderStreamOps(AnnotationConfigApplicationContext applicationContext) {ObjectProvider userObjectProvider = applicationContext.getBeanProvider(User.class);printBeansException("displayObjectProviderStreamOps", () -> userObjectProvider.stream().forEach(System.out::println));}private static void printBeansException(String source, Runnable runnable) {System.err.println("===================================================================");System.err.println("Source from " + source);try {runnable.run();} catch (BeansException e) {e.printStackTrace();}}}

运行结果如下:

在这里插入图片描述

可以看出与上面的表格是一样的。

8.内建可查找的依赖

AbstractApplicationContext 内建可查找的依赖

在这里插入图片描述

注解驱动 Spring 应用上下文内建可查找的依赖( 部分)

在这里插入图片描述

注解驱动 Spring 应用上下文内建可查找的依赖( 续)

在这里插入图片描述

那么这些类在什么地方注册的呢?具体的如下:

在这里插入图片描述

后面在初始化的流程中,我们会详细的分析,上面的代码是AnnotationConfigUtils类中的

9.依赖查找中的经典异常

BeansException 子类型

在这里插入图片描述

具体的代码如下:

演示NoUniqueBeanDefinitionException异常

package org.learn.spring.dependency.lookup;import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;// NoUniqueBeanDefinitionException 的异常举例
public class NoUniqueBeanDefinitionExceptionDemo {public static void main(String[] args) {// 创建BeanFactory 容器AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();// NoUniqueBeanDefinitionExceptionDemo 作为配置类(configuration class)applicationContext.register(NoUniqueBeanDefinitionExceptionDemo.class);// 启动应用上下文applicationContext.refresh();try {// 由于应用上下文存在两个String类型的Bean,通过单一查找会抛出异常applicationContext.getBean(String.class);} catch (NoUniqueBeanDefinitionException e) {System.err.printf("Spring 应用上下文存在 %d 个 %s 类型的Bean,具体的原因:%s", e.getNumberOfBeansFound(), e.getBeanType(), e.getMessage());}// 关闭应用上下文applicationContext.close();}@Beanpublic String bean1() {return "bean1";}@Beanpublic String bean2() {return "bean2";}
}

演示BeanInstantiationException异常

package org.learn.spring.dependency.lookup;import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;// BeanInstantiationException 异常的示例
public class BeanInstantiationExceptionDemo {public static void main(String[] args) {// 创建BeanFactory 容器AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();// 注册BeanDefinition Bean Class 是CharSequence的接口BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(CharSequence.class);applicationContext.registerBeanDefinition("errorBean", beanDefinitionBuilder.getBeanDefinition());// 启动应用上下文applicationContext.refresh();// 关闭应用上下文applicationContext.close();}
}

演示BeanCreationException异常

package org.learn.spring.dependency.lookup;import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;// BeanCreationException 异常示例
public class BeanCreationExceptionDemo {public static void main(String[] args) {// 创建BeanFactory 容器AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();// 注册BeanDefinition Bean Class 是POJO的普通类,不过在初始化方法回调时抛出异常BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(POJO.class);applicationContext.registerBeanDefinition("errorBean", beanDefinitionBuilder.getBeanDefinition());// 启动应用上下文applicationContext.refresh();// 关闭应用上下文applicationContext.close();}static class POJO implements InitializingBean {@Overridepublic void afterPropertiesSet() throws Exception {throw new Exception("For purposes....");}}
}

10.面试题

10.1 ObjectFactory 与 BeanFactory 的区别?

ObjectFactory 与 BeanFactory 均提供依赖查找的能力。

不过 ObjectFactory 仅关注一个或一种类型的 Bean 依赖查找, 并且自身不具备依赖查找的能力, 能力则由 BeanFactory 输出。

BeanFactory 则提供了单一类型、 集合类型以及层次性等多种依赖查找方式。

10.2 BeanFactory.getBean 操作是否线程安全?

BeanFactory.getBean 方法的执行是线程安全的, 操作过程中会增加互斥锁

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
一帆风顺二龙腾飞三阳开泰祝福语... 本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...