什么是安全查找Bean,什么是非安全查找Bean?如何安全地查找Bean?

查找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方法返回值,不会进行校验。

就相当于一个是框架内部自己进行了验证,确保返回给使用者的数据不为空,另一个不对返回值进行任何保证,需要使用者自己进行校验。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值