BeanFactory与ApplicationContext
1. 容器和接口
1.1 BeanFactory与ApplicationContext的联系与区别:
ConfigurableApplicationContext 是 ApplicationContext的子接口,而ApplicationContext接口又是 BeanFactory 的子接口。因此ConfigurableApplicationContext 接口简介继承了 BeanFactory 的接口。注意,BeanFactory 才是Spring 容器的核心实现!而ApplicationContext只是组合了BeanFactory的功能。例如 ApplicationContext 中的 getBean() 方法的就是首先获取到 BeanFactory,然后再调用中的 BeanFactory.getBean()方法
@Override
public Object getBean(String name) throws BeansException {this.assertBeanFactoryActive();return this.getBeanFactory().getBean(name);
}
编写一段代码:
@SpringBootApplication
public class A01Application {private static final Logger log = LoggerFactory.getLogger(A01Application.class);public static void main(String[] args){ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);System.out.println(context); // 在此处打断点调试}
}
就会发现,ApplicationContext对象其实包含了一个BeanFactory对象,自然也就包含了BeanFactory中定义的核心方法。
1.2 BeanFactory能做什么事情?
查看BeanFactory对外提供了哪些方法?(ctrl+f12)
在 BeanFactory 接口中定义了许多获取bean的方法,表面上只有 getBean(), 实际上控制反转,依赖注入,bean的生命周期管理以及功能都是由 BeanFactory 的实现类提供。BeanFactory 的真正实现类就是 DefaultListableBeanFactory。其中 DefaultListableBeanFactory 可以管理管理单例对象,而对单例对象的管理是在它的父类 DefaultSingletonBeanRegistry 中实现的。
该类不仅仅实现了对bean的单例管理,还有循环依赖的解决,以及三级缓存的处理。在后面依次分析。
手动编写两个组件 Component1和Component2:
@Component
public class Component1 { }package com.cherry.a01;//..............@Component
public class Component2 { }
然后通过反射的机制拿到DefaultSingletonBeanRegistry的存放单例的属性,判断这两个组件是否存在:
@SpringBootApplication
public class A01Application {public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);System.out.println(context);// 首先获取DefaultSingletonBeanRegistry类中的私有属性singletonObjects,该属性记录了Spring容器中运行时的单例Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");// 由于是似有属性,要设置该属性可访问singletonObjects.setAccessible(true);// 获取bean对象ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();Map<String, Object> map = (Map<String, Object>) singletonObjects.get(beanFactory);map.entrySet().stream().filter(e -> e.getKey().startsWith("component")).forEach(e -> {System.out.println(e.getKey() + "=" + e.getValue());});}
}
component1=com.cherry.a01.Component1@784abd3e
component2=com.cherry.a01.Component2@36c2b646
1.3 ApplicationContext有哪些扩展功能
首先查看 ApplicationContext 接口的定义:
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver { }
ApplicationContext 继承了如下四个接口:
- MessageSource:该类具备处理国际化资源的能力
- ResourcePatternResolver:该类具备资源匹配的能力
- ApplicationEventPublisher:该类具备发布事件的能力
- EnvironmentCapable:该类具备获取系统环境的能力
1.3.1 处理国际资源
首先编写通用语言翻译结果的配置文件 messages.properties
其次依次编写不同的语言翻译配置文件,以及写入如下信息:
- messahes_en.proeprties ---> hi=hello
- messages_zh.properties ---> hi=你好
- messages_ja.properties ---> hi=こんにちは
编写代码使用 ApplicationContext 对象进行翻译:
@SpringBootApplication
public class A01Application {public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);System.out.println(context.getMessage("hi", null, Locale.CHINA));System.out.println(context.getMessage("hi", null, Locale.ENGLISH));System.out.println(context.getMessage("hi", null, Locale.JAPAN));}
}
1.3.2 处理资源
该类主要用于获取各种资源,例如下面的获取resources下的application的配置文件:
@SpringBootApplication
public class A01Application {public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);Resource[] resources = context.getResources("classpath*:/application.properties");for(Resource res:resources){System.out.println(res);}}
}
file [/home/yihaoshen/Spring-Project/show/target/classes/application.properties]
1.3.3 获取当前系统资源(配置信息)
编写如下代码获取当前JDK版本以及当前服务器端口号:
@SpringBootApplication
public class A01Application {public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);System.out.println(context.getEnvironment().getProperty("JAVA_HOME"));System.out.println(context.getEnvironment().getProperty("server.port"));}
}
/opt/development_tools/jdk21/jdk-21.0.3
9000
1.3.4 发布事件(最大作用就是实现组件或功能的解偶!)
首先编写一个事件:
public class UserRegisterEvent extends ApplicationEvent {// 参数:事件源(谁发布的事件)public UserRegisterEvent(Object source) {super(source);}
}
其次再编写一个事件监听器(用于处理事件)
@Component
public class Component2 {// 编写一个监听器,用于接收事件,其参数为发布事件的类型@EventListener //该注解表明该方法专门用于监听事件public void receive(UserRegisterEvent event){System.out.println(event.toString());}
}
在ApplicationContext中使用发布事件的功能:
@SpringBootApplication
public class A01Application {public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);context.publishEvent(new UserRegisterEvent(context));}
}
com.cherry.a01.UserRegisterEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@7ef82753, started on Sat Jun 08 13:07:15 CST 2024]
2. 容器实现
2.1 BeanFactory 实现的特点
由前面可知,BeanFactory 的默认实现是DefaultListableBeanFactory,我们可以在BeanFactory中添加一些bean的定义,然后BeanFactory根据Bean的定义创建Bean对象。
关于Bean的定义,包含如下部分:
- Bean的类型(class)
- 该Bean的作用域(scope),单例还是多例
- 该Bean有无初始化方法,有无销毁方法等
首先编写Bean1, Bean2:
public class Bean1 {public Bean1(){System.out.println("构造 Bean1()");}@Autowiredprivate Bean2 bean2;public Bean2 getBean2(){System.out.println("Get Bean2对象");return bean2;}
}// ===============public class Bean2 {public Bean2(){System.out.println("构造 Bean2()");}
}
编写配置类:
@Configuration
public class Config {@Beanpublic Bean1 bean1(){return new Bean1;}@Beanpublic Bean2 bean2(){return new Bean2;}
}
首先创建一个关于Config类的BeanDefinition对象, 并设置为单例模式:
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class.getName()).setScope("singleton").getBeanDefinition();
其次将创建好的而BeanDefinition对象加入到beanFactory中,并为该beanDefineition命名:
beanFactory.registerBeanDefinition("config", beanDefinition);
完整代码如下:
@SpringBootApplication
public class TestBeanFactory {public static void main(String[] args) {DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();// 为beanFactory添加一些bean的定义,然后beanFactory根据定义创建bean对象// 创建BeanDefinition并设置为单例AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class.getName()).setScope("singleton").getBeanDefinition();//将beanDefinition注册到beanFactory中beanFactory.registerBeanDefinition("config", beanDefinition);//此时beanFactory中已经有了config bean//查看当前beanFactory中有哪些beanDefinitionString[] beanNames = beanFactory.getBeanDefinitionNames();for(String beanName:beanNames){System.out.println(beanName);}}
}
输出结果为:
config
此时我们发现,命名我们已经在Config类里面已经为bean1和bean2两个属性加上了@Bean注解,但是并没有将该两者的对象加入到容器中,换句话说,容器中并没有包含对@Bean注解标记的 BeanDefinition,由此看出 BeanFactory缺少了注解解析的能力。
此时就需要我们为beanFactory加入一些后处理器来实现支持对其它注解的解析的能力:
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
此时再来查看当前容器中包含哪些beanDefine:
config
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor //解析2Autowired注解
org.springframework.context.annotation.internalCommonAnnotationProcessor //解析@Resource注解
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
此时我们还没找到Bean1和Bean2这两个bean, 原因是我们虽然加入了上面这些处理更多功能的后处理器,但没有执行后处理器,只有执行后处理器才能呢过真正的获取到bean对象,完整代码如下:
@SpringBootApplication
public class TestBeanFactory {public static void main(String[] args) {DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();// 为beanFactory添加一些bean的定义,然后beanFactory根据定义创建bean对象// 创建BeanDefinition并设置为单例AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class.getName()).setScope("singleton").getBeanDefinition();//将beanDefinition注册到beanFactory中beanFactory.registerBeanDefinition("config", beanDefinition);//此时beanFactory中已经有了config bean//查看当前beanFactory中有哪些beanDefinition// 该方法为beanFactory添加一个后处理器,后处理器可以扩展beanFactory的功能,// 该处理器可以帮助beanFactory处理更多的注解信息(当然包括@Bean)AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);// 执行beanFactory后处理器beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().stream().forEach(beanFactoryPostProcessor -> {beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);});String[] beanNames = beanFactory.getBeanDefinitionNames();for(String beanName:beanNames){System.out.println(beanName);}}
}
运行结果如下:
config
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
bean1
bean2
此时判断bean1对象能否正常使用:
Bean1 bean1 = beanFactory.getBean(Bean1.class);
System.out.println(bean1.getBean2());
bean1
bean2
构造 Bean1()
Get Bean2对象
null
此时发现bean1.getBean2()获取bean2对象竟然为空,这是为什么?
@Autowired
private Bean2 bean2;
原因在于@Autowired注解并未没解析处理,因为2Autowired注解解析是由bean的后处理器完成的,并不是beanFactory的后处理器完成的。因此我们需要添加对@Autowired注解解析的bean后处理器,完整代码如下:
@SpringBootApplication
public class TestBeanFactory {public static void main(String[] args) {DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();// 为beanFactory添加一些bean的定义,然后beanFactory根据定义创建bean对象// 创建BeanDefinition并设置为单例AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class.getName()).setScope("singleton").getBeanDefinition();//将beanDefinition注册到beanFactory中beanFactory.registerBeanDefinition("config", beanDefinition);//此时beanFactory中已经有了config bean//查看当前beanFactory中有哪些beanDefinition// 该方法为beanFactory添加一个后处理器,后处理器可以扩展beanFactory的功能,// 该处理器可以帮助beanFactory处理更多的注解信息(当然包括@Bean)AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);// beanFactory 后处理器,补充一些对bean的定义beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().forEach(beanFactoryPostProcessor -> {beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);});// bean 后处理器:针对bean的生命周期各个阶段来扩展bean的功能,例如@Autowired解析,@Resource的解析等beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanFactory::addBeanPostProcessor);String[] beanNames = beanFactory.getBeanDefinitionNames();for(String beanName:beanNames){System.out.println(beanName);}Bean1 bean1 = beanFactory.getBean(Bean1.class);System.out.println(bean1.getBean2());}
}
此时发现bean1对象可以正常使用了!
config
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
bean1
bean2
构造 Bean1()
构造 Bean2()
Get Bean2对象
com.cherry.a02.Bean2@45752059
此外我们还发现了,容器中只包含了对bean的定义,只有需要使用某一个bean的时候没,才会根据bean定义创建bean对象供给调用者使用!当然如果我们希望在使用bean对象之前就创建好bean对象,我么可以调用如下这个方法:
beanFactory.preInstantiateSingletons(); // 提前准备好单例
关于BeanFactory的总结:
- 不会主动调用BeanFactory的后处理器
- 不会主动添加Bean的后处理器
- 不会主动初始化单例对象,只有需要用到的时候才会实例化所需要的Bean对象
关于注解排序问题:
如果我们在一个属性或方法上添加了多个注解,那么注解解析是怎么样的?
此时的解析顺序就和后处理器的添加顺序有关,谁先加入了后处理器,哪个后处理器的优先级就高,谁就先做注解的解析。
2.2 ApplicationContext 的常见实现和用法
关于ApplicationContext接口,有如下4个经典的实现:
- ClassPathXmlApplicationContext:基于classpath下的xml格式的Spring配置文件
- FileSystemXmlApplicationContext:基于磁盘路径下xml格式的Spring配置文件
- AnnotationConfigApplicationContext:基于java的配置类来创建
- AnnotationConfigServletWebServerApplicationContext:基于java的配置类来创建,用于WEB环境