Spring 最全Bean的加载和获取方式整理

目录

  • 一、 前言
  • 二、Bean加载的九种方式
    • 1. XML配置方式
    • 2. XML+注解配置方式
    • 3. 注解方式
    • 4. 使用@Bean方式
    • 5. 使用@Import方式
    • 6. 容器初始化完毕后注入bean
    • 7. 实现ImportSelector接口
    • 8. 实现ImportBeanDefinitionRegistrar接口
    • 9. 实现BeanDefinitionRegistryPostProcessor接口
  • 三、Bean获取的九种方式
    • 1. 通过BeanFactory获取
    • 2. 启动获取ApplicationContext
    • 3. 实现BeanFactoryAware接口
    • 4. 实现ApplicationContextAware接口
    • 5. 通过继承ApplicationObjectSupport
    • 6. 通过继承WebApplicationObjectSupport
    • 7. 通过BeanFactoryPostProcessor
    • 8. 通过WebApplicationContextUtils
    • 9. 通过ContextLoader

一、 前言

Spring Framework是一个强大且广泛使用的Java应用程序框架,它提供了众多功能和工具,其中之一就是Spring容器。Spring容器是Spring应用程序的核心,它负责管理和维护对象(通常称为"Bean")的生命周期。在Spring中,Bean的加载和获取是常见的操作,本文将总结Spring Bean的加载和获取方式,以帮助开发者更好地理解和使用Spring框架。

二、Bean加载的九种方式

1. XML配置方式

在Spring中,最传统的方式是使用XML配置文件定义Bean。通过在XML配置文件中声明Bean的定义,Spring容器会在应用程序启动时加载这些Bean并管理它们的生命周期。以下是一个简单的XML配置示例:


<context:component-scan base-package="com.fd.spring"/>


<bean class="com.fd.spring.domain.User" id="user"/>

测试:

public static void main( String[] args )
    {

        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");

        System.out.println(applicationContext.getBean(User.class));

    }

在这里插入图片描述

2. XML+注解配置方式

Bean类添加自动注入注解

@Component // 自动注入注解
public class User {
}

spring.xml配置文件


<context:component-scan base-package="com.fd.spring"/>

测试结果:
在这里插入图片描述

3. 注解方式

创建配置类

@Configuration
@ComponentScan(basePackages = "com.fd.spring") // 开启注解扫描
public class SpringConfig {
}

测试方法:

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);

System.out.println(applicationContext.getBean(User.class));

测试结果:
在这里插入图片描述

4. 使用@Bean方式

使用@Bean加载第三方bean,并将所在类定义为配置类或Bean

@Configuration
public class SpringConfig1 {

    @Bean
    public Book book() {
        return new Book();
    }

}

测试方法:

AnnotationConfigApplicationContext applicationContext1 = new AnnotationConfigApplicationContext(SpringConfig1.class);

for (String name : applicationContext1.getBeanDefinitionNames()) {
    System.out.println(name);
}

Spring 最全Bean的加载和获取方式整理_第1张图片
扩展:

根据条件确认是否加载Bean
引入依赖:

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-autoconfigureartifactId>
dependency>
@Configuration
@Import(User.class)
public class SpringConfig1 {

    @Bean
    public Book book() {
        return new Book();
    }

    @Bean
    @ConditionalOnClass(Mouse.class) // 存在Mouse类就加载Cat
    public Cat tom() {
        return new Cat();
    }

    @Bean
    @ConditionalOnMissingClass("com.fd.spring.domain.Mouse") // 不存在Mouse类就加载Dog
    public Dog dog() {
        return new Dog();
    }

    @Bean
    @ConditionalOnBean(User.class) // 容器存在User bean则加载Food
    public Food food() {
        return new Food();
    }

}

测试结果:
Spring 最全Bean的加载和获取方式整理_第2张图片

类似注解:
@ConditionalOnBean(仅仅在当前上下文中存在某个对象时,才会实例化一个Bean)
@ConditionalOnClass(某个class位于类路径上,才会实例化一个Bean)
@ConditionalOnExpression(当表达式为true的时候,才会实例化一个Bean)
@ConditionalOnMissingBean(仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean)
@ConditionalOnMissingClass(某个class类路径上不存在的时候,才会实例化一个Bean)
@ConditionalOnNotWebApplication(不是web应用)

5. 使用@Import方式

使用@Import注解导入要注入的bean对应的字节码,

@Import(User.class)
public class SpringConfig2 {

}

被导入的bean无需使用注解声明为bean,此形式可以有效的降低源代码与Spring技术的耦合度,在spring技术底层及诸多框架的整合中大量使用

public class User {
}

测试:

AnnotationConfigApplicationContext applicationContext2 = new AnnotationConfigApplicationContext(SpringConfig2.class);

for (String name : applicationContext2.getBeanDefinitionNames()) {
    System.out.println(name);
}

测试结果:
Spring 最全Bean的加载和获取方式整理_第3张图片

使用@Import注解导入配置类,会把配置类内的bean一起加载

@Import(SpringConfig1.class)
public class SpringConfig2 {

}

测试结果:
Spring 最全Bean的加载和获取方式整理_第4张图片

6. 容器初始化完毕后注入bean

使用上下文对象在容器初始化完毕后注入bean

AnnotationConfigApplicationContext applicationContext2 = new AnnotationConfigApplicationContext(SpringConfig2.class);
// 手动注册
applicationContext2.register(Dog.class);

for (String name : applicationContext2.getBeanDefinitionNames()) {
    System.out.println(name);
}

测试结果:
Spring 最全Bean的加载和获取方式整理_第5张图片

7. 实现ImportSelector接口

导入实现了ImportSelector接口的类,实现对导入源的编程式处理(动态加载)

public class MySelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 判断导入类元数据是否存在XX注解
        boolean flag = importingClassMetadata.hasAnnotation("com.fd.spring.annotation.MyAnnotation");
        // 如果存在就导入User类,否则导入Book类
        if (flag) {
            return new String[] {"com.fd.spring.domain.User"};
        } else {
            return new String[] {"com.fd.spring.domain.Book"};
        }
    }
}

自定义注解

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}
@MyAnnotation
@Import(MySelector.class)
public class SpringConfig3 {

}

测试方法:

AnnotationConfigApplicationContext applicationContext3 = new AnnotationConfigApplicationContext(SpringConfig3.class);

for (String name : applicationContext3.getBeanDefinitionNames()) {
    System.out.println(name);
}

测试结果:
Spring 最全Bean的加载和获取方式整理_第6张图片

取消自定义注解

// @MyAnnotation
@Import(MySelector.class)
public class SpringConfig3 {

}

测试结果:
Spring 最全Bean的加载和获取方式整理_第7张图片

扩展:

通过ImportSelector接口,我们就可以根据条件判断确认是否加载Bean

public class MySelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        try {
            // 如果存在Mouse类则加载Cat类
            Class<?> clazz = Class.forName("com.fd.spring.domain.Mouse");

            return new String[] {"com.fd.spring.domain.Cat"};

        } catch (ClassNotFoundException e) {
            return new String[]{};
        }
    }
}

8. 实现ImportBeanDefinitionRegistrar接口

导入实现了ImportBeanDefinitionRegistrar接口的类,通过BeanDefinition的注册器注册实名bean,实现对容器中bean的裁定,例如对现有bean的覆盖,进而达成不修改源代码的情况下更换实现的效果

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(Book.class).getBeanDefinition();

        registry.registerBeanDefinition("book1",beanDefinition);
    }
}

配置类

@Import(MyImportBeanDefinitionRegistrar.class)
public class SpringConfig4 {
    
}

测试方法:

AnnotationConfigApplicationContext applicationContext4 = new AnnotationConfigApplicationContext(SpringConfig4.class);

for (String name : applicationContext4.getBeanDefinitionNames()) {
  	System.out.println(name);
}

测试结果:
Spring 最全Bean的加载和获取方式整理_第8张图片

9. 实现BeanDefinitionRegistryPostProcessor接口

导入实现了BeanDefinitionRegistryPostProcessor接口的类,通过BeanDefinition的注册器注册实名bean,实现对容器中bean的最终裁定

public class MyBeanDefinitionRegisterPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {

        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(Dog.class).getBeanDefinition();
        beanDefinitionRegistry.registerBeanDefinition("dog", beanDefinition);
    }
}

配置类

@Import(MyBeanDefinitionRegisterPostProcessor.class)
public class SpringConfig5 {

}

测试结果:
Spring 最全Bean的加载和获取方式整理_第9张图片

三、Bean获取的九种方式

在Spring中,Bean的实例化、定位、配置应用程序中的对象及建立对象间的依赖关系,都是在IoC容器中进行的。因此,要在Spring中获取Bean,本质上就是从IoC容器当中获取Bean。

在Spring中,BeanFactory是IoC容器的实际代表者,该接口提供了IoC容器最基本功能。同时,Spring还提供了另外一种类型的容器:ApplicationContext容器。

ApplicationContext容器包括BeanFactory容器的所有功能(BeanFactory的子接口),提供了更多面向应用的功能,它提供了国际化支持和框架事件体系,更易于创建实际应用。

一般情况,我们称BeanFactory为IoC容器,称ApplicationContext为应用上下文。但有时为了方便,也将ApplicationContext称为Spring容器。

通常不建议使用BeanFactory,但BeanFactory 仍然可以用于轻量级的应用程序,如移动设备或基于applet的应用程序,其中它的数据量和速度是显著。

1. 通过BeanFactory获取

通过BeanFactory来获取Bean。基于xml配置文件的时代,可以通过如下方式获得BeanFactory,再通过BeanFactory来获得对应的Bean。这种写法估计也只会出现在古老的项目当中。

BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring.xml"));

beanFactory.getBean(User.class);

2. 启动获取ApplicationContext

在项目启动时先获取ApplicationContext对象,然后将其存储在一个地方,以便后续用到时进行使用。这里只介绍基于SpringBoot启动实现:

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        // 启动时,保存上下文,并保存为静态
        ConfigurableApplicationContext ac = SpringApplication.run(Application.class, args);
        SpringContextUtil.setApplicationContext(ac);
    }
}

对应的SpringContextUtil类如下:

public class SpringContextUtil {

    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 setApplicationContext(ApplicationContext applicationContext){
        ac = applicationContext;
    }
}

启动Spring项目时,直接获取到ApplicationContext的引用,然后将其存储到工具类当中。在使用时,则从工具类中获取ApplicationContext容器,进而从中获得Bean对象。

3. 实现BeanFactoryAware接口

在上面的方式中,XmlBeanFactory已经被废弃,但可以通过其他方式来获得BeanFactory,然后再从BeanFactory中获得指定的Bean。获取BeanFactory实例最简单的方式就是实现BeanFactoryAware接口。

@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  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。

4. 实现ApplicationContextAware接口

通过实现ApplicationContextAware接口,在Spring容器启动时将ApplicationContext注入进去,从而获取ApplicationContext对象,这种方法也是常见的获取Bean的一种方式,推荐使用。

@Component
public class SpringContextUtils 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;
	 }

}

5. 通过继承ApplicationObjectSupport

此种方式依旧是先获得ApplicationContext容器,然后从中获取Bean对象,只不过是基于继承ApplicationObjectSupport类实现的。

@Component
public class SpringContextUtils extends ApplicationObjectSupport {

    private ApplicationContext applicationContext;

    public <T> T getBean(Class<T> clazz) {

        if (applicationContext == null) {
            applicationContext = getApplicationContext();
        }

        if (applicationContext == null) {
            return null;
        } else {
            return applicationContext.getBean(clazz);
        }
    }
}

ApplicationObjectSupport类图如下,我们看到它实现了ApplicationContextAware接口,在Spring容器初始化过程中回调方法setApplicationContext来完成ApplicationContext的赋值。
Spring 最全Bean的加载和获取方式整理_第10张图片

6. 通过继承WebApplicationObjectSupport

WebApplicationObjectSupport是ApplicationObjectSupport的一个实现类,提供了Web相关的支持。实现原理与ApplicationObjectSupport一样。

@Component
public class SpringContextUtils extends WebApplicationObjectSupport {

	private ApplicationContext applicationContext;

    public <T> T getBean(Class<T> clazz) {

        if (applicationContext == null) {
            applicationContext = getApplicationContext();
        }

        if (applicationContext == null) {
            return null;
        } else {
            return applicationContext.getBean(clazz);
        }
    }
}

类图如下:
Spring 最全Bean的加载和获取方式整理_第11张图片

7. 通过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();
    }
}

8. 通过WebApplicationContextUtils

Spring提供了工具类WebApplicationContextUtils,通过该类可获取WebApplicationContext对象。
这个方法很常见于SpringMVC构建的Web项目中,适用于Web项目的B/S结构。下面两个工具方式的区别是,前者在获取失败时抛出异常,后者返回null。

public class SpringContextUtils2  {

    private ApplicationContext applicationContext;

    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);

        return webApplicationContext.getBean(name, clazz);
    }

}

9. 通过ContextLoader

使用ContextLoader提供的getCurrentWebApplicationContext方法,也是常用的获取WebApplicationContext的一种方法。

具体实现代码如下:

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

该方法常见于SpringMVC实现的Web项目中。该方式是一种不依赖于Servlet,不需要注入的方式。但是需要注意一点,在服务器启动时和Spring容器初始化时,不能通过该方法获取Spring容器。

总结:

虽然,spring提供了好几种方法(3、4、5、6、7)可以实现在普通的类中继承或实现相应的类或接口来获取spring 的ApplicationContext对象,但是在使用是一定要注意实现了这些类或接口的普通java类一定要在Spring 的配置文件application-context.xml文件中进行配置或注解配置(注入容器)。否则获取的ApplicationContext对象将为null。

你可能感兴趣的:(Spring全家桶,1024程序员节,spring,java)