ObjectProvider 详解
ObjectProvider 详解
ObjectProvider 简单源码分析
ObjectProvider
接口是 ObjectFactory
的一个变体,专门为注入点设计,专门用于各种注入,比如函数参数注入和属性注入,此接口类型的参数在注入时允许通过编程进行可选性处理和处理存在多个可注入 bean 时的情况。属于比 @Autowired
高级一些的注入工具。
从 Spring 5.1 开始,这个 ObjectProvider
继承了 Iterable
并提供了 Stream
支持。因此,它可以用于 for 循环,用 forEach 进行迭代访问,并允许集合风格的流式访问。这些在后面的实践小节中我们都会看到。
简单看一下接口内容:
public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
// 返回具体实现类(一般是IOC容器)管理的对象的实例(可能是共享的,也可能是独立的)。同时,允许显示指定构造参数,这些参数会传入 BeanFactory#getBean(String, Object...)方法来构造实例
T getObject(Object... args) throws BeansException;
// 如果有的话,不指定构造参数,返回具体实现类(一般是IOC容器)管理的对象的实例(可能是共享的,也可能是独立的)。
@Nullable
T getIfAvailable() throws BeansException;
// 默认方法
// 如果有的话,不指定构造参数,返回具体实现类(一般是IOC容器)管理的对象的实例(可能是共享的,也可能是独立的)。
// 如果实例不存在,则调用调用 Supplier 接口实例提供默认实例。
default T getIfAvailable(Supplier<T> defaultSupplier) throws BeansException {
T dependency = getIfAvailable();
return (dependency != null ? dependency : defaultSupplier.get());
}
// 默认方法
// 如果有的话,不指定构造参数,返回具体实现类(一般是IOC容器)管理的对象的实例(可能是共享的,也可能是独立的)。
// 然后调用 Consumer 接口实例消费这个实例,执行具体的业务逻辑
default void ifAvailable(Consumer<T> dependencyConsumer) throws BeansException {
T dependency = getIfAvailable();
if (dependency != null) {
dependencyConsumer.accept(dependency);
}
}
// 返回具体实现类(一般是IOC容器)管理的对象的实例(可能是共享的,也可能是独立的)。
// 如果实例只有一个,则直接返回,如果不存在或者有多个且没有进行@Primary注解进行标记,则返回null。
@Nullable
T getIfUnique() throws BeansException;
// 默认方法
// 返回具体实现类(一般是IOC容器)管理的对象的实例(可能是共享的,也可能是独立的)。
// 如果实例只有一个,则直接返回,如果不存在或者有多个且没有进行@Primary注解进行标记,则返回null。
// 然后实例为null,则调用调用 Supplier 接口实例提供默认实例。
default T getIfUnique(Supplier<T> defaultSupplier) throws BeansException {
T dependency = getIfUnique();
return (dependency != null ? dependency : defaultSupplier.get());
}
// 默认方法
// 返回具体实现类(一般是IOC容器)管理的对象的实例(可能是共享的,也可能是独立的)。
// 如果实例只有一个,则直接返回,如果不存在或者有多个且没有进行@Primary注解进行标记,则返回null。
// 然后调用 Consumer 接口实例消费这个实例,执行具体的业务逻辑
default void ifUnique(Consumer<T> dependencyConsumer) throws BeansException {
T dependency = getIfUnique();
if (dependency != null) {
dependencyConsumer.accept(dependency);
}
}
// 返回所有匹配的对象实例的迭代器,没有进行排序(但通常是注册顺序)。
// 使用时可直接for循环。
@Override
default Iterator<T> iterator() {
return stream().iterator();
}
// 返回所有匹配对象实例的顺序流,没有进行排序(但通常是注册顺序)。
// 实现类必须实现,否则调用报错
default Stream<T> stream() {
throw new UnsupportedOperationException("Multi element access not supported");
}
// 返回所有匹配对象实例的顺序流,根据工厂的公共的顺序比较器进行预先排序。
// 在标准Spring应用程序上下文中,根据惯例,它将根据org.springframework.core.Ordered进行排序,如果是基于注释的配置,还会考虑org.springframework.core.annotation.Order注释,
// 用于模拟list/array类型的多元素注入点。
default Stream<T> orderedStream() {
throw new UnsupportedOperationException("Ordered element access not supported");
}
}
这些接口在后文中的 实践
小节我们都会实践一下。
ObjectProvider
有多个实现类,从下图可以看出,正经的实现类只有一个,它是那么的突出,DefaultListableBeanFactory$DependencyObjectProvider
,我们有必要好好看看它的源码。
DependencyObjectProvider
简单看了下源码,可以注意到 ObjectProvider
接口所有方法的实现基本上都委托给了 DefaultListableBeanFactory#createOptionalDependency
和 DefaultListableBeanFactory#doResolveDependency
方法,委托给 DefaultListableBeanFactory#createOptionalDependency
的条件也很简单,就是 DependencyObjectProvider#optional
字段是否为 true,那 DependencyObjectProvider#optional
又是怎么确定的呢,是在 DependencyObjectProvider
的构造函数中确定的。
public DependencyObjectProvider(DependencyDescriptor descriptor, @Nullable String beanName) {
this.descriptor = new NestedDependencyDescriptor(descriptor);
// 如果需要注入的依赖的类型就是 Optional ,那 optional 就为true
this.optional = (this.descriptor.getDependencyType() == Optional.class);
this.beanName = beanName;
}
逻辑很简单,如果需要注入的依赖的类型就是 Optional
,那 DependencyObjectProvider#optional
字段就为 true,那么最终返回的也是一个 Optional
类型的实例,很合理。
我们来看看 DefaultListableBeanFactory#createOptionalDependency
方法:
private Optional<?> createOptionalDependency( DependencyDescriptor descriptor, @Nullable String beanName, final Object... args) {
// 匿名内部类的写法,
// 重写 isRequired 和 resolveCandidate,新建descriptorToUse实例,
DependencyDescriptor descriptorToUse = new NestedDependencyDescriptor(descriptor) {
@Override
public boolean isRequired() {
return false;
}
@Override
public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory) {
return (!ObjectUtils.isEmpty(args) ? beanFactory.getBean(beanName, args) :
super.resolveCandidate(beanName, requiredType, beanFactory));
}
};
// 调用 doResolveDependency
Object result = doResolveDependency(descriptorToUse, beanName, null, null);
return (result instanceof Optional ? (Optional<?>) result : Optional.ofNullable(result));
}
可以看到最终还是调用 DefaultListableBeanFactory#doResolveDependency
方法。我们来简单看看此方法的源码,可以梳理出解析依赖的 5 个步骤:
-
首先通过自动装配解析器(
AutowireCandidateResolver
)来解析注入点配置的@Value
注解的值,即默认值,如果没有配置@Value
注解,,如果找到了,就直接返回。则返回 null,继续查找 -
判断 descriptor 是不是
StreamDependencyDescriptor
类型,如果不是,再判断 type 是不是 数组、Collection
、Map
,如果是这四个判断有一个为 true,则进行处理,然后直接返回,否则返回 null。继续判断。 -
查找 IOC 容器中能匹配所需类型的 bean 实例。有可能会返回多个,所以用 map 接收返回结果,跟
@Autowired
注解的效果一样,也许就是@Autowired 注解的代码。此时如果没找到,则直接返回 null,不再进行查找,找到了,则进行下一步的解析。 -
分找到多个匹配的 bean 和只找到一个匹配的 bean 两种情况进行分析。
-
确定了开始最终返回的 bean 的类型,然后开始实例化,实例化后返回。
@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
try {
Object shortcut = descriptor.resolveShortcut(this);
if (shortcut != null) {
return shortcut;
}
// 获取依赖描述中记录的依赖的类型,即需要注入的类型,也就是在是哟ObjectProvider<T>的时候,T的类型
Class<?> type = descriptor.getDependencyType();
// 1. 首先通过自动装配解析器来解析注入点配置的`@Value`注解的值,即默认值,
// 如果没有配置`@Value`注解,则返回null, 如果找到了,就直接返回
// ---------------------------------------------------------------------------------------------------------------
// 简单介绍一下 AutowireCandidateResolver 这个接口的作用是确定特定的bean定义是否有资格作为指定依赖项(注入点)的自动装配的候选bean。
// getAutowireCandidateResolver()方法返回的 AutowireCandidateResolver 实例是 ContextAnnotationAutowireCandidateResolver
// 作用是支持对注入点的`@Qualifier`注解、`@Lazy`注解、`@Value`注解的匹配,也就是说,我们可以在注入`ObjectProvider`的时候,配合使用`@Qualifier`注解、`@Value`注解、`@Lazy`注解,限制注入的bean。
// AutowireCandidateResolver#getSuggestedValue 方法顾名思义是获取建议(默认)值,是为了获取注入点的`@Value`注解配置的默认值,如果没有配置,那这里就是null
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
if (value != null) {
if (value instanceof String) {
String strVal = resolveEmbeddedValue((String) value);
BeanDefinition bd = (beanName != null && containsBean(beanName) ?
getMergedBeanDefinition(beanName) : null);
value = evaluateBeanDefinitionString(strVal, bd);
}
TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
try {
return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());
}
catch (UnsupportedOperationException ex) {
// A custom TypeConverter which does not support TypeDescriptor resolution...
return (descriptor.getField() != null ?
converter.convertIfNecessary(value, type, descriptor.getField()) :
converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
}
}
// 2. 判断 descriptor 是不是 StreamDependencyDescriptor类型,如果不是,再判断 type 是不是 数组、Collection、Map,如果是这四个判断有一个为true,则进行处理,然后直接返回,否则返回null。继续判断
// 调用 ObjectProvider 的 stream 方法的时候,descriptor 的类型就是 StreamDependencyDescriptor 类型
Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
if (multipleBeans != null) {
return multipleBeans;
}
// 3. 查找IOC容器中能匹配所需类型的bean实例。有可能会返回多个,所以用map接收返回结果,
// 返回的Map中key是匹配所需类型的候选的bean的名称,value是候选bean实例的实际类型
// 跟@Autowired注解的效果一样,也许就是@Autowired注解的代码
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
if (matchingBeans.isEmpty()) {
if (isRequired(descriptor)) {
raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
}
// 经过前面三步的解析,如果还没有找到可以注入的bean,那就放弃了,返回null
return null;
}
// 4. matchingBeans 结果不为空,开始解析,分多个和单个的情况
String autowiredBeanName;
Object instanceCandidate;
if (matchingBeans.size() > 1) {
// 匹配的,可以注入的bean不止一个
// 那我们就要确定到底要注入哪一个,返回最终确定的bean的名字
autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
if (autowiredBeanName == null) {
if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
// 在众多可注入的bean中没有确定最终注入的bean,同时这个注入的实例还是必须的,那就只能进一步处理。
return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
}
else {
// 在众多可注入的bean中没有确定最终注入的bean,同时这个注入的实例不是必须的,那就返回null好了。
// In case of an optional Collection/Map, silently ignore a non-unique case:
// possibly it was meant to be an empty collection of multiple regular beans
// (before 4.3 in particular when we didn't even look for collection beans).
return null;
}
}
// 通过最终确定的bean的名称,直接获取bean的类型
instanceCandidate = matchingBeans.get(autowiredBeanName);
}
else {
// 匹配的,可以注入的bean只有一个一个
// 因为只有一个,那就直接使用,将key和value直接赋值给 autowiredBeanName 和 instanceCandidate
Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
autowiredBeanName = entry.getKey();
instanceCandidate = entry.getValue();
}
if (autowiredBeanNames != null) {
autowiredBeanNames.add(autowiredBeanName);
}
// 5. 确定了最终返回的bean的类型,然后开始实例化
if (instanceCandidate instanceof Class) {
// 指定 bean名称(autowiredBeanName),bean的类型(type),容器(this),开始实例化
// 并将实例化的结果覆盖给 instanceCandidate
instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
}
// 将 instanceCandidate 作为最终结果,直接返回。
Object result = instanceCandidate;
if (result instanceof NullBean) {
// 如果实例化不成功,报错
if (isRequired(descriptor)) {
raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
}
result = null;
}
if (!ClassUtils.isAssignableValue(type, result)) {
throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
}
return result;
}
finally {
ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
}
}
AutowireCandidateResolver
AutowireCandidateResolver
是一个这个接口的作用是确定特定的 bean 定义是否有资格作为指定依赖项(注入点)的自动装配的候选 bean。
类继承结构
常见的实现类:
-
SimpleAutowireCandidateResolver
:当注入点没有注解修饰时使用。这个实现只检查 bean 定义。 -
QualifierAnnotationAutowireCandidateResolver
:将 bean 定义匹配注入点(比如字段或者方法参数)的@Qualifier
注解。还支持解析注入点上通过@Value
注解配置的默认的表达式的值。 -
ContextAnnotationAutowireCandidateResolver
:继承自QualifierAnnotationAutowireCandidateResolver
,除了支持注入点的的@Qualifier
注解和@Value
注解,还支持注入点的@Lazy
注解。实际用的最多的也是这个实现类。
也就是说,我们可以在注入 ObjectProvider
的时候,配合使用 @Qualifier
注解、@Value
注解、@Lazy
注解,限制注入的 bean。
DependencyDescriptor
DependencyDescriptor
用于描述待注入的特定依赖项。包装构造函数参数、方法参数或字段,允许统一访问它们的元数据。
比如待注入的是
ObjectProvider
,那么中DependencyDescriptor
中保存的就是使用DependencyObjectProvider
这个 bean 的位置的信息,即待注入的位置的信息,比如依赖DependencyObjectProvider
的 bean 的名称,以及等待注入DependencyObjectProvider
的位置。
实践
ObjectProvider
使用起来非常简单。
getIfAvailable
用于获取单个 bean,如果有多个,会报错,我不喜欢会报错的 API,不推荐使用。
效果最类似于
@Autowired
注解
新建一个简单的 bean,类型为 User
@Component
@Data
public class User {
private Integer id = 123;
private String name = "xiashuo";
private Integer age;
}
然后新建一个 UserObjectProviderTest
类型的 bean,并在构造函数参数中使用 ObjectProvider<User>
类型的参数,(会自动从 IOC 中注入)
@Component
public class UserObjectProviderTest {
public UserObjectProviderTest(ObjectProvider<User> users) {
users.ifAvailable((user) -> System.out.println(user.toString()));
}
}
经过调试可知,注册的参数 users 的类型为 DefaultListableBeanFactory$DependencyObjectProvider
最终输出的日志如下:
User(id=123, name=xiashuo, age=null)
ifAvailable
方法的参数是一个函数式接口 Consumer<Object> dependencyConsumer
,我们可以根据自身的业务需求进行自定义。
getIfUnique
用于获取单个 bean,但是如果有多个,也不会报错,有多个只会返回 null。
将 UserObjectProviderTest
的构造方法换成
public UserObjectProviderTest(ObjectProvider<User> users) {
User ifUnique = users.getIfUnique();
if (ifUnique != null) {
System.out.println(ifUnique);
}else{
System.out.println("返回null");
}
}
也可以正常输出:
User(id=123, name=xiashuo, age=null)
然后,我们在 Web 配置类中添加 @Bean
方法,再注册一个 User
类型的 bean,此时,系统中有两个 User
类型的 bean。
@Configuration(proxyBeanMethods = false)
public class MyConfig implements WebMvcConfigurer {
@Bean
public User myBean(){
User user = new User();
user.setId(456);
user.setName("aaa");
user.setAge(20);
return user;
}
}
此时通过 ObjectProvider#ifAvailable
方法会报错,因为当前 IOC 中有两个 User
类型的 bean,ObjectProvider#ifAvailable
方法无法处理这种情况。
***************************
APPLICATION FAILED TO START
***************************
Description:
Constructor in xyz.xiashuo.springbootwebsimple.beans.UserObjectProviderTest required a single bean, but 2 were found:
- user: defined in file [E:\IDEAProject\SpringBoot\SpringBoot-WebSimple\target\classes\xyz\xiashuo\springbootwebsimple\beans\User.class]
- myBean: defined by method 'myBean' in class path resource [xyz/xiashuo/springbootwebsimple/beans/MyConfig.class]
Action:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
但是 ObjectProvider#getIfUnique
方法不会报错,只会返回 null。
返回null
其实这个时候(通过 ObjectProvider#iterator
方法)我们可以同时获取这两个 bean,这是 @Autowired
注解不具备的能力。
iterator
获取所有的 bean。有一个或者多个都不会报错。这是 @Autowired
注解不具备的能力。
在前文的基础上,将 UserObjectProviderTest
的构造方法换成
public UserObjectProviderTest(ObjectProvider<User> users) {
//ObjectProvider 支持迭代器 iterator
// 按照注册顺序输出
for (User user : users) {
System.out.println(user);
}
}
此时可以正常获取两个 User
类型的 bean,
User(id=123, name=xiashuo, age=null)
User(id=456, name=aaa, age=20)
注意,此时的输出顺序,是这些 bean 的注册顺序:@Component
注解扫描的 bean 在 @Configuration
配置类中的 @Bean
方法注册的 bean 前面注册。
如果我们项自定义 bean 的输出顺序,可以使用 ObjectProvider#orderedStream
配合 @Order
注解实现。
orderedStream
可通过 @Order
注解自定义 bean 的顺序
修改 User
类和 MyConfig#myBean
方法,添加 @Order
注解,将 User
类的顺序设置在 MyConfig#myBean
方法的后面
@Component
@Data
@Order(2)
public class User {
private Integer id = 123;
private String name = "xiashuo";
private Integer age;
}
@Configuration(proxyBeanMethods = false)
public class MyConfig implements WebMvcConfigurer {
@Bean
@Order(1)
public User myBean(){
User user = new User();
user.setId(456);
user.setName("aaa");
user.setAge(20);
return user;
}
}
同时修改 UserObjectProviderTest
的构造方法
public UserObjectProviderTest(ObjectProvider<User> users) {
//ObjectProvider 支持迭代器 iterator
// 按照注册顺序输出
System.out.println("------------ iterator() 方法输出 ------------------");
for (User user : users) {
System.out.println(user);
}
System.out.println("------------ orderedStream() 方法输出 ------------------");
// orderedStream 会使用 @Order排序,输出
users.orderedStream().forEach((user) -> {
System.out.println(user);
});
}
日志输出
------------ iterator() 方法输出 ------------------
User(id=123, name=xiashuo, age=null)
User(id=456, name=aaa, age=20)
------------ orderedStream() 方法输出 ------------------
User(id=456, name=aaa, age=20)
User(id=123, name=xiashuo, age=null)
可以看到,ObjectProvider#iterator
方法的输出依然不变,但是 ObjectProvider#orderedStream
的输出的顺序已经是通过 @Order
注解自定义过了的。
使用在字段上
跟在效果是一样的。无非是写法不一样。
@Component
public class UserObjectProviderTest {
@Autowired
ObjectProvider<User> users;
@PostConstruct
public void init() {
}
}
使用注解
通过对 DependencyObjectProvider
和 AutowireCandidateResolver
的分析,我们知道我们可以在注入 ObjectProvider
的时候,配合使用 @Qualifier
注解、@Value
注解、@Lazy
注解,限制注入的 bean。
其实从容器实现(
DefaultListableBeanFactory
)的角度看,注入ObjectProvider
类型的依赖跟注入其他类型的依赖没有什么不同,因此,在注入其他类型的依赖的时候可以使用的注解,理应在注入ObjectProvider
的时候同样可以使用
比如使用 @Qualifier
注解
@Component
public class UserObjectProviderTest {
@Autowired
//只注入 id为user的Userbean。
@Qualifier("user")
ObjectProvider<User> users;
@PostConstruct
public void init() {
users.ifAvailable((user) -> System.out.println(user.toString()));
}
}
虽然 IOC 容器中包含了两个 User
类型的 bean,但是只会注入 id 为 user
的那个。输出日志:
User(id=123, name=xiashuo, age=null)
简单总结
ObjectProvider
跟通过 @Autowired
注解直接注入相比,提供了一个中间态。
使用 @Autowired
注解的时候,如果容器中存在两个同类型的 bean(假设没有配置 @Primary
),那么就会直接报错,解析的过程在后台直接处理了,没有可配置的空间,而 ObjectProvider
的作用是提供一个中间态,注入 ObjectProvider
的时候,ObjectProvider
提供的 bean 的解析还未开始,我们可以调用 ObjectProvider
接口不同的方法走不同的解析逻辑,比如获取容器中唯一的 bean,比如获取满足条件的多个 bean,相当于将 bean 的解析置后了,而且让用户自己根据业务逻辑定制。