在Spring IoC中,依赖注入和依赖查找的数据来源一样吗?
什么是依赖注入?什么是依赖查找?
简单来说,依赖查找是一个主动获取的过程,例如我需要某个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 查找 -> ApplicationContext
ObjectProvider<ApplicationContext> contextObjectProvider =
context.getBeanProvider(ApplicationContext.class);
System.out.println("查找到的 ApplicationContext 为 :" + contextObjectProvider.getIfAvailable());
// 使用AnnotationConfigApplicationContext 查找 -> BeanFactory
ObjectProvider<BeanFactory> factoryObjectProvider =
context.getBeanProvider(BeanFactory.class);
System.out.println("查找到的 BeanFactory 为 :" + factoryObjectProvider.getIfAvailable());
// 使用AnnotationConfigApplicationContext 查找 -> ApplicationEventPublisher
ObjectProvider<ApplicationEventPublisher> eventPublishers =
context.getBeanProvider(ApplicationEventPublisher.class);
System.out.println("查找到的 ApplicationEventPublisher 为 :" + eventPublishers.getIfAvailable());
// 使用AnnotationConfigApplicationContext 查找 -> ResourceLoader
ObjectProvider<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,它们只是一种特殊的依赖项,无法通过依赖查找的方式来获取,只能通过依赖注入的方式来获取。
本文地址:https://blog.csdn.net/m0_43448868/article/details/111866510
上一篇: 双亲委派机制和沙箱安全机制
下一篇: Java实验13 银行账户存取款业务