spring底层原理

server/2024/10/19 18:53:36/

本文参考黑马程序员的spring底层讲解,想要更详细的可以去看视频。

另外文章会每日更新,大概持续1个月!!!每天更新一讲

这部分比较抽象,要经常复习!!!

一、BeanFactory与ApplicationContext

1、关系

我们在启动类中的代码,获取返回值就得到了ConfigurableApplicationContext类。

ConfigurableApplicationContext context = SpringApplication.run(A01.class, args);

观看下面的类图,可以发现这个类继承了ApplicationContext接口,而ApplicationContext又间接继承了BeanFactory接口。
 

注意:接口可以多继承,类不能多继承

2、到底什么是 BeanFactory?

  1. 它是 ApplicationContext 的父接口 它才是 Spring 的核心容器, 主要的 ApplicationContext 实现都【组合】了它的功能,【组合】是指 ApplicationContext 的一个重要成员变量就是 BeanFactory
  2. BeanFactory 能干点啥

    • 表面上只有 getBean

    • 实际上控制反转、基本的依赖注入、直至 Bean 的生命周期的各种功能,都由它的实现类提供

    • 例子中通过反射查看了它的成员变量 singletonObjects,内部包含了所有的单例 bean

  3. 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 方法,从而自动注册 bean1bean2

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);}}@Configurationstatic class Config {@Beanpublic TestBeanFactory.Bean1 bean1() {return new TestBeanFactory.Bean1();}@Beanpublic 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()");}@Autowiredprivate 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()");}}@Configurationstatic class Config {@Beanpublic Bean1 bean1() {return new Bean1();}@Beanpublic Bean2 bean2() {return new Bean2();}@Beanpublic Bean3 bean3() {return new Bean3();}@Beanpublic 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()");}@Autowiredprivate 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);}}@Configurationstatic class WebConfig {@Beanpublic ServletWebServerFactory servletWebServerFactory(){return new TomcatServletWebServerFactory();}@Beanpublic DispatcherServlet dispatcherServlet() {return new DispatcherServlet();}@Beanpublic DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) {return new DispatcherServletRegistrationBean(dispatcherServlet, "/");}@Bean("/hello")public Controller controller1() {return (request, response) -> {response.getWriter().print("hello");return null;};}}


http://www.ppmy.cn/server/133130.html

相关文章

自然语言处理 (NLP) 的 5 个步骤

自然语言处理 (NLP) 的 5 个步骤 引言 如今&#xff0c;我们的世界在数字化连接方面达到了前所未有的水平。信息、见解和数据不断争夺我们的注意力&#xff0c;我们不可能全部消化。对于你的企业来说&#xff0c;挑战在于了解客户和潜在客户对你的产品和服务的看法&#xff0c;…

LeetCode-四数相加-Java

一、题目 给你四个整数数组 nums1、nums2、nums3 和 nums4 &#xff0c;数组长度都是 n &#xff0c;请你计算有多少个元组 (i, j, k, l) 能满足&#xff1a; 0 < i, j, k, l < nnums1[i] nums2[j] nums3[k] nums4[l] 0 示例 1&#xff1a; 输入&#xff1a;nums1…

Vue.js 学习总结(10)—— Vue 前端项目性能优化常用技巧

1. 使用路由懒加载 在 Vue.js 应用中&#xff0c;路由懒加载可以延迟加载路由组件直到它们被需要时才加载&#xff0c;从而减少应用的初始加载时间。示例代码&#xff1a; // router/index.js import { createRouter, createWebHistory } from vue-router;const Home () >…

C++中的vector使用与实现

一、vector的使用 1.1 vector的定义 是一种类模板 template < class T, class Alloc allocator<T> > class vector; 其中的模板参数Alloc是在使用空间配置器&#xff08;内存池&#xff09;&#xff0c;并给了缺省值&#xff0c;暂时不深究 1.2遍历方式 1.…

[旧日谈]关于Qt的刷新事件频率,以及我们在Qt的框架上做实时的绘制操作时我们该关心什么。

[旧日谈]关于Qt的刷新事件频率&#xff0c;以及我们在Qt的框架上做实时的绘制操作时我们该关心什么。 最近在开发的时候&#xff0c;发现一个依赖事件来刷新渲染的控件会导致程序很容易异常和崩溃。 当程序在运行的时候&#xff0c;其实软件本身的负载并不高&#xff0c;所以…

前端js html css 基础巩固6

这样可以当做一个字典 来使用 每次 点击 键盘上的字母或数字 就可以获得 keyCode 这个 在实际应用中还是有可能使到的 所以大家可以练习一下 直接上代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta…

Linux中文件的读写过程

文件的读取过程 在Linux系统中&#xff0c;读取文件的过程主要由操作系统内核通过文件系统与存储设备的交互来完成。以下是文件读取过程的详细步骤&#xff1a; 1. 系统调用阶段 当用户程序&#xff08;如cat、less&#xff09;请求读取文件时&#xff0c;会调用系统调用&…

82.【C语言】数据结构之顺序表的初始化和销毁

目录 1.线性表 2.分类 1.静态顺序表&#xff1a;使用定长数组存储元素 代码示例(写入Seqlist.h中) 2.动态顺序表:使用与动态内存管理有关的函数 代码示例(写入Seqlist.h中) 补:数据管理的四个需求:增改删查 3.操作顺序表 1.初始化顺序表 1.不开辟空间 2.开辟空间 1…