前语

随着SpringBoot的普及,Spring的运用也越来越广,在某些场景下,咱们无法经过注解或装备的办法直接获取到某个Bean。比如,在某一些东西类、规划形式完成中需求运用到Spring容器办理的Bean,此刻就需求直接获取到对应的Bean。

本文为咱们整理汇总了常见的获取Bean的办法,并供给一些优劣剖析,便利咱们在运用届时有更好的选择。一起,也会为咱们恰当的普及和拓展一些相关知识。

Spring的IoC容器

在Spring中,Bean的实例化、定位、装备运用程序中的目标及树立目标间的依靠关系,都是在IoC容器中进行的。因而,要在Spring中获取Bean,本质上便是从IoC容器傍边获取Bean。

在Spring中,BeanFactory是IoC容器的实践代表者,该接口供给了IoC容器最底子功用。一起,Spring还供给了别的一种类型的容器:ApplicationContext容器。

ApplicationContext容器包含BeanFactory容器的所有功用(BeanFactory的子接口),供给了更多面向运用的功用,它供给了国际化支持和结构事情系统,更易于创建实践运用。

一般状况,咱们称BeanFactory为IoC容器,称ApplicationContext为运用上下文。但有时为了便利,也将ApplicationContext称为Spring容器。

通常不建议运用BeanFactory,但BeanFactory 仍然能够用于轻量级的运用程序,如移动设备或依据applet的运用程序,其间它的数据量和速度是明显。

BeanFactory与ApplicationContext的差异

BeanFactory是Spring结构的基础设施,面向Spring本身。ApplicationContext则面向运用Spring结构的开发者,几乎所有的运用场合都能够直接运用ApplicationContext,而非底层的BeanFactory。

别的,ApplicationContext的初始化和BeanFactory有一个重大的差异:

BeanFactory在初始化容器时,并未实例化Bean,直到第一次访问某个Bean时才实例目标Bean。这样,咱们就不能发现一些存在的Spring的装备问题。假如Bean的某一个特点没有注入,BeanFacotry加载后,直至第一次运用调用getBean办法才会抛出反常。

而ApplicationContext则在初始化运用上下文时就实例化所有单实例的Bean,相对应的,ApplicationContext的初始化时刻会比BeanFactory长一些。

了解了上述的底子理论知识之后,咱们就能够尝试从IoC容器傍边获取Bean目标了。

办法一:经过BeanFactory获取

经过BeanFactory来获取Bean。依据xml装备文件的时代,能够经过如下办法取得BeanFactory,再经过BeanFactory来取得对应的Bean。

BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
UserInfo userInfo = (UserInfo) beanFactory.getBean("userInfo");

有一定编程年纪的程序员,应该对此还有一些形象。这种写法估计也只会出现在陈旧的项目傍边。鉴于xml办法装备文件现已被依据注解办法所替代,一起XmlBeanFactory也被标示为抛弃。此种办法不引荐运用。

其实,不引荐的理由还有一个,在上面现已说到,尽量不要运用BeanFactory,而应该运用ApplicationContext。

办法二:经过BeanFactoryAware获取

在上面的办法中,XmlBeanFactory现已被抛弃,但能够经过其他办法来取得BeanFactory,然后再从BeanFactory中取得指定的Bean。获取BeanFactory实例最简略的办法便是完成BeanFactoryAware接口。

BeanFactoryAware接口源码:

public interface BeanFactoryAware extends Aware {
​
  /**
   * 初始化回调办法,Spring会自动将BeanFactory注入进去,接收之后即可运用BeanFactory
   */
  void setBeanFactory(BeanFactory beanFactory) throws BeansException;
}

BeanFactoryAware归于org.springframework.beans.factory.Aware根标记接口,运用setter注入来在运用程序上下文发动期间获取目标。Aware接口是回调,监听器和观察者规划形式的混合,它表明Bean有资历经过回调办法被Spring容器告诉。

这儿供给一个完好的东西类:

@Component
public class BeanFactoryHelper implements BeanFactoryAware {
​
  private static BeanFactory beanFactory;
​
  /**
   * 重写 BeanFactoryAware 接口的办法
   * @param beanFactory :参数赋值给本地特点之后即可运用 BeanFactory
   * @throws BeansException BeansException
   */
  @Override
  public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
    BeanFactoryHelper.beanFactory = beanFactory;
  }
  /**
   * 依据称号获取容器中的目标实例
   * @param beanName :注入的实例有必要现已存在容器中,不然抛反常:NoSuchBeanDefinitionException
   * @return Object
   */
  public static Object getBean(String beanName) {
    return beanFactory.getBean(beanName);
  }
  /**
   * 依据 class 获取容器中的目标实例
   * @param requiredType :被注入的有必要现已存在容器中,不然抛反常:NoSuchBeanDefinitionException
   * @param <T> Class
   * @return 目标
   */
  public static <T> T getBean(Class<T> requiredType) {
    return beanFactory.getBean(requiredType);
  }
  /**
   * 判别 spring 容器中是否包含指定称号的目标
   * @param beanName bean称号
   * @return 是否存在
   */
  public static boolean containsBean(String beanName) {
    return beanFactory.containsBean(beanName);
  }
  //其它需求皆可参考 BeanFactory 接口和它的完成类
}

在上述东西类中,便是依据BeanFactoryAware的特性,取得了BeanFactory,然后再经过BeanFactory来取得指定的Bean。

该方案满足了获取Bean的底子需求,但一起具有运用BeanFactory的缺点。依据前文介绍的BeanFactory特性,可酌情运用。

上面供给了两种依据BeanFactory容器取得Bean的办法,下面则经过ApplicationContext来获取容器中的Bean,不同的是获取ApplicationContext的办法的差异。

办法三:发动获取ApplicationContext

在项目发动时先获取ApplicationContext目标,然后将其存储在一个当地,以便后续用届时进行运用。

这儿供给两种场景的获取:

  • 依据xml装备bean的办法,适用于比较陈旧的项目,现已很少运用了;
  • 依据SpringBoot发动时获取ApplicationContext目标;

依据xml的办法完成:

// 其间applicationContext.xml 为装备容器的xml,不过现在一般很少运用了
ApplicationContext ac = new FileSystemXmlApplicationContext("applicationContext.xml");

这儿等于直接初始化容器,而且取得容器的引证。这种办法适用于采用Spring结构的独立运用程序,需求程序经过装备文件手工初始化Spring的状况。目前大多数Spring项目现已不再采用xml装备,很少运用了。

依据SpringBoot发动完成:

@SpringBootApplication
public class ExampleApplication {
​
   public static void main(String[] args) {
     // 发动时,保存上下文,并保存为静态
     ConfigurableApplicationContext ac = SpringApplication.run(ExampleApplication.class, args);
     SpringContextUtil.setAc(ac);
   }
}

对应的SpringContextUtil类如下:

public class SpringContextUtil1 {
​
   private static ApplicationContext ac;
​
   public static <T>  T getBean(String beanName, Class<T> clazz) {
     T bean = ac.getBean(beanName, clazz);
     return bean;
   }
​
   public static void setAc(ApplicationContext applicationContext){
     ac = applicationContext;
   }
}

两种办法都是在发动Spring项目时,直接获取到ApplicationContext的引证,然后将其存储到东西类傍边。在运用时,则从东西类中获取ApplicationContext容器,然后从中取得Bean目标。

办法四:经过承继ApplicationObjectSupport

此种办法依旧是先取得ApplicationContext容器,然后从中获取Bean目标,只不过是依据承继ApplicationObjectSupport类完成的。

详细完成代码:

@Component
public class SpringContextUtil extends ApplicationObjectSupport {
  public <T> T getBean(Class<T> clazz) {
    ApplicationContext ac = getApplicationContext();
    if(ac == null){
      return null;
    }
    return ac.getBean(clazz);
  }
}

留意,这儿的SpringContextUtil类需求实例化。

办法五:经过承继WebApplicationObjectSupport

WebApplicationObjectSupport是ApplicationObjectSupport的一个完成类,供给了Web相关的支持。完成原理与ApplicationObjectSupport一样。

详细完成代码如下:

@Component
public class SpringContextUtil extends WebApplicationObjectSupport {
  public <T> T getBean(Class<T> clazz) {
    ApplicationContext ac = getApplicationContext();
    if(ac == null){
      return null;
    }
    return ac.getBean(clazz);
  }
}

对照依据ApplicationObjectSupport的完成,除了承继目标不同外,没有其他差异,都是依据getApplicationContext办法来获取。

办法六:经过WebApplicationContextUtils

Spring供给了东西类WebApplicationContextUtils,经过该类可获取WebApplicationContext目标。

详细完成代码如下:

public class SpringContextUtil2 {
  public static <T> T getBean(ServletContext request, String name, Class<T> clazz){
    WebApplicationContext webApplicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(request);
    // 或者
    WebApplicationContext webApplicationContext1 = WebApplicationContextUtils.getWebApplicationContext(request);
//     webApplicationContext1.getBean(name, clazz)
    T bean = webApplicationContext.getBean(name, clazz);
    return bean;
  }
}

这个办法很常见于SpringMVC构建的Web项目中,适用于Web项目的B/S结构。

办法七:经过ApplicationContextAware

经过完成ApplicationContextAware接口,在Spring容器发动时将ApplicationContext注入进去,然后获取ApplicationContext目标,这种办法也是常见的获取Bean的一种办法,引荐运用。

详细完成代码如下:

@Component
public class SpringContextUtil3 implements ApplicationContextAware {
​
  private static ApplicationContext ac;
​
  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    ac = applicationContext;
  }
​
  public static <T> T getBean(Class<T> clazz) {
    T bean = ac.getBean(clazz);
    return bean;
  }
​
}

这种办法与前面经过BeanFactoryAware取得BeanFactory的思路一致。

办法八:经过ContextLoader

运用ContextLoader供给的getCurrentWebApplicationContext办法,也是常用的获取WebApplicationContext的一种办法。

详细完成代码如下:

WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext();
wac.getBean(beanID);

该办法常见于SpringMVC完成的Web项目中。该办法是一种不依靠于Servlet,不需求注入的办法。但是需求留意一点,在服务器发动时和Spring容器初始化时,不能经过该办法获取Spring容器。

办法九:经过BeanFactoryPostProcessor

Spring东西类,便利在非Spring办理环境中获取Bean。

@Component
public final class SpringUtils implements BeanFactoryPostProcessor{
  
   /** Spring运用上下文环境 */
   private static ConfigurableListableBeanFactory beanFactory;
​
   @Override
   public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException{
     SpringUtilsS.beanFactory = beanFactory;
   }
​
   /**
   * 获取目标
   *
   * @param name
   * @return Object 一个以所给姓名注册的bean的实例
   * @throws BeansException
   *
   */
   @SuppressWarnings("unchecked")
   public static <T> T getBean(String name) throws BeansException{
     return (T) beanFactory.getBean(name);
   }
​
   /**
   * 获取类型为requiredType的目标
   *
   * @param clz
   * @return
   * @throws BeansException
   *
   */
   public static <T> T getBean(Class<T> clz) throws BeansException{
     T result = (T) beanFactory.getBean(clz);
     return result;
   }
​
   /**
   * 假如BeanFactory包含一个与所给称号匹配的bean界说,则回来true
   *
   * @param name
   * @return boolean
   */
   public static boolean containsBean(String name){
     return beanFactory.containsBean(name);
   }
​
   /**
   * 判别以给定姓名注册的bean界说是一个singleton仍是一个prototype。 假如与给定姓名相应的bean界说没有被找到,将会抛出一个反常(NoSuchBeanDefinitionException)
   *
   * @param name
   * @return boolean
   * @throws NoSuchBeanDefinitionException
   *
   */
   public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException{
     return beanFactory.isSingleton(name);
   }
​
   /**
   * @param name
   * @return Class 注册目标的类型
   * @throws NoSuchBeanDefinitionException
   *
   */
   public static Class<?> getType(String name) throws NoSuchBeanDefinitionException{
     return beanFactory.getType(name);
   }
​
   /**
   * 假如给定的bean姓名在bean界说中有别号,则回来这些别号
   *
   * @param name
   * @return
   * @throws NoSuchBeanDefinitionException
   *
   */
   public static String[] getAliases(String name) throws NoSuchBeanDefinitionException{
     return beanFactory.getAliases(name);
   }
​
   /**
   * 获取aop署理目标
   * 
   * @param invoker
   * @return
   */
   @SuppressWarnings("unchecked")
   public static <T> T getAopProxy(T invoker){
     return (T) AopContext.currentProxy();
   }
}

其间ConfigurableListableBeanFactory接口,也归于BeanFactory的子接口。

小结

在本文中介绍了9种从Spring容器中获取Bean的办法,虽然每种办法完成各有不同,但从本质上来讲,无非便是经过BeanFactory或ApplicationContext获取Bean,只不过获取BeanFactory或ApplicationContext容器的办法不同而已。

那么,你是否意识到,学习一项技能或一个完成办法,只要把握住它的底子,无论办法如何改变,都万变不离其宗。而这儿“宗”便是IoC容器。