Skip to content

Spring的核心基石

IoC、DI、AOP

Inversion of Control(控制反转)

控制反转是一种设计思想, 类A需要创建类B时,传统方式在类A里new B(),B的生命周期由A控制,IOC使控制权交到Spring容器里, 类A等待Spring将装配好的类B传递给它。

Dependency Injection(依赖注入)

依赖注入是控制反转的具体实现方式,Spring容器在运行期间,动态地将依赖关系注入到对象中。

Bean
由Spring IoC容器实例化、组装和管理的对象,依赖注入的对象

IoC容器
应用启动时,容器会读取配置(注解或XML),将所有需要的Bean实例化并放在仓库里,当某个组件需要调用其他组件时,直接从仓库里按需分配

Aspect-Oriented Programming(面向切面编程)

AOP用来处理与业务逻辑无关,复用量大的代码,比如:日志记录、鉴权、数据库事务管理。
AOP的底层原理是Dynamic Proxy(动态代理),Spring会在运行时为原本的Bean生成一个“代理对象”,外部调用业务方法时,实际上调用了代理对象,代理对象在执行核心逻辑之前或之后,自动插入日志、事务等“切面”逻辑。

注解

  • @Configuration 告诉Spring,这个类是个配置中心
  • @ComponectScan 告诉Spring,去哪里寻找被打上标记的组件
  • @Service 将类实例化为业务逻辑层的Bean
  • @Controller 将类实例化为接口控制层的Bean
  • @RestController
  • @Autowired 依赖注入 用于一个类的某个属性前
  • @Qualifier 当Spring注入时找到多个Bean类时,Qualifier注解说明精确匹配 example:@Qualifier("class")
  • @Bean(name={"string"}) 声明在方法前,表明方法返回一个名为string的Bean对象,@Qualifier可以调用
  • @SpringBootApplication

使用Spring Context构建IoC容器

手写纯Spring容器,添加SpringContext依赖
创建业务类:

  • 接口MessageService,包含sendMessage()方法
  • 实现类EmailMessageService,实现sendMessage()方法,打印一行输出
  • UserController类,内部包含一个MessageService类型的属性

注解驱动:

  • EmailMessageService上添加@service注解
  • UserController上使用@Controller注解,并在MessageService属性上实现@AutoWired依赖注入

配置与启动

  • 创建AppConfig配置类,添加@Configuration,@ComponectScan注解便于Spring扫描创建Bean类
  • 创建带main方法的启动类,使用AnnotationConfigApplicationContext的构造函数加载AppConfig.class,从容器中获取UserController的Bean(使用'getBean(UserController.class)'),并调用方法

Spring容器只包含对象的初始化问题。
SpringBoot简化配置过程 SpringApplication.run(Application.class, args);

启动与生命周期回调

@SpringBootApplication    
SpringApplication.run(Application.class, args);

SpringApplication的run方法的执行过程如下:
1.准备环境,事件监听器等前期工作

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext,applicationArguments);

2.创建IoC容器上下文

ConfigurableApplicationContext content = createApplicationContext();

3.准备上下文并刷新容器(完成包扫描、Bean的实例化、@Autowired注入)

refreshContext(content);

4.容器刷新后处理逻辑

afterRefresh(context, applicationArguments);

5.调用所有的Runners

callRunners(context, applicationArguments);

6.启动完成,返回上下文

return context;

refreshContext() Spring容器启动,根据注解将类实例化并放入容器,完成依赖注入
callRunners() SpringBoot特有的扩展逻辑。容器刷新完后,SpringBoot会主动向容器索要特定类型的Bean并执行它们。如实现了CommandLineRunnerApplicationRunner函数式接口的Bean类,在SpringBoot即将启动完成的最后一步被调用。

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList<>();
    runners.addAll(context.getBeanOfType(ApplicationRunner.class).values());
    runners.addAll(context.getBeanOfType(CommandLineRunner.class).values());
    //按Order注解排序执行
    AnnotationAwareOrderComparator.sort(runners);
    for (Object runner : new LinkedHashSet<>(runners)) {
        if(runner instanceof ApplicationRunner applicationRunner) {
            applicationRunner.run(args);
        }
        if(runner instanceof CommandLineRunner commandLineRunner) {
            commandLineRunner.run(args.getSourceArgs());
        }
    }
}

自动装配机制

在pom.xml里引入依赖,SpringBoot就会自动配置好相应的组件,而无需手动写配置类。
自动装配内置Tomcat服务器:

  1. 依赖传递:准备ClassPath
    在pom.xml中引入spring-boot-starter-web时,Maven的依赖传递机制会自动下载并引入spring-boot-starter-tomcat.此时,Tomcat相关的类(如org.apache.catalina.startup.Tomcat)存在于 项目的ClassPath(类路径)中。
  2. 读取进口清单(SPI机制)
    应用启动,AutoConfigurationImportSelector开始工作,它去类路径下所有的JAR包中寻找特定文件,文件路径为:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
    (文件位于spring-boot-autoconfigure-xxx.jar)
  3. 加载自动配置类
    文件中列出上百个自动配置类的全限定名。其中有一个专门负责Web服务器配置的类:org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,引擎会将这个类加载进内存并进行条件判断。
  4. 条件注解的精准过滤(@Conditional)
    引擎查看ServletWebServerFactoryAutoConfiguration类,根据注解进行条件判断:
  • @ConditionalOnClass({ServletRequest.class}):要求类路径下必须有Servlet相关的类
  • @ConditionalOnWebApplication:要求当前是一个Web应用
  • 内部的Tomcat配置类@ConditionalOnClass({Tomcat.class, UpgradeProtocol.class}):要求类路径下必须有Tomcat的核心类
  1. 动态注册Bean
    由于第一步将Tomcat类存在了类路径中,因此条件判断通过,SpringBoot自动配置Tomcat所需的Bean(TomcatServletWebServerFactory)注册到Spring IoC容器。容器在刷新时,会调用这个工厂Bean来启动真实的内嵌Tomcat进程。
    spring-boot-starter-web内嵌Tomcat服务器,当在依赖中强制禁用exclusion spring-boot-starter-tomcat依赖时,可以引入spring-boot-starter-undertow依赖从而启动undertow服务器,因为spring-boot-starter-web中不止含有tomcat,还包含undertow,Jetty的@Conditional嵌套类,条件注解就会过滤掉tomcat但是留下undertow,启动undertow服务器。

当在SpringBoot项目下的Application.yml文件下写入:

server:
    port:8090

服务器将会运行在localhost:8090端口
这都依赖于@ConfigurationProperties注解,以内置Tomcat为例,SpringBoot内置的是第三方服务器,无法直接更改源码,因此采用中间人代理的机制。先创建一个带有@ConfigurationProperties注解的类,用于读取application.yml内的键值对配置信息并赋值给自身字段,在SpringBoot中这个类为org.springbootframework.boot.autoconfigure.web.ServerProperties:

@ConfigurationProperties(prefix="server",ignoreUnknownFields = true)
public class ServerProperties{
    private Integer port;
    // address,servlet,getter,setter等等属性
}

SpringBoot启动时,会读取application.yml,利用Java反射,调用ServerProperties的setPort(8090)方法,将数值注入到这个Bean中。
接下来就是Customizer机制(定制器桥接),8090存在ServerProperties而Tomcat不可见。SpringBoot会在底层注册一个ServletWebServerFactoryCustomizer的组件,这个组件在IoC容器准备创建Tomcat工厂之前拦截下来,从ServerProperties中取出port的值,然后调用Undertow的API,factory.setPort(serverProperties.getPort());最终Tomcat服务器运行在8090端口。

内嵌容器与Web

在传统的Java Web开发中,需要在web.xml中配置一个前端控制器(DispatcherServlet),由它来拦截所有的HTTP请求,并分发给对应的Controller。传统Java Web利用Undertow的分发能力,在web.xml中写<servlet-mapping>来配置拦截路径,多个路径导致代码膨胀且Servlet API原生只能处理String类型的参数,无法自动把JSON转化为Java对象和参数校验。

Spring MVC

为解决上述痛点,引入Spring MVC,核心:DispatcherServlet,挂载在Servlet容器下的唯一一个全局Servlet。

  • 全面接管: DispatcherServlet在向Undertow注册时,拦截路径通常为/,Undertow会把所有的HTTP请求都交给DispatcherServlet来处理
  • 二次路由: DispatcherServlet通过扫描代码中的@GETMapping,@POSTMapping建立了一套极其复杂的映射表,请求到达DispatcherServlet后,它会根据请求的URL,在内存中精确匹配到对应的Controller方法。
  • 参数处理与序列化:DispatcherServlet可以将HTTP请求里的JSON字符串,利用Jackson库自动反序列化为方法签名里的Java对象(@RequestBody User user),并在方法执行完成后,将返回的Java对象再次序列化为JSON响应给前端。

DispatcherServlet装配过程

传统的Tomcat中,想让一个Servlet工作,

  1. 写一个类继承HttpServlet(DispatcherServlet就是一个Servlet),
  2. 在web.xml中配置<servlet>标签给它取名字,再配置<servlet-mapping>告诉它要拦截哪些路径 SpringBoot没有web.xml,因此它必须用纯JAVA代码自动完成这两件事。 这都依赖于DispatcherServletAutoConfiguration类,实现了DispatcherServlet的自动配置:
@AutoConfigureOrder(Integer.MIN_VALUE)
@AutoConfiguration(after = {ServletWebServerFactoryAutoConfiguration.class})

表明DispatcherServletAutoConfiguration在自动配置类中优先级别最高,等待Undertow/Tomcat的自动配置类完成后,才能执行。先有Web服务器,再有DispatcherServlet。
class DispatcherServletConfiguration负责实例化,由中介类WebMvcProperties从配置文件中读取spring.mvc.xxx并赋值给DispatcherServlet实例。
DefaultDispatcherServletCondition则是去IoC容器内查找用户有没有手动写名为dispatcherServlet的Bean,如果nomatch,才会自动配置生效。这是SpringBoot的“按需退让”原则。
class DispatcherServletRegistrationConfiguration负责挂载到已经配置好的服务器下。@Bean返回了一个DispatcherServletRegistrationBean对象,DispatcherServletRegistrationBean继承了ServletRegistrationBean继承DynamicRegistrationBean继承RegistrationBean。RegistrationBean是SpringBoot的一个特殊包装器,当内嵌的Undertow启动后,它会去SpringIoC容器里搜寻所有的RegistrationBean,然后调用原生的Servlet API(ServletContext.addServlet()),将包装在里面的DispatcherServlet动态注册到Web服务器中。
DispatcherServletRegistrationCondition与DefaultDispatcherServlet作用相似,去IoC容器中查找用户有没有自己写同名注册类,按需退让。这两个类的@Order均为2^32-11的极低优先级,保证用户配置优先。

Undertow与DispatcherServlet

Undertow是Web Server,DispatcherServlet是Servlet。Servlet是Java官方定义的一个接口规范,规定了Java程序如何接收HTTP请求并返回HTTP响应。而Web Server是一个Servlet容器,负责监听底层的网络端口,把TCP/IP字节流解析成HTTP对象,然后调用具体的Servlet进行处理。
Undertow负责网络通信和基础的Servlet调度,Spring MVC则作为一个超级Servlet挂载在Undertow下,负责高级的路由转发、参数绑定、数据转换、和业务流程控制。前者是基础设施,后者是应用框架。

数据与外部集成

Java应用访问数据库,是一层一层向上抽象的过程。可以将其分为三个递进的层次。

  1. JDBC(JAVA DataBase Connectivity) 每执行一次查询,都需要经历"建立网络连接->创建statement->执行SQL->遍历ResultSet->关闭连接"的完整生命周期,且建立数据库底层的TCP连接是非常耗时且消耗资源的物理操作。如果并发请求实时创建和销毁连接,数据库会瞬间崩溃。
  2. 数据库连接池(Connection Pool) 为了解决JDBC频繁创建连接的性能问题,需要使用连接池技术。
    核心思想:池化技术,系统启动时,提前创建好一批数据库连接放在池子里。当有请求时,直接从池中借走一个连接,用完后再还回去,而不是销毁。最大连接数决定了同时能处理多少个并发数据库操作,连接超时时间决定了当池中没有空闲连接时,新请求原意排队等待的最长时间。
  3. 持久层框架(ORM / SQL Mapper) 即便有了连接池,开发者依然需要自己拼接SQL字符串,手动把数据库返回的数据一条条塞进Java对象里。为了消除重复,诞生了MyBatis和Spring Data JPA这类框架。
  • MyBatis(半自动)
    开发者需要自己写SQL,MyBatis负责把Java方法参数塞进SQL,执行后,再自动把数据库返回的结果映射成Java对象
  • Spring Data JPA(全自动/ORM) Objective Relational Mapping对象关系映射
    不需要写SQL,只需要定义Java实体类和相关的Repository接口,框架会根据接口方法名,自动生成并执行底层的SQL语句。

Spring Data JPA三层技术栈

  1. 规范层 JPA-Java Persistence API,Java官方制定的一套接口规范
  2. 实现层 Hibernate--ORM引擎,Spring Boot默认集成Hibernate作为JPA规范的底层实现者,Hibernate才是真正去拼接SQL,管理对象状态。
  3. 抽象封装层 Spring Data 只需按照规则定义一个Java接口(Repository),不需要写任何实现类。Spring在运行时利用AOP动态代理,自动生成实现类的实例,并将其注入到IoC容器中。
    findByUsernameAndAgeAfter(String username, Integer age); ---> SELECT * FROM t_user WHERE username = ? AND age = ?
    findByUsernameGreaterThanAndCreatedAt(String username, LocalDateTime createdat) ---> SELECT * FROM t_user WHERE username > ? AND created_at = ?
    Spring Data JPA的CRUD并非简单的SQL拼接,而是基于持久化上下文管理实体的状态。
  4. 临时态
  5. 持久态
  6. 游离态
  7. 删除态

ORM框架:Schema自动推导生成

JPA/Hibernate中控制数据库结构的最高配置:ddl-auto
在SpringBoot中,可以通过在application.yml中配置spring.jpa.hibernate.ddl-auto属性,来显示命令底层Hibernate如何对待数据库表结构

  • none(无作为): SpringBoot连接传统数据库(MySQL、PostgreSQL)的默认行为,如果没有提前建表,启动时可能不会立即报错,但执行到CRUD操作时,应用则直接抛出SQLSyntaxErrorException并崩溃。
  • validate(严格校验),启动时,Hibernate会去对比Java实体类和数据库现有的表结构,如果出现问题,应用立即报错并拒绝启动
  • update(增量更新): 如果表不存在,会自动按实体类名建表,在Java类里新增一个属性,重启后会在数据库表里加一列。
  • create(暴力重建),启动时,不论表存不存在,都会先执行DROP TABLE把表删了,重新CREATE TABLE。
  • create-drop(用完即焚):启动时建表,当Spring Boot应用正常关闭,IoC容器销毁时,把表删掉。 数据库版本控制工具(Flyway/Liquibase)

多表关联映射与序列化返回数据

@OneToMany、@ManyToOne注解,例如User和Article两个Entity,一个User有多个Article,一个Article只有一个User。两个类互相持有对象引用,在底层MySQL库的表现形式仅为一个表内添加外键,(@JoinColumn(name="user_id")则表示外键在article这个表内)。
由于Entity互相持有对方引用,因此返回前端时,不允许返回Entity,否则互相嵌套对象会崩溃。因此新建Data Transfer Object(DTO)专门用来传输信息

数据库操作的原子性与全局异常拦截器

当一个Service执行两条以上的写操作时,第一步成功而第二步失败时,就会造成数据库脏数据。为了同时成功,或同时失败。需要关闭事务的自动提交,当触发异常,如空指针时,让AOP接管并在捕捉异常时向数据库发送回滚请求。实现则通过Service方法前的@Transactional注解即可。当异常被手动try-catch捕捉时,AOP不会捕捉到,也就不会触发回滚,可能会造成脏数据。
SpringBoot具有全局异常拦截器,可以集中处理整个应用在运行过程中抛出的异常的机制。允许在拦截器中捕获并处理Controller层中向外抛出的各类异常,无需重复编写try-catch模块。@RestControllerAdvice声明类是全局异常处理器类,结合了@ControllerAdvice和@ResponseBody,表明该类的方法返回的值直接写入HTTP响应体。

SpringBoot自动装配HikaciCP连接池和JDBC驱动

  1. 依赖触发机制。引入spring-boot-starter-data-jpa,Maven的依赖传递会自动引入spring-boot-starter-jdbc,JDBC Starter内部默认包含了HikariCP连接池的依赖,此时,classpath中已经存在JDBC API和HikariCP的类文件。
    核心自动配置类为DataSourceAutoConfiguration。
  2. 自动配置类执行前先进行Condition检测,进行类检测和Bean检测,确保DataSource和驱动类存在,以及IoC容器是否有用户自定义的同名Bean
  3. 属性绑定与连接池实例化。读取application.yml配置文件,通过中间类DataSourceProperties将配置信息注入到HikariDataSource实例中,最后将该实例作为Bean注册到IoC容器

扩展

Bean的生命周期

  1. 实例化:在内存中创建对象实例,此时调用类的构造函数
  2. 属性赋值:执行依赖注入,将外部赋值(如@Value或@ConfigurationProperties)赋值给对象的属性
  3. 初始化:执行开发者定义的启动前准备逻辑
  4. 使用:调用方法
  5. 销毁: 容器关闭时执行资源释放逻辑

IoC容器初始化流程

核心方法AbstractApplicationContext.refresh()

  1. prepareRefresh()环境准备与自检
    初始化环境变量,并验证required的属性是否存在。

  2. obtainFreshBeanFactory()获取/创建Bean工厂
    通知子类刷新内部的BeanFactory,确认创建DefaultListableBeanFactory实例,同时加载基础的BeanDefinition。

  3. prepareBeanFactory(beanFactory)为工厂装配标准配件
    给刚才建好的BeanFactory配置类加载器、表达式解析器、属性编辑器。

  4. postProcessBeanFactory(beanFactory)子类的专属扩展点
    在标准工厂准备好后,提供给子类一个修改工厂的钩子

  5. invokeBeanFactoryPostProcessors(beanFactory)执行工厂后置处理器
    调用所有实现了BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor的类。SpringBoot自动装配,ConfigurationClassPostProcessor被唤醒,去解析所有@Configuration,执行@ComponentScan包扫描,读取imports文件,并严格评估所有的@Conditional条件注解。BeanDefinition数量增加,Service、Controller被扫描成元数据。

  6. registerBeanPostProcessors(beanFactory)注册Bean拦截器
    寻找容器中所有实现了BeanPostProcessor的类,把它们实例化,并按照优先级、注册到容器的拦截器链表。

  7. initMessageSource()初始化国际组件,初始化国际化组件

  8. initApplicationEventMulticaster()初始化事件多播器,创建并注册ApplicationEventMulticaster,创建了广播站。应用内事件通过它广播给对应的监听器。

  9. onRefresh()特定上下文的刷新逻辑,留给子类实现的钩子方法。ServletWebServerApplicationContext内嵌服务器在这一步被创建并启动

  10. registerListeners()注册事件监听器,把容器中所有实现了ApplicationListener的Bean提取出来,注册到8创建的广播站。

  11. finishBeanFactoryInitialization(beanFactory)实例化所有单例Bean。实例化、依赖注入、初始化并被拦截返回AOP动态代理对象

  12. finishRefresh()清理与收尾,清理资源缓存,初始化生命周期处理器,发布ContextRefreshedEvent事件。IoC容器初始化完毕。

手写一个自动装配类hyperspeed-spring-boot-starter

  • HyperspeedProperties类:
    用于读取application.yml配置文件。@Component,@ConfigurationProperties(prefix="hyperspeed")
  • HyperspeedClient类:
    从HyperspeedProperties获得配置文件数据,不含@Component,由HyperspeedAutoConfiguration类来自动配置注册为Bean
  • HyperspeedCondition继承Condition类,重写matches方法,对环境配置进行精准校验
  • HyperspeedBeanPostProcessor类,继承BeanPostProcessor,通过逻辑判断,可在HyperspeedClient初始化前后进行拦截操作
  • HyperspeedAutoConfiguration自动配置类,@Configuration,@EnableConfigurationProperties。返回两个Bean,@Bean,@Conditional(HyperspeedCondition.class)HyperspeedClient。@Bean HyperspeedPostProcessor

actuator暴露端点可供检测运行情况,在MeterRegistry中注册计数器之类的操作。

设计思想

约定大于配置,且允许覆盖(Optional but Overridable)

@ConditionalOnMissingBean注解,当Spring容器中不存在某个特定类型或名称的Bean时,我提供的默认Bean才会生效。 在应用启动早期,通过@Bean,@RestController定义的组件,通过@ComponentScan扫描并注册到容器。而自动配置类spring.factories的加载时机相对靠后,自动配置类准备向容器中注入默认的Bean类时,先检查容器,如果冲突,则主动放弃。因此用户的配置优先级永远高宇框架的自动配置

源码部分

ConditionOutcome,两个字段boolean match,ConditionMessage message,用来记录条件是否满足match,message用来记录信息。类的内置方法match(),nomatch()及其不同的 同构