文章

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 请求,并将请求分发到相应的控制器进行处理。它的主要职责包括:

  1. 接收请求:作为所有请求的入口。

  2. 请求分发:根据请求的 URL 或其他条件,将请求分发到相应的控制器。

  3. 视图解析:将控制器返回的逻辑视图名称解析为实际的视图(如 JSP 页面)。

  4. 异常处理:统一处理请求过程中发生的异常。

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 会调用 AutoConfigurationImportSelectorselectImports 方法,这个方法会返回一个字符串数组,数组中的每个字符串都是一个自动配置类的全限定名。Spring 会将这些自动配置类导入到应用上下文中,从而实现自动配置。

AutoConfigurationImportSelector 的工作流程大致如下:

  1. 加载 spring.factories 文件AutoConfigurationImportSelector 会从 META-INF/spring.factories 文件中加载所有配置的自动配置类。

  2. 过滤自动配置类:根据条件(如类路径中是否存在某个类、某个 Bean 是否已经存在等)过滤掉不需要的自动配置类。

  3. 返回自动配置类:将过滤后的自动配置类的全限定名返回给 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,\
...

参考

  1. https://www.itbaima.cn/document/h7sjo5oy0l03607e

  2. JavaGuide

License:  CC BY 4.0