Java SSM & SpringBoot 原理八股
1. Spring IoC 原理
1.1 实例化:BeanFactory 注册 Bean
我们获取 Bean 用的都是 ApplicationContext 接口:
@Slf4j
public class TestIoc {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
Person person = context.getBean("person", Person.class);
log.debug(String.valueOf(person));
}
}
ApplicationContext 源码中其实就维护了一个 BeanFactory 对象,并可以通过 get 方法获取:
public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {
@Nullable
private volatile DefaultListableBeanFactory beanFactory; // 默认构造后存放在这里的是一个DefaultListableBeanFactory对象
...
@Override
public final ConfigurableListableBeanFactory getBeanFactory() { // getBeanFactory就可以直接得到上面的对象了
DefaultListableBeanFactory beanFactory = this.beanFactory;
if (beanFactory == null) {
throw new IllegalStateException("BeanFactory not initialized or already closed - " +
"call 'refresh' before accessing beans via the ApplicationContext");
}
return beanFactory;
}
ApplicationContext 也继承了 BeanFactory , getBean()
就是 BeanFactory 的方法:
@Slf4j
public class TestIoc {
public static void main(String[] args) {
BeanFactory beanFactory = new DefaultListableBeanFactory();
Person person = beanFactory.getBean("person", Person.class);
log.debug(String.valueOf(person));
}
}
当然,我们没有用任何方式配置Bean,所以运行自然抛出以下异常:
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'person' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:772)
at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1221)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:294)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:204)
at com.werun.werunjuly.common.ioc.TestIoc.main(TestIoc.java:14)
我们通过 BeanDefination 对 BeanFactory 进行注册,就完成了配置。
@Slf4j
public class TestIoc {
public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
BeanDefinition definition = BeanDefinitionBuilder // 使用BeanDefinitionBuilder快速创建Bean定义
.rootBeanDefinition(Person.class) // Bean的类型
.setScope("singleton") // 设置作用域为单例模式
.getBeanDefinition(); //生成此Bean定义
beanFactory.registerBeanDefinition("lbwnb", definition); //向工厂注册Bean此定义,并设定Bean的名称
Person person = beanFactory.getBean("lbwnb", Person.class);
log.debug(String.valueOf(person));
}
}
如果我们需要用 xml 或注解配置,自然需要用到这三个实现类:
ClassPathXmlApplicationContext:适用于类路径下的XML配置文件。
FileSystemXmlApplicationContext:适用于非类路径下的XML配置文件。
AnnotationConfigApplicationContext:适用于注解配置形式。
比如ClassPathXmlApplicationContext在初始化的时候就会创建一个对应的XmlBeanDefinitionReader进行扫描:
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 为给定的BeanFactory创建XmlBeanDefinitionReader便于读取XML中的Bean配置
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 各种配置,忽略掉
beanDefinitionReader.setEnvironment(this.getEnvironment());
...
// 配置完成后,直接开始加载XML文件中的Bean定义
loadBeanDefinitions(beanDefinitionReader);
}
大体过程:
这样,BeanFactory 就知道怎么实例化 Bean 了。
1.2 属性注入:单例 Bean 的创建与循环依赖
一个 Bean 的实例对象到底是如何创建出来的呢?我们还要继续对我们之前讲解的 BeanFactory 进行深入介绍。
我们可以直接找到 BeanFactory 接口的一个抽象实现AbstractBeanFactory
类,它实现了getBean()
方法:
public Object getBean(String name) throws BeansException {
// 套娃开始了,做好准备
return this.doGetBean(name, (Class)null, (Object[])null, false);
}
而 doGetBean() 方法的流程大概如下:
核心方法就是 getSingleton(),这里通过三级缓存机制解决了单例bean循环依赖的问题:
singletonObjects,用于保存实例化、注入、初始化完成的 bean 实例
earlySingletonObjects,用于保存实例化完成的 bean 实例
singletonFactories,在初始创建Bean对象时都会生成一个对应的单例工厂用于获取早期对象
// 是的,Spring IoC 的本质就是一个 HashMap...
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
private final Map<String, Object> earlySingletonObjects = new HashMap(16);
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
具体过程如下:
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
// 先从第一层列表中拿Bean实例,拿到直接返回
if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
// 如果第一层拿不到,并且已经认定为处于循环状态,看看第二层有没有
singletonObject = this.earlySingletonObjects.get(beanName);
// 要是还是没有,继续往下
if (singletonObject == null && allowEarlyReference) {
synchronized(this.singletonObjects) {
// 加锁再执行一次上述流程
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
// 仍然没有获取到实例,只能从singletonFactory中获取了
ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject(); // 如果我们获取的Bean压根就没在工厂中注册,那得到的结果肯定是null
// 丢进earlySingletonObjects中,下次就可以直接在第二层拿到了
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
现在我们根据上面的流程,来模拟一下A和B循环依赖的情况:
三级缓存通过提前暴露Bean的早期引用,使得Bean在尚未完全初始化时,其他Bean就可以引用它,从而避免了循环依赖导致的初始化问题。两级缓存无法完全解决这个问题,因此Spring采用了三级缓存的设计。
1.3 初始化:后置处理器与 AOP
PostProcessor 其实是Spring提供的一种后置处理机制,它可以让我们能够插手Bean、BeanFactory、BeanDefinition的创建过程,相当于进行一个最终的处理,而最后得到的结果(比如Bean实例、Bean定义等)就是经过后置处理器返回的结果,它是整个加载过程的最后一步。
我们可以实现一个接口 BeanPostProcessor
:
@Component
public class TestBeanProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean; //这里返回的Bean会交给下一个阶段,也就是初始化方法
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean; //这里返回的Bean相当于最终的结果了,我们依然能够插手修改,这里返回之后是什么就是什么了
}
}
示例代码:
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.config.BeanPostProcessor;
import javax.annotation.PostConstruct;
// 定义 TestService 接口
interface TestService {
void doSomething();
}
// 定义 TestMapper 接口
interface TestMapper {
void query();
}
// TestServiceImpl 实现 TestService 接口
@Component
class TestServiceImpl implements TestService {
public TestServiceImpl() {
System.out.println("我是构造方法");
}
@PostConstruct
public void init() {
System.out.println("我是初始化方法");
}
TestMapper mapper;
@Autowired
public void setMapper(TestMapper mapper) {
System.out.println("我是依赖注入");
this.mapper = mapper;
}
@Override
public void doSomething() {
System.out.println("我是业务方法");
}
}
// TestBeanProcessor 实现 BeanPostProcessor 接口
@Component
public class TestBeanProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("我是之前:" + beanName);
return bean; // 这里返回的Bean会交给下一个阶段,也就是初始化方法
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("我是之后:" + beanName);
return bean; // 这里返回的Bean相当于最终的结果了,我们依然能够插手修改,这里返回之后是什么就是什么了
}
}
// 主程序
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
// 创建 Spring 上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(TestServiceImpl.class, TestBeanProcessor.class); // 注册Bean
context.refresh(); // 刷新上下文,触发Bean的创建和初始化
// 获取 TestServiceImpl Bean 并调用方法
TestService testService = context.getBean(TestService.class);
testService.doSomething();
// 关闭上下文
context.close();
}
}
运行上述代码后,控制台输出如下:
我是构造方法
我是依赖注入
我是之前:testServiceImpl
我是初始化方法
我是之后:testServiceImpl
我是业务方法
1.4 Bean 的生命周期
Bean 定义
首先扫描Bean,加载Bean定义
Bean定义和Bean工厂后置处理
依赖注入
根据Bean定义通过反射创建Bean实例
进行依赖注入(顺便解决循环依赖问题)
初始化Bean
BeanPostProcessor的初始化之前方法
Bean初始化方法
BeanPostProcessor的初始化之后方法
2. Spring MVC 原理
2.1 核心组件
DispatcherServlet
是 Spring MVC 的核心组件,它是一个前端控制器(Front Controller),负责接收所有的 HTTP 请求,并将请求分发到相应的控制器进行处理。它的主要职责包括:
接收请求:作为所有请求的入口。
请求分发:根据请求的 URL 或其他条件,将请求分发到相应的控制器。
视图解析:将控制器返回的逻辑视图名称解析为实际的视图(如 JSP 页面)。
异常处理:统一处理请求过程中发生的异常。
2.2 DispatcherServlet 工作流程
3. Spring Boot 原理
Spring Boot 启动过程:负责应用的整体启动流程,包括初始化 Spring 容器、加载配置、启动内嵌服务器等。
自动配置过程:负责根据应用的依赖和环境,自动配置所需的 Bean 和组件。
关系:自动配置是启动过程的一部分,两者协同工作,共同完成应用的初始化和配置。
通过启动和自动配置,Spring Boot 能够快速、高效地初始化应用,并减少开发者的配置工作。
3.1 启动原理
我们来看整个程序的 main 方法:
@SpringBootApplication()
public class WerunJulyApplication {
public static void main(String[] args) {
SpringApplication.run(WerunJulyApplication.class, args);
}
}
看一下这个静态 run()
方法:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class[]{primarySource}, args);
}
// 套娃如下:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
我们发现,这里直接new了一个新的 SpringApplication 对象,传入我们的主类作为构造方法参数,并调用了非static的run方法。
3.1.1 构造方法与类加载
我们先来看看构造方法里面做了什么事情:
public SpringApplication(Class<?>... primarySources) {
this((ResourceLoader)null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 省略 ...
// 设置主要源,也就是我们的启动主类
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
// 判断当前 SpringBoot 应用程序是否为Web项目,并返回当前的项目类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// getSpringFactoriesInstances() 一次性获取指定类型已注册的实现类
// 获取并设置所有 ApplicationContextInitializer 实现
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 设置应用程序监听器
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
// 找到并设定当前的启动主类
this.mainApplicationClass = this.deduceMainApplicationClass();
}
对一些不熟悉的地方做一下说明:
SpringApplication::getSpringFactoriesInstances() 关键方法,一次性获取指定类型已注册的实现类。
WebApplicationType.deduceFromClasspath() 方法的内容是根据类路径下判断是否包含 SpringBootWeb 依赖。(NONE / REACTIVE / SERVLET)
ApplicationContextInitializer 接口用于在 Spring 容器执行 onRefresh 方法刷新之前执行一个回调函数,通常用于向 SpringBoot 启动的容器中注入一些属性,比如 ContextIdApplicationContextInitializer 就是将配置中定义的 spring.application.name 属性值设定为应用程序上下文的ID。
通过阅读上面的源码,我们发现 getSpringFactoriesInstances()
这个方法可以一次性获取指定类型已注册的实现类,我们先来研究一下它是怎么做到的。这里就要提到 spring.factories 文件了,它是 Spring 仿造Java SPI实现的一种类加载机制。它在 META-INF/spring.factories 文件中配置接口的实现类名称,然后在程序中读取这些配置文件并实例化。这种自定义的SPI机制是 Spring Boot Starter 实现的基础。
比如我们查找这个依赖的 jar 包,做一些解析(这个依赖包含进了 spring-boot-starter 里)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.1.2.RELEASE</version>
<scope>compile</scope>
</dependency>
这里面 META-INF/spring.factories 文件中就配置了 ApplicationContextInitializer 的实现类:
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
键:
org.springframework.context.ApplicationContextInitializer
,表示ApplicationContextInitializer
接口。值:两个实现类的全限定名,分别是:
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
getSpringFactoriesInstances
其实就是通过读取所有 META-INF/spring.factories
文件得到的列表,然后实例化指定类型下读取到的所有实现类并返回。
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = this.getClassLoader();
Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = this.<T>createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
这样,我们就清楚 SpringBoot 这一大堆类是怎么加载进来的了。
3.1.2 创建 ApplicationContext
目前 SpringApplication 对象已经构造好了,而 run 方法则创建了一个 ApplicationContext 用于整个 Spring 环境的 IoC 容器:
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
ConfigurableApplicationContext context = null;
this.configureHeadlessProperty();
// 获取所有的SpringApplicationRunListener,并通知启动事件,默认只有一个实现类EventPublishingRunListener
// EventPublishingRunListener会将初始化各个阶段的事件转发给所有监听器
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 环境配置,包括我们之前配置的多环境选择
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 打印 Banner,从这里开始我们就可以切切实实看到运行状了
Banner printedBanner = this.printBanner(environment);
// 最关键的一步:创建 ApplicationContext,也就是整个Spring应用程序的IoC容器,注意这里会根据构造时得到的类型,创建不同的ApplicationContext实现类(比如Servlet环境下就是Web容器)
context = this.createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
//对 ApplicationContext 进行前置处理,这里会将创建对象时设定的所有ApplicationContextInitializer拿来执行一次initialize方法,这也验证了我们之前的说法,这一步确实是在刷新容器之前进行的
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 执行 ApplicationContext 的 refresh 方法,刷新容器初始化所有的 Bean,这个也在SSM阶段详细介绍过了
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), timeTakenToStartup);
}
listeners.started(context, timeTakenToStartup);
// 因为所有的 Bean 都已经加载,这里就可以调用全部的自定义 Runner 实现了
this.callRunners(context, applicationArguments);
// ...
return context;
}
我们再关注一下 createApplicationContext()
方法(不同 SpringBoot 版本差别略大,我的是 2.1.2 版本,但是都是返回 Servlet / Reactive / 普通的 ApplicationContext):
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
break;
case REACTIVE:
contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
break;
default:
contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
}
} catch (ClassNotFoundException ex) {
throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
}
}
return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}
至此,Spring Boot 项目启动成功。
3.2 自动配置原理
既然主类已经在初始阶段注册为Bean,那么在加载时,就会根据注解定义,进行更多的额外操作。
所以我们来看看主类上的@SpringBootApplication注解做了什么事情。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration // 表示当前类是一个Spring Boot的配置类
@EnableAutoConfiguration // 启用Spring Boot的自动配置机制
@ComponentScan( // 启用组件扫描,自动发现和注册Bean
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
...
其中 @EnableAutoConfiguration
就是与 Spring Boot 自动配置最核心的类。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
我们尤其注意一下 @Import({AutoConfigurationImportSelector.class})
:
@Import
注解用于将额外的配置类或 Bean 定义导入到 Spring 的应用上下文中。它可以导入以下几种类型的类:
普通类:这个类会被当作一个配置类处理,Spring 会扫描它定义的 Bean。
实现了
ImportSelector
接口的类:这个类可以根据条件动态地选择要导入的配置类。实现了
ImportBeanDefinitionRegistrar
接口的类:这个类可以手动注册 Bean 定义。
而AutoConfigurationImportSelector
是一个实现了 ImportSelector
接口的类,它的作用是根据条件动态地选择要导入的自动配置类。
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
}
public interface DeferredImportSelector extends ImportSelector {
}
public interface ImportSelector {
String[] selectImports(AnnotationMetadata var1);
}
具体来说,AutoConfigurationImportSelector
会从 META-INF/spring.factories
文件中加载所有配置的自动配置类,并根据条件决定哪些类需要被导入。
private static final String[] NO_IMPORTS = new String[0];
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 判断自动装配开关是否打开
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
//获取所有需要装配的bean
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
}
当你在一个配置类上使用 @Import({AutoConfigurationImportSelector.class})
时,Spring 会调用 AutoConfigurationImportSelector
的 selectImports
方法,这个方法会返回一个字符串数组,数组中的每个字符串都是一个自动配置类的全限定名。Spring 会将这些自动配置类导入到应用上下文中,从而实现自动配置。
AutoConfigurationImportSelector
的工作流程大致如下:
加载
spring.factories
文件:AutoConfigurationImportSelector
会从META-INF/spring.factories
文件中加载所有配置的自动配置类。过滤自动配置类:根据条件(如类路径中是否存在某个类、某个 Bean 是否已经存在等)过滤掉不需要的自动配置类。
返回自动配置类:将过滤后的自动配置类的全限定名返回给 Spring,Spring 会将这些类导入到应用上下文中。
spring.factories
文件是 Spring Boot 自动配置的核心文件之一,它位于 META-INF
目录下。这个文件中定义了许多自动配置类,例如:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
...
参考
https://www.itbaima.cn/document/h7sjo5oy0l03607e
JavaGuide