本文参考黑马程序员的spring底层讲解,想要更详细的可以去看视频。
另外文章会每日更新,大概持续1个月!!!每天更新一讲
这部分比较抽象,要经常复习!!!
一、BeanFactory与ApplicationContext
1、关系
我们在启动类中的代码,获取返回值就得到了ConfigurableApplicationContext类。
ConfigurableApplicationContext context = SpringApplication.run(A01.class, args);
观看下面的类图,可以发现这个类继承了ApplicationContext接口,而ApplicationContext又间接继承了BeanFactory接口。
注意:接口可以多继承,类不能多继承
2、到底什么是 BeanFactory?
- 它是 ApplicationContext 的父接口 它才是 Spring 的核心容器, 主要的 ApplicationContext 实现都【组合】了它的功能,【组合】是指 ApplicationContext 的一个重要成员变量就是 BeanFactory
-
BeanFactory 能干点啥
-
表面上只有 getBean
-
实际上控制反转、基本的依赖注入、直至 Bean 的生命周期的各种功能,都由它的实现类提供
-
例子中通过反射查看了它的成员变量 singletonObjects,内部包含了所有的单例 bean
-
-
ApplicationContext 比 BeanFactory 多点啥
-
ApplicationContext 组合并扩展了 BeanFactory 的功能
-
国际化、通配符方式获取一组 Resource 资源、整合 Environment 环境、事件发布与监听
-
新学一种代码之间解耦途径,事件解耦。 (注意:这里的 通知机制是同步的,主要是用于解耦,而不是异步通知)
-
/*
BeanFactory 与 ApplicationContext 的区别
*/
@SpringBootApplication
public class A01 {
private static final Logger log = LoggerFactory.getLogger(A01.class);
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {
ConfigurableApplicationContext context = SpringApplication.run(A01.class, args);
/*
1. 到底什么是 BeanFactory
- 它是 ApplicationContext 的父接口
- 它才是 Spring 的核心容器, 主要的 ApplicationContext 实现都【组合】了它的功能
*/
System.out.println(context);
/*
2. BeanFactory 能干点啥
- 表面上只有 getBean
- 实际上控制反转、基本的依赖注入、直至 Bean 的生命周期的各种功能, 都由它的实现类提供
*/
Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");
singletonObjects.setAccessible(true);
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());
});
/*
3. ApplicationContext 比 BeanFactory 多点啥
*/
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));
Resource[] resources = context.getResources("classpath*:META-INF/spring.factories");
for (Resource resource : resources) {
System.out.println(resource);
}
System.out.println(context.getEnvironment().getProperty("java_home"));
System.out.println(context.getEnvironment().getProperty("server.port"));
// context.publishEvent(new UserRegisteredEvent(context));
context.getBean(Component1.class).register();
/*
4. 学到了什么
a. BeanFactory 与 ApplicationContext 并不仅仅是简单接口继承的关系, ApplicationContext 组合并扩展了 BeanFactory 的功能
b. 又新学一种代码之间解耦途径
练习:完成用户注册与发送短信之间的解耦, 用事件方式、和 AOP 方式分别实现
*/
}
}
二、beanFactory的实现
1、第一部分(beanFactory的后处理器)
首先我们看这段代码,运行之后为什么没有打印被@Bean修饰的两个bean呢。你没有看到通过 @Bean
修饰的两个 bean1()
和 bean2()
打印出来,是因为当前的代码只是注册了一个 Config
配置类的 BeanDefinition
,而没有通过 Spring 容器去解析 @Configuration
配置类中的 @Bean
方法,从而自动注册 bean1
和 bean2
。
public static void main(String[] args) {
//1、DefaultListableBeanFactory 是 Spring 中最常用的 BeanFactory 实现类
DefaultListableBeanFactory beanFactory=new DefaultListableBeanFactory();
//2、定义和注册 Bean 定义 genericBeanDefinition的参数代表要定义bean的类型
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class).setScope("singleton").getBeanDefinition();
beanFactory.registerBeanDefinition("config",beanDefinition);//将bean注册到bean工厂
for (String name : beanFactory.getBeanDefinitionNames()) {
System.out.println(name);
}
}
@Configuration
static class Config {
@Bean
public TestBeanFactory.Bean1 bean1() {
return new TestBeanFactory.Bean1();
}
@Bean
public TestBeanFactory.Bean2 bean2() {
return new TestBeanFactory.Bean2();
}
}
我们应该向bean工厂加上一些后处理器,让后处理器去完成后续的任务
// 给 BeanFactory 添加一些常用的后处理器 AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
添加这行代码之后,再次打印bean工厂中的bean定义,我们会发现多了几个后处理器。bean1和bean2还没有看到,这是因为这些后处理器还没执行
随后我们要让这些后处理器执行起来,
// BeanFactory 后处理器主要功能,补充了一些 bean 定义 beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().forEach( e->e.postProcessBeanFactory(beanFactory) );
我详细来说明
①beanFactory.getBeansOfType(BeanFactoryPostProcessor.class)获取到name为key,后处理器(BeanFactoryPostProcessor)为value的map集合。
②调用values方法获取到map集合中里面的后处理器封装成一个集合。
③然后调用forEach挨个执行处理器的代码。
这段代码的功能是对所有注册的 BeanFactoryPostProcessor
进行调用。
2、第二部分(bean的后处理器)
准备了下面的两个bean,其中bean1依赖注入bean2。
static class Bean1 {
private static final Logger log = LoggerFactory.getLogger(Bean1.class);
public Bean1() {
log.debug("构造 Bean1()");
}
@Autowired
private Bean2 bean2;
public Bean2 getBean2() {
return bean2;
}
@Autowired
@Resource(name = "bean4")
private Inter bean3;
public Inter getInter() {
return bean3;
}
}
static class Bean2 {
private static final Logger log = LoggerFactory.getLogger(Bean2.class);
public Bean2() {
log.debug("构造 Bean2()");
}
}
@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();
}
}
interface Inter {
}
static class Bean3 implements Inter {
}
static class Bean4 implements Inter {
}
我们执行前面的代码,并添加
System.out.println(beanFactory.getBean(Bean1.class).getBean2());
这样就会触发bean1的构造方法,但是bean2没有获取到
但是我们发现,并没有出现构造bean2的字样。证明了前面的beanFactory添加的后处理器并没有实现@Autowired功能,而且要注意的是 这些bean都是用到 了才会加载。如果我们没有beanFactory.getBean(Bean1.class); bean1也不会被构造。
//执行bean的后处理器的逻辑
beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(
beanPostProcessor-> beanFactory.addBeanPostProcessor(beanPostProcessor)
);
这样就能得到bean2的构造
beanFactory的bean加载默认是懒汉式的,我们可以设置为饿汉式,就是全部单例bean都构造出来
beanFactory.preInstantiateSingletons();
这样就用等待getBean才加载了
后处理器的排序
准备代码:(其他代码跟上面重复的就不写出来了)
interface Inter {
}
static class Bean3 implements Inter {
}
static class Bean4 implements Inter {
}
static class Bean1 {
private static final Logger log = LoggerFactory.getLogger(Bean1.class);
public Bean1() {
log.debug("构造 Bean1()");
}
@Autowired
private Bean2 bean2;
public Bean2 getBean2() {
return bean2;
}
//@Autowired
@Resource(name = "bean4")
private Inter bean3;
public Inter getInter() {
return bean3;
}
}
如果我们执行
System.out.println(beanFactory.getBean(Bean1.class).getInter());
打印的是
我们会发现和@Resource的name指定的类型一致
如果我们写成(这种情况实际 开发中不会遇到,但能帮我们理解背后的原理)
@Autowired @Resource(name = "bean4") private Inter bean3;
那生效的是bean3还是bean4呢
打印结果是com.itheima.a02.TestBeanFactory$Bean3@663c9e7a
为什么会这样呢? 这是和添加bean后处理器的顺序有关。
添加比较器的情况
如果我们加上个比较器再进行bean处理器的添加
beanFactory.getBeansOfType(BeanPostProcessor.class).values().stream() .sorted(beanFactory.getDependencyComparator()) .forEach(beanPostProcessor -> { System.out.println(">>>>" + beanPostProcessor); beanFactory.addBeanPostProcessor(beanPostProcessor); });
当我们执行代码后发现是@Resource,翻看源码发现这两个注解的类中有一个Order字段,
越小的优先级越低(这里黑马的老师没有讲清楚,因为sort是升序排序,所以order越小排在钱买你,所以优先级才更高)
而
Common -->Ordered.LOWEST_PRECEDENCE - 3)); Autowired -->Ordered.LOWEST_PRECEDENCE - 2));
可以看出Common(也就是Resource的那个类的order更小)
三、Application的实现
下面是几种Application的实现,分别是spring加载bean的方式和springboot加载bean的方式
1、传统bean的注入 (使用xml的方式) --了解即可
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("b01.xml"); for (String name : context.getBeanDefinitionNames()) { System.out.println(name); }
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bean1" class="com.itheima.a39.A39_1.Bean1"/>
</beans>
还有一种是使用文件路径进行加载bean,只是写法不同。
private static void testFileSystemXmlApplicationContext() {
FileSystemXmlApplicationContext context =
new FileSystemXmlApplicationContext(
"src\\main\\resources\\a02.xml");
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
System.out.println(context.getBean(Bean2.class).getBean1());
}
2、解析xml文件注入bean的原理
底层还是使用
DefaultListableBeanFactory bean工厂
然后使用XmlBeanDefinitionReader 来读取xml文件里面的bean信息,加载到bean工厂中。
(使用file导入bean的方式也是一样,只是换个方法)
public static void main(String[] args) {
//获取bean工厂
DefaultListableBeanFactory beanFactory=new DefaultListableBeanFactory();
System.out.println("读取前=========================");
for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
XmlBeanDefinitionReader reader=new XmlBeanDefinitionReader(beanFactory); //专门用于读取xml里面的bean信息的类 并将bean工厂传给他
reader.loadBeanDefinitions(new ClassPathResource("a02.xml"));//指定xml文件的路径
System.out.println("读取后=========================");
for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
}
3、AnnotationConfigApplicationContext
这种是springboot加载bean的方式。
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());
}
不单只bean加载进来,甚至Config类都加载到了IOC容器中还有一系列后处理器
这里提一嘴,在使用传统加载bean的方式中(使用xml方式),我们只需要加上下面的这个配置,就能实现加载一系列后处理器的效果。以前老师说的扫描bean.
4、AnnotationConfigServletWebServerApplicationContext
这个是加载web应用所需要的一些bean. 这集老师的讲解让我大为震撼。
黑马满一航老师讲解
就是需要内嵌的Tomcat和DispatcherServlet这两个bean,还有将他们进行绑定的bean。这三个bean是web应用中最基础的。
DispatcherServlet是前端控制器,所有http请求都必须经过它。
// ⬇️较为经典的容器, 基于 java 配置类来创建, 用于 web 环境
private static void testAnnotationConfigServletWebServerApplicationContext() {
AnnotationConfigServletWebServerApplicationContext context =
new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
}
@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;
};
}
}