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#createOptionalDependencyDefaultListableBeanFactory#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 个步骤:

  1. 首先通过自动装配解析器(AutowireCandidateResolver)来解析注入点配置的 @Value 注解的值,即默认值,如果没有配置 @Value 注解,,如果找到了,就直接返回。则返回 null,继续查找

  2. 判断 descriptor 是不是 StreamDependencyDescriptor 类型,如果不是,再判断 type 是不是 数组、CollectionMap,如果是这四个判断有一个为 true,则进行处理,然后直接返回,否则返回 null。继续判断。

  3. 查找 IOC 容器中能匹配所需类型的 bean 实例。有可能会返回多个,所以用 map 接收返回结果,跟 @Autowired 注解的效果一样,也许就是@Autowired 注解的代码。此时如果没找到,则直接返回 null,不再进行查找,找到了,则进行下一步的解析。

  4. 分找到多个匹配的 bean 和只找到一个匹配的 bean 两种情况进行分析。

  5. 确定了开始最终返回的 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。

类继承结构

常见的实现类:

也就是说,我们可以在注入 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() {

    }

}

使用注解

通过对 DependencyObjectProviderAutowireCandidateResolver 的分析,我们知道我们可以在注入 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 的解析置后了,而且让用户自己根据业务逻辑定制。