查找Bean有哪些手段?
总体来说,查找Bean有两种手段,一种是单Bean查找(只会返回一个Bean),一种是多Bean查找(返回多个Bean),它们分别对应BeanFactory和ListableBeanFactory。ListableBeanFactoty继承自BeanFactory接口。
再往细分,BeanFactory接口定义的方法主要有两种,一种是根据名称或类型非安全查找(当未根据传入的参数查找到对应的Bean时会抛出异常),这系列的代表是getBean(String)、getBean(Class)…,另一种是安全查找,这一系列的代表是getBeanProvider方法。ListableBeanFactory中所有和Bean查找相关的方法都是安全的,因为其返回值不是Map就是数组,如果未找到,只会返回空Map或者空数组,不会抛出异常,但其findAnotationOnBean方法并不是安全地。
除了根据BeanName或者BeanType来查找单个或者多个Bean之外,ListableBeanFactory还定义了根据注解来查找Bean的方法-getBeansWithAnnotation以及根据指定BeanName和注解类型来查找对应Bean中的指定注解数据方法-findAnnotationOnBean。
另外需注意的是虽然BeanProvider的getIfAvailable方法是安全的,但是其getObject方法并不是安全地。下面会演示到。
依赖查找安全性对比
Talk is cheap. Show me the code
第一步:定义一个类,创建Spring 注解驱动应用上下文-AnnotationConfigApplicationContext,不需要向该上下文注册任何Bean,调用上下文的refresh方法,然后调用我们上面提到的方法来从应用上下文中查找DependencyLookupDemo。
package com.xxx.hyl.dependency.lookup;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.Map;
/*
* @author 君战
*/
public class DependencyLookupDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.refresh();
try {
// 非安全查找,会抛出异常
DependencyLookupDemo contextBean = context.getBean(DependencyLookupDemo.class);
} catch (BeansException e) {
System.err.println("查找 DependencyLookupDemo 失败,异常信息为:" + e.getMessage());
}
// 安全地查找Bean,不会抛出异常
ObjectProvider<DependencyLookupDemo> beanProvider =
context.getBeanProvider(DependencyLookupDemo.class);
DependencyLookupDemo lookupDemo = beanProvider.getIfAvailable();
System.out.println("根据 getBeanProvider.getIfAvailable 方法查找到的 DependencyLookupDemo 为 : " + lookupDemo);
try{
DependencyLookupDemo providerObject = beanProvider.getObject();
} catch (BeansException e){
System.err.println("根据 getBeanProvider.getObject 方法查询失败,异常信息为 : " + e.getMessage());
}
System.out.println("=======================以上为查找单个Bean==========================");
Map<String, DependencyLookupDemo> lookupDemoMap =
context.getBeansOfType(DependencyLookupDemo.class);
System.out.println("根据 getBeansOfType 方法查找到的 DependencyLookupDemo 为:" + lookupDemoMap);
String[] lookupBeanNames = context.getBeanNamesForType(DependencyLookupDemo.class);
System.out.println("根据 getBeanNamesForType 方法查找到的 DependencyLookupDemo 为:" + Arrays.toString(lookupBeanNames));
// 根据注解类型来查找Bean
Map<String, Object> beansWithAnnotation = context.getBeansWithAnnotation(Component.class);
System.out.println("根据 getBeansWithAnnotation 方法查找到的IoC容器添加了@Component注解的Bean有 :" + beansWithAnnotation);
try {
// 非安全查找,会抛出异常。随便指定一个BeanName
Component unKnowBeanType = context.findAnnotationOnBean("unKnowBeanType", Component.class);
} catch (BeansException e) {
System.err.println("根据 findAnnotationOnBean 方法,指定beanName为 unKnowBeanType ,注解类型为 @Component ,异常信息为:" + e.getMessage());
}
}
}
第二步:查看控制台打印信息。可以看到红字的都是发生了异常,基本与我们描述的情况吻合。
底层行为分析
要分析getBeanProvider方法为什么是安全的,就要和getBean方法来对比着分析,因为这两者的底层实现其实都是一样的。我们都知道Spring提供的唯一一个可用的IoC容器产品就是DefaultListableBea-nFactory,在该类中实现了BeanFactory定义的这两个方法。
先看看DefaultListableBeanFactory实现的一个参数的getBean方法。可以看到其底层还是调用两个参数的getBean方法,并且在resolveBean方法返回值为null的时候,抛出NoSuchBeanDefinitionException异常。
// DefaultListableBeanFactory#getBean(java.lang.Class<T>)
@Override
public <T> T getBean(Class<T> requiredType) throws BeansException {
return getBean(requiredType, (Object[]) null);
}
@SuppressWarnings("unchecked")
@Override
public <T> T getBean(Class<T> requiredType, @Nullable Object... args) throws BeansException {
Assert.notNull(requiredType, "Required type must not be null");
// getBean方法底层调用的是resolveBean方法,resolveBean方法并不会抛出异常,如果未找到,只会返回null
Object resolved = resolveBean(ResolvableType.forRawClass(requiredType), args, false);
if (resolved == null) { // getBean自己在这里做了一层校验,如果resolveBean方法返回值为null,抛出NoSuchBeanDefinitionException异常
throw new NoSuchBeanDefinitionException(requiredType);
}
return (T) resolved;
}
再看看参数类型为Class的getBeanProvider方法,其底层是调用参数类型为ResolvableType的getBeanProvider方法。在该方法中使用了匿名内部类的方式返回一个BeanObjectProvider实现类。BeanObjectProvider是ObjectProvider接口的一个子接口,定义在DefaultListableBeanFactory类中,在该接口中并未进行任何扩展。
在返回的BeanObjectProvider类中,对于getObject及其重载方法,底层实现依然是调用resolveBean方法,并且和getBean方法的实现一致,如果该方法的返回值为null,都会抛出NoSuchBeanDefinitionException。但是在其实现的getIfAvailable、getIfUnique方法中不会对resolveBean方法的返回值进行校验,这意味着即使该方法返回为null,也不会抛出异常。
// DefaultListableBeanFactory#getBeanProvider(java.lang.Class<T>)
@Override
public <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType) {
Assert.notNull(requiredType, "Required type must not be null");
return getBeanProvider(ResolvableType.forRawClass(requiredType));
}
@Override
public <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType) {
return new BeanObjectProvider<T>() {
@Override
public T getObject() throws BeansException {
T resolved = resolveBean(requiredType, null, false); // 底层实现依然是调用resolveBean方法
if (resolved == null) { // 检查resolveBean方法返回值,如果resolveBean方法返回值为null,抛出NoSuchBeanDefinitionException异常
throw new NoSuchBeanDefinitionException(requiredType);
}
return resolved;
}
@Override
public T getObject(Object... args) throws BeansException {
T resolved = resolveBean(requiredType, args, false);
if (resolved == null) { // 检查resolveBean方法返回值,如果resolveBean方法返回值为null,抛出NoSuchBeanDefinitionException异常
throw new NoSuchBeanDefinitionException(requiredType);
}
return resolved;
}
@Override
@Nullable
public T getIfAvailable() throws BeansException {
return resolveBean(requiredType, null, false); // 不检查resolveBean方法返回值
}
@Override
@Nullable
public T getIfUnique() throws BeansException {
return resolveBean(requiredType, null, true);// 不检查resolveBean方法返回值
}
@SuppressWarnings("unchecked")
@Override
public Stream<T> stream() {
return Arrays.stream(getBeanNamesForTypedStream(requiredType))
.map(name -> (T) getBean(name))
.filter(bean -> !(bean instanceof NullBean));
}
@SuppressWarnings("unchecked")
@Override
public Stream<T> orderedStream() {
String[] beanNames = getBeanNamesForTypedStream(requiredType);
if (beanNames.length == 0) {
return Stream.empty();
}
Map<String, T> matchingBeans = new LinkedHashMap<>(beanNames.length);
for (String beanName : beanNames) {
Object beanInstance = getBean(beanName);
if (!(beanInstance instanceof NullBean)) {
matchingBeans.put(beanName, (T) beanInstance);
}
}
Stream<T> stream = matchingBeans.values().stream();
return stream.sorted(adaptOrderComparator(matchingBeans));
}
};
}
private interface BeanObjectProvider<T> extends ObjectProvider<T>, Serializable {
}
总结
getBean及其重载方法以及getBeanProvider和其重载方法,底层实现都是调用resolveBean方法,只不过在getBean和其重载方法以及getBeanProvider方法返回的ObjectProvider实现类中实现的getObject方法都会对resolveBean方法的返回值进行校验,如果为null则抛出NoSuchBeanDefinitionExcepti-on。而getIfAvailable方法则是直接返回resolveBean方法返回值,不会进行校验。
就相当于一个是框架内部自己进行了验证,确保返回给使用者的数据不为空,另一个不对返回值进行任何保证,需要使用者自己进行校验。