目录
- 一、 前言
- 二、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-->
<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);
}
扩展:
根据条件确认是否加载Bean
引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</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();
}
}
测试结果:
类似注解:
@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);
}
测试结果:
使用@Import注解导入配置类,会把配置类内的bean一起加载
@Import(SpringConfig1.class)
public class SpringConfig2 {
}
测试结果:
6. 容器初始化完毕后注入bean
使用上下文对象在容器初始化完毕后注入bean
AnnotationConfigApplicationContext applicationContext2 = new AnnotationConfigApplicationContext(SpringConfig2.class);
// 手动注册
applicationContext2.register(Dog.class);
for (String name : applicationContext2.getBeanDefinitionNames()) {
System.out.println(name);
}
测试结果:
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);
}
测试结果:
取消自定义注解
// @MyAnnotation
@Import(MySelector.class)
public class SpringConfig3 {
}
测试结果:
扩展:
通过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);
}
测试结果:
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 {
}
测试结果:
三、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 <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。
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的赋值。
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);
}
}
}
类图如下:
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。