什么是依赖注入?什么是依赖查找?

简单来说,依赖查找是一个主动获取的过程,例如我需要某个Bean,通过BeanFactory的getBean方法来获取;依赖注入是一个被动接受的过程,例如我需要某个Bean,我只需在类中方法或字段上添加@Autowired或@Resource注解即可,由IoC容器来帮我完成查找并注入。

Talk is cheap. Show me the code

第一步:定义一个Bean,其拥有ApplicationContext、BeanFactory、ApplicationEventPublisher、ResourceLoader这四个依赖项。使用@Autowired注解来声明需要IoC容器处理。

package com.xxx.hyl.dependency.datasource;import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.io.ResourceLoader;/**** 依赖注入演示Bean* @author 君战* */
public class DependencyInjectionBean {@Autowired private ApplicationContext applicationContext;@Autowired private BeanFactory beanFactory;@Autowired private ApplicationEventPublisher applicationEventPublisher;@Autowired private ResourceLoader resourceLoader;// 定义一个display方法来打印这几个依赖项,如果依赖项为空,则会抛出空指针异常public void display(){System.out.println("applicationContext : " + applicationContext.getClass().getSimpleName());System.out.println("beanFactory : " + beanFactory.getClass().getSimpleName());System.out.println("applicationEventPublisher : " + applicationEventPublisher.getClass().getSimpleName());System.out.println("resourceLoader : " + resourceLoader.getClass().getSimpleName());}}

第二步:编写一个启动类,创建注解驱动Spring 应用上下文,在main方法中使用安全查找方式(不知道什么是安全查找什么是非安全查找的同学可以查看我的另一篇博文,那里会详细解释 什么是安全查找Bean,什么是非安全查找Bean?如何安全地查找Bean?)来从应用上下文中查找ApplicationContext 、BeanFactory 、ApplicationEventPublisher 、ResourceLoader 并打印。

package com.xxx.hyl.dependency.datasource;import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.io.ResourceLoader;/*** Spring IoC 依赖查找和依赖注入区别演示Demo** @author 君战*/
public class DependencyAcquireDemo {public static void main(String[] args) {// 这里我们使用了带参的 AnnotationConfigApplicationContext 构造函数,无需再调用refresh方法AnnotationConfigApplicationContext context =new AnnotationConfigApplicationContext(DependencyInjectionBean.class);DependencyInjectionBean injectionBean = context.getBean(DependencyInjectionBean.class);// 执行DependencyInjectionBean#display 方法打印其依赖项injectionBean.display();System.out.println("=====================依赖注入完毕======================");// 这里我们使用安全查找,防止查找不到而抛出NoSuchBeanDefinitionException// 使用AnnotationConfigApplicationContext 查找 -> ApplicationContextObjectProvider<ApplicationContext> contextObjectProvider =context.getBeanProvider(ApplicationContext.class);System.out.println("查找到的 ApplicationContext 为 :" + contextObjectProvider.getIfAvailable());// 使用AnnotationConfigApplicationContext 查找 -> BeanFactoryObjectProvider<BeanFactory> factoryObjectProvider =context.getBeanProvider(BeanFactory.class);System.out.println("查找到的 BeanFactory 为 :" + factoryObjectProvider.getIfAvailable());// 使用AnnotationConfigApplicationContext 查找 -> ApplicationEventPublisherObjectProvider<ApplicationEventPublisher> eventPublishers =context.getBeanProvider(ApplicationEventPublisher.class);System.out.println("查找到的 ApplicationEventPublisher 为 :" + eventPublishers.getIfAvailable());// 使用AnnotationConfigApplicationContext 查找 -> ResourceLoaderObjectProvider<ResourceLoader> loaderObjectProvider =context.getBeanProvider(ResourceLoader.class);System.out.println("查找到的 ResourceLoader 为 :" + loaderObjectProvider.getIfAvailable());String[] beanDefinitionNames = context.getBeanDefinitionNames();System.out.println("IoC容器中已注册的BeanDefinition有:" + Arrays.toString(beanDefinitionNames));}
}

第三步:查看控制台输出

可以看到在DependencyInjectionBean 中声明的四个依赖项都被IoC容器所处理,而在DependencyAcquireDemo#main方法中查找到的却是为null,并且最后打印的IoC容器的BeanDefinition并没有这四个类的BeanDefinition数据,那么这是什么原因导致的呢?

底层原理分析

要回答这个问题,就要从源头-prepareBeanFactory方法来分析,该方法定义在AbstractApplicationContext类中。在该方法中通过传入的ConfigurableListableBeanFactory实例的registerResolvableDependency方法注册了四个类及其对应的实例,这四个Class分别为BeanFactory、ResourceLoader、ApplicationEventPublisher、ApplicationContext,正好和我们在DependencyInjectionBean声明的四个依赖项的类型一致。

registerResolvableDependency方法的作用是用来注册特殊的依赖项,因为BeanFactory、ResourceLoader、ApplicationEventPublisher、ApplicationContext并不是IoC容器中的Bean,它们无法被查找,只能通过这种方式来注册。

// AbstractApplicationContext#prepareBeanFactory
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {// 删除与本次分析无关代码...beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);beanFactory.registerResolvableDependency(ResourceLoader.class, this);beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);beanFactory.registerResolvableDependency(ApplicationContext.class, this);// 删除与本次分析无关代码...
}

那么ConfigurableListableBeanFactory的registerResolvableDependency方法将传入的特殊依赖项保存到了哪里呢?

DefaultListableBeanFactory实现了该方法,在该实现中,首先判断传入的autowiredvalue是否不为空,如果判断成立,接下来判断autowiredValue 是否不是ObjectFactory 类型或者传入的autowiredValue不是dependencyType类型的实例,如果有任何两个条件都不满足,抛出异常,否则保存到resolvableDependencies这个Map中,Key为dependencyType,Value为autowiredValue。

public void registerResolvableDependency(Class<?> dependencyType, @Nullable Object autowiredValue) {Assert.notNull(dependencyType, "Dependency type must not be null");if (autowiredValue != null) {if (!(autowiredValue instanceof ObjectFactory || dependencyType.isInstance(autowiredValue))) {throw new IllegalArgumentException("Value [" + autowiredValue +"] does not implement specified dependency type [" + dependencyType.getName() + "]");}this.resolvableDependencies.put(dependencyType, autowiredValue);}
}

通过这段代码,我们知道应用上下文注册了四个特殊的依赖项,那么它们又是如何和Bean中的依赖项产生关系的呢?这就需要回到IoC容器处理Bean依赖的地方-doResolveDependency方法。

在该方法中通过传入的beanName和类型以及依赖描述符来调用findAutowireCandidates方法,该方法的返回值是一个Map,如果Map为空,则通过依赖描述符来判断该依赖是否是必须的(例如@Autowired注解的required),如果是则抛出异常,否则结束方法执行。

// DefaultListableBeanFactory#doResolveDependency
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {// 删除与本次分析无关代码...Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);if (matchingBeans.isEmpty()) {if (isRequired(descriptor)) {raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);}return null;}// 删除与本次分析无关代码...
}

在findAutowireCandidate方法中,首先BeanFactoryUtils的beanNamesForTypeIncludingAncestors方法来层次性的查找指定BeanFactory以及指定类型的所有BeanName。

这不是重点,重点是接下来的处理-遍历resolvableDependencies,看到这里同学们应该明白了,这不就是前面保存BeanFactory、ResourceLoader、ApplicationEventPublisher、ApplicationContext这四个特殊类型依赖项的Map吗?就是在这里和Bean的依赖项的产生了关系。

遍历resolvableDependencies这个Map,首先调用遍历到的Entry的getKey方法来获取类型,然后判断获取到的类型是否是期望的类型,如果是然后再调用Entry的getValue方法获取到对应的实例,然后该实例是否是需要类型的实例,如果判断成立,则将其添加到result中。

// DefaultListableBeanFactory#findAutowireCandidates
protected Map<String, Object> findAutowireCandidates(@Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this, requiredType, true, descriptor.isEager());Map<String, Object> result = new LinkedHashMap<>(candidateNames.length);for (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) {Class<?> autowiringType = classObjectEntry.getKey();if (autowiringType.isAssignableFrom(requiredType)) {Object autowiringValue = classObjectEntry.getValue();autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);if (requiredType.isInstance(autowiringValue)) {result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);break;}}}// 删除与本次分析无关代码...return result;
}

总结

在Spring IoC中,依赖查找和依赖注入的数据来源并不一样。因为BeanFactory、ResourceLoader、ApplicationEventPublisher、ApplicationContext这四个并不是Bean,它们只是一种特殊的依赖项,无法通过依赖查找的方式来获取,只能通过依赖注入的方式来获取。

在Spring IoC中,依赖注入和依赖查找的数据来源区别相关推荐

  1. 【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入具体的操作细节 | 获取 Activity 中的所有方法 | 获取方法上的注解 | 获取注解上的注解 | 通过注解属性获取事件信息 )

    文章目录 前言 一.获取 Activity 中的所有方法 二.获取方法上的注解 三.获取注解上的注解 四.通过注解属性获取相关事件信息 前言 Android 依赖注入的核心就是通过反射获取 类 / 方 ...

  2. spring依赖注入_Spring依赖注入

    spring依赖注入 介绍: 在设计良好的Java应用程序中,这些类应尽可能独立. 这样的设计提高了组件的可重用性. 它还使对各个组件进行单元测试变得更加容易. 依赖注入的概念促进了Java对象之间的 ...

  3. 【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入具体的操作细节 | 创建 事件监听器 对应的 动态代理 | 动态代理的数据准备 | 创建调用处理程序 | 创建动态代理实例对象 )

    文章目录 前言 一.创建 事件监听器 对应的 动态代理 二.动态代理 数据准备 三.动态代理 调用处理程序 四.动态代理 实例对象创建 前言 Android 依赖注入的核心就是通过反射获取 类 / 方 ...

  4. 【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入具体的操作细节 | 获取要注入事件的 View 对象 | 通过反射获取 View 组件的事件设置方法 )

    文章目录 前言 一.获取要注入事件的 View 对象 二.通过反射获取 View 组件的事件设置方法并执行 前言 Android 依赖注入的核心就是通过反射获取 类 / 方法 / 字段 上的注解 , ...

  5. 【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入代码示例 )

    文章目录 总结 一.Android 事件依赖注入示例 1.创建依赖注入库 2.声明注解 (1).修饰注解的注解 (2).修饰方法的注解 3.Activity 基类 4.动态代理类调用处理程序 5.依赖 ...

  6. 【IOC 控制反转】Android 视图依赖注入 ( 视图依赖注入步骤 | 视图依赖注入代码示例 )

    文章目录 总结 一.Android 视图依赖注入步骤 二.Android 布局依赖注入示例 1.创建依赖注入库 2.声明注解 3.Activity 基类 4.依赖注入工具类 5.客户端 Activit ...

  7. java依赖注入_Java依赖注入选项

    java依赖注入 我想花一些时间来总结一些流行的Java依赖注入(DI)框架. 这是可用功能的高级概述. 首先,什么是依赖注入? "依赖注入是一种软件设计模式,可以删除硬编码的依赖,并可以在 ...

  8. Spring IOC中的Bean对象

    Spring IOC中的Bean对象 (一)Bean是什么 突然发现提到了好多次Bean,居然忘记了讲Bean是什么.没事,现在讲也不晚.Java中的Bean是一种规范,是一种特殊的java类.所以我 ...

  9. 依赖注入?依赖注入是如何实现解耦的?

    如何用最简单的方式解释依赖注入?依赖注入是如何实现解耦的? 第一章:小明和他的手机 从前有个人叫小明 小明有三大爱好,抽烟,喝酒-- 咳咳,不好意思,走错片场了.应该是逛知乎.玩王者农药和抢微信红包 ...

  10. php 依赖注入框架,依赖注入模式(Dependency Injection)

    依赖注入模式(Dependency Injection) 由 学院君 创建于5年前, 最后更新于 10个月前 版本号 #3 18333 views 16 likes 0 collects 1.模式定义 ...

最新文章

  1. Nodejs实现WebSocket通信demo
  2. 创建型模式 工厂模式
  3. com.rabbitmq.client.AuthenticationFailureException: ACCESS_REFUSED
  4. Flashlight should be gray after finishing Recor...
  5. Redis配置优化和使用
  6. 【开源.NET】 分享一个前后端分离的轻量级内容管理框架
  7. Android mes系统源码,基于Android的MES监控系统设计与开发
  8. dms虚拟服务器,取得dms服务器ip
  9. 火狐可以使用广告终结者_使用Jupyter从终结者终止的地方重新启动脚本
  10. Android Netd ndc
  11. 复习单片机:流水灯(内含2种方法(左移操作符法+crol法)+设计思路+原始代码)
  12. 华氏温度转换为摄氏温度,c语言实例一
  13. Array Shrinking
  14. 关于windows自带的两种远程访问方式
  15. 月薪不过万郑州程序员的真实生活
  16. 曲线拟合的数值方法——《数值计算方法》
  17. TJOI 2015 弦论 题解
  18. UA MATH566 统计理论 完备性的证明方法
  19. 2019年-2020年计划
  20. 解析产品开发失败的5个根本原因

热门文章

  1. atm机编程java_初识Java,关于一个简单的ATM机的java程序设计
  2. xamarin android 设备,【Xamarin.Android】在Visual Studio中增强设备日志记录
  3. C/C++[黑盒测试]
  4. caffe手写数字分类-学习曲线
  5. 阿里云云计算 21 VPC的概念
  6. ngrok 通过外网链接映射到本地机器,支持http,https
  7. 易筋SpringBoot 2.1 | 第廿篇:SpringBoot的复杂JPA以及源码解析
  8. 算法:Minimum Path Sum(最小路径和)
  9. 数据集:男女身高体重(二维)
  10. php类的实例化方法吗,php实例化一个类的具体方法