一、容器和Bean
1.1容器接口
1.1.1BeanFacroty能做的事

?什么是BeanFactory
- 他是
ApplicationContext 的父接口 - 他才是 Spring 的核心容器,主要的
ApplicationContext 实现都【组合】了它的功能
?BeanFactory能做些什么

- 表面上只有
getBean() - 实际上IOC、基本的依赖注入、直至Bean的生命周期的各种功能,都是由他的实现类(
DefaultListableBeanFactory )提供
🖼 DefaultListableBeanFactory 继承关系图:

其中,DefaultSingletonBeanRegistry 是单例实现类:

其中singletonObjects 是所有单例的集合,因为是私有的,我们通过反射获取其中的值:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);
Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");
singletonObjects.setAccessible(true);
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
Map<String,Object> map = (Map<String,Object>)singletonObjects.get(beanFactory);
map.forEach((k,v)->{
System.out.println(k+"==="+v);
});
}
}
运行结果较长,可以自行实验
1.1.2ApplicationContext有哪些拓展功能

主要提现在这些类上
MessageSource:处理国际化资源,程序支持多种语言时,支持翻译的能力
示例:
springBoot项目里,未来方便支持多语言,会编写语言转换的文件,比如下面的

其中左边的hi,是程序中所用到的,右边的是翻译出来的值,而MessageSource 可以将值具体显示
ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.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.JAPANESE));
运行结果:
你好
hello
こんにちは
ResourcePatternResolver
查找资源路径,返回资源数组
Resource[] resources = context.getResources("classpath*:META-INF/spring.factories");
for (Resource resource : resources) {
System.out.println(resource);
}
运行结果:
URL [jar:file:/E:/environment_tools/apache-maven-3.8.1/maven-repo/org/springframework/boot/spring-boot/2.3.7.RELEASE/spring-boot-2.3.7.RELEASE.jar!/META-INF/spring.factories]
URL [jar:file:/E:/environment_tools/apache-maven-3.8.1/maven-repo/org/springframework/spring-beans/5.2.12.RELEASE/spring-beans-5.2.12.RELEASE.jar!/META-INF/spring.factories]
URL [jar:file:/E:/environment_tools/apache-maven-3.8.1/maven-repo/com/baomidou/mybatis-plus-boot-starter/3.4.2/mybatis-plus-boot-starter-3.4.2.jar!/META-INF/spring.factories]
URL [jar:file:/E:/environment_tools/apache-maven-3.8.1/maven-repo/org/springframework/boot/spring-boot-autoconfigure/2.3.7.RELEASE/spring-boot-atoconfigure-2.3.7.RELEASE.jar!/META-INF/spring.factories]
EnvironmentCapable
获取资源环境的值
System.out.println(context.getEnvironment().getProperty("java_home"));
System.out.println(context.getEnvironment().getProperty("server.port"));
运行结果:
C:\Program Files\Java\jdk-17.0.3.1
8080
ApplicationEventPublisher
事件处理(监听、跟踪),其中有三个组成部分:
- 被监听对象source(也称为事件源)
- 事件event
- 监听对象listener
我们模拟监听和发送来测试一下
public class UserRegisteredEvent extends ApplicationEvent {
public UserRegisteredEvent(Object source) {
super(source);
}
}
ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);
context.publishEvent(new UserRegisteredEvent(context));
@Slf4j
@Component
public class Receive {
@EventListener
public void listen(UserRegisteredEvent event){
log.debug("监听事件:{}",event);
}
}
监听事件com.spring.demo.UserRegisteredEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@6531a794, started on Tue Sep 27 20:24:43 CST 2022]
接下来来看发布事件最重要的功能:
1.1.3事件解耦
使用ApplicationEventPublisher可以实现事件解耦,比如注册之后需要发送邮件或者短信,但是在其他功能可能需要其他的功能,可能是变化的,所以这时再一起用耦合度就太高了
事件解耦的例子:
假如我们此时有注册之后发短信的需求,正常情况下我们可能这样写:
public void register(){
logger.debug("用户注册");
logger.debug("发短信");
}
但是我们如果有一天需要把发短信给改了,改成发邮件,如果我们一行一行改代码不就和麻烦了吗,这时我们就需要监听事件了
@Component
public class Send {
private static final Logger logger = LoggerFactory.getLogger(Receive.class);
@Autowired
private ApplicationEventPublisher context;
public void register(){
logger.debug("用户注册");
context.publishEvent(new UserRegisteredEvent(this));
}
}
@Component
public class Receive {
private static final Logger logger = LoggerFactory.getLogger(Receive.class);
@EventListener
public void listen(UserRegisteredEvent event){
logger.debug("监听事件:{}",event);
logger.debug("发送一条短信");
}
}
context.getBean(Send.class).register();
用户注册
监听事件:com.spring.demo.UserRegisteredEvent[source=com.spring.demo.Send@6ee99964]
发送一条短信
1.2容器实现
1.2.1BeanFactory实现的特点
1??例子1
运行结果的解释说明(先看实例再看这个):
该实例模拟在最原始的情况下,spring对各bean的加载情况:
- 实例1:
- 最原始的情况下,不配置BeanFactory后置解析器的情况下,仅仅只有config这个bean被输出
- 配置BeanFactory后置解析器(
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory) )之后,可以解析出一部分注解(自行打印测试),但是无法解析出自己定义的注解,比如bean1和bean2 - 继续进行后置解析器配置,此时添加可以解析
BeanFactoryPostProcessor.class ,此时会有更多的bean输出,比如自定义的bean1和bean2 - 实例2:
- 在实例1的基础上,直接运行获得的结果实际是null,实例2下面有解释说明,此时
@Autowired 是没有被解析的 - 同实例1,继续进行后置解析器配置,不过此时配置的是Bean的后置解析器
BeanPostProcessor.class ,并调用beanFactory.addBeanPostProcessor() ,给解析的内容赋值,此时运行才能发现运行结果是个实例对象,不再是null - 可以看详细的运行结果,spring容器创建出bean1这个对象之后,解析到了bean2,接着创建了bean2的容器,然后打印了bean2的无参构造
实例1:BeanFactory处理器解析一些注解
package com.spring.demo.demo02;
public class TestBeanFactory {
public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class).setScope("singleton").getBeanDefinition();
beanFactory.registerBeanDefinition("config",beanDefinition);
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().stream().forEach(s->{
s.postProcessBeanFactory(beanFactory);
});
for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
}
@Configuration
static class Config{
@Bean
public Bean1 bean1(){return new Bean1();}
@Bean
public Bean2 bean2(){return new Bean2();}
}
@Slf4j
static class Bean1{
public Bean1() {
log.info("构造Bean1()");
}
@Autowired
private Bean2 bean2;
public Bean2 getBean2(){return bean2;}
}
@Slf4j
static class Bean2{
public Bean2() {
log.info("构造Bean2()");
}
}
}
运行结果:
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
实例2:Bean后处理器解析注解
我们bean1中有对bean2的引用,此时我们打印一下
System.out.println(beanFactory.getBean(Bean1.class).getBean2());
他的结果是null,这是为什么呢?因为beanFactory并没有对该注解(@Autowired )的加载,自然无法注入,所以我们需要另一个后处理器来加载该注解
beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanFactory::addBeanPostProcessor);
System.out.println(beanFactory.getBean(Bean1.class).getBean2());
运行结果:
22:17:58.904 [main] INFO com.spring.demo.demo02.TestBeanFactory$Bean1 - 构造Bean1()
22:17:58.914 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean2' //解析到了@Autowired注解,创建bean2对象
22:17:58.915 [main] INFO com.spring.demo.demo02.TestBeanFactory$Bean2 - 构造Bean2()
com.spring.demo.demo02.TestBeanFactory$Bean2@48f2bd5b
总结:
-
beanFactory不会做到的事:
-
bean后处理器会有排序的顺序:详细看例子2
2??例子2:关于@Autowired 和@Resource
@Autowired
我们对TestBeanFactory对象进行修改,新增bean3、bean4,并在config类中进行配置,main方法内的代码和上面一样
@Configuration
static class Config{
@Bean
public Bean1 bean1(){return new Bean1();}
@Bean
public Bean2 bean2(){return new Bean2();}
@Bean
public Bean3 bean3(){return new Bean3();}
@Bean
public Bean4 bean4(){return new Bean4();}
}
@Slf4j
static class Bean1{
public Bean1() {
log.info("构造Bean1()");
}
@Autowired
private Bean2 bean2;
public Bean2 getBean2(){return bean2;}
@Autowired
private Inter inter;
public Inter getInter() {
return inter;
}
}
@Slf4j
static class Bean2{
public Bean2() {
log.info("构造Bean2()");
}
}
interface Inter {}
static class Bean3 implements Inter{}
我们在bean1内,注入的是inter,那么我们使用 @Autowired 注解,会成功的把inter注入进容器内吗?显然是不行的,因为他有两个实现类,分别是bean3和bean4,他不知道找哪一个,所以自然就找不到,那么我们把
@Autowired
private Inter inter;
@Autowired
private Inter bean3;
会成功注入吗?显然是会的,原因是@Autowired ,注解声明对象时,会先根据类型匹配,匹配失败后才会去根据名称匹配,优先匹配到名称相同的
测试:
System.out.println(beanFactory.getBean(Bean1.class).getInter());
结果:
com.spring.demo.demo02.TestBeanFactory$Bean3@30c15d8b
实验结果也验证了我们的猜想
@Resource
类似的,@Resource 和@Autowired 一样,不过@Resource 可以指定声明的bean是哪个,并且注解内的优先级比变量名高
@Resource(name = "bean4")
private Inter bean3;
运行结果也与上面一样 ,不过结果是bean4的罢了
那么如果两个注解同时加了呢?
@Autowired和@Resource
两个同时加的情况:
@Autowired
@Resource(name = "bean4")
private Inter bean3;
运行结果怎么样呢?
com.spring.demo.demo02.TestBeanFactory$Bean3@3c46e67a
没有报错,而是先注入的bean3,这是为什么呢?
因为后处理器的执行顺序,解析@Autowired 的后处理器比解析@Resource 的先执行,所以优先级就会比较高,我们可以改变一下main方法里的一段代码来看一下:
beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanPostProcessor -> {
System.out.println(">>>"+beanPostProcessor);
beanFactory.addBeanPostProcessor(beanPostProcessor);
});
运行结果:
>>>org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor@7b9a4292
>>>org.springframework.context.annotation.CommonAnnotationBeanPostProcessor@4a94ee4
当然也可以改变后处理器的解析顺序,那么@Resource 就会比@Autowried 先执行
1.2.2ApplicationContext
关于ApplicationContext的四个容器,新建TestApplication类,以下四个经典的类都是在该类下的方法
@Slf4j
public class TestApplication {
public static void main(String[] args) {
}
static class Bean1{}
static class Bean2{
private Bean1 bean1;
public void setBean1(Bean1 bean1) {
this.bean1 = bean1;
}
public Bean1 getBean1() {
return bean1;
}
}
}
- 较为经典的容器,基于classpath下xml格式的配置文件来创建
private static void testClassPathXmlApplicationContext(){
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("testClassPathXmlApplication.xml");
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
System.out.println(context.getBean(Bean2.class).getBean1());
}
在resource 路径下创建xml文件,写入
<bean id="bean1" class="com.spring.demo.demo02.TestApplication.Bean1"/>
<bean id="bean2" class="com.spring.demo.demo02.TestApplication.Bean2">
<property name="bean1" ref="bean1"/>
</bean>
测试:
testClassPathXmlApplicationContext();
结果:
bean1
bean2
com.spring.demo.demo02.TestApplication$Bean1@31190526
- 基于磁盘路径下xml格式的配置文件来创建
private static void testFileSystemXmlApplicationContext(){
FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("E:\\Demo\\JavaDemo\\SpringSourceCodeDemo\\src\\main\\resources\\testClassPathXmlApplication.xml");
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
System.out.println(context.getBean(Bean2.class).getBean1());
}
测试:
testFileSystemXmlApplicationContext()
结果:
bean1
bean2
com.spring.demo.demo02.TestApplication$Bean1@6743e411
- 较为经典的实现,基于java配置类来创建
private static void testAnnotationConfigApplicationContext(){
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(Config.class);
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
System.out.println(context.getBean(Bean2.class).getBean1());
}
@Configuration
static class Config{
@Bean
public Bean1 bean1(){
return new Bean1();
}
@Bean
public Bean2 bean2(Bean1 bean1){
Bean2 bean2 = new Bean2();
bean2.setBean1(bean1);
return bean2;
}
}
测试:
testAnnotationConfigApplicationContext();
结果:
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
testApplication.Config
bean1
bean2
com.spring.demo.demo02.TestApplication$Bean1@f2ff811
给我们加入了后置处理器,不用再手动加了,以前的比如xml方式如果要开启,就在xml内配置<context:annotation-config/> ,开启注解模式,实际上就是他给我们自动配置了后处理器
1.2.3内嵌容器、注册DispatcherServlet
- 较为经典的容器,基于java配置类来创建,用于web环境
private static void testAnnotationConfigServletWebServerApplicationContext(){
AnnotationConfigServletWebServerApplicationContext context =
new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
}
@Configuration
static class WebConfig{
@Bean
public ServletWebServerFactory servletWebServerFactory(){
return new TomcatServletWebServerFactory();
}
@Bean
public DispatcherServlet dispatcherServlet(){
return new DispatcherServlet();
}
@Bean
public DispatcherServletRegistrationBean registrationBean( DispatcherServlet dispatcherServlet){
return new DispatcherServletRegistrationBean(dispatcherServlet,"/");
}
@Bean("/hello")
public Controller controller1(){
return (request,response)->{
response.getWriter().print("hello");
return null;
};
}
测试:
testAnnotationConfigServletWebServerApplicationContext();
结果:
运行成功之后,打开浏览器,输入localhost:8080/hello ,可以看到页面会返回一个hello

web服务的一些说明:
- 前三个bean是必须的,他帮我们创建spring内置的tomcat容器,得以运行web项目
- 第四个bean实际上类似web中的
@RequestMapping() ,当以/ 开头时,就是指定的路径 - 实际上,springboot的web应用层内的注解,大多数底层都是这些,只是帮我们封装好了
|