Springboot源码分析

简单概括一下Springboot的源码流程架构,一些比较重要的类和细节会详细写出来。

Springboot的启动流程

我们知道,Springboot就是靠这串SpringApplication的静态方法便启动了web服务:

SpringApplication.run(DemoApplication.class, args);

它的实现是封装了一个SpringApplication对象,并调用run方法的重载:new SpringApplication(primarySources)).run(args);

这个run方法的核心代码是干了这样几件事:

public ConfigurableApplicationContext run(String... args) {
   // 计时工具
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
   ConfigurableApplicationContext context = null;
   Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
   configureHeadlessProperty();
   // 第一步:获取并启动监听器
   SpringApplicationRunListeners listeners = getRunListeners(args);
   listeners.starting();

   try {
       ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
       
       // 第二步:根据SpringApplicationRunListeners以及参数来准备环境
       ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
       configureIgnoreBeanInfo(environment);

       // 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
       Banner printedBanner = printBanner(environment);

       // 第三步:创建Spring容器
       context = createApplicationContext();

       exceptionReporters = getSpringFactoriesInstances(
               SpringBootExceptionReporter.class,
               new Class[] { ConfigurableApplicationContext.class }, context);

       // 第四步:Spring容器前置处理
       prepareContext(context, environment, listeners, applicationArguments,printedBanner);

       // 第五步:刷新容器
       refreshContext(context);

     // 第六步:Spring容器后置处理
       afterRefresh(context, applicationArguments);

    // 第七步:发出结束执行的事件
       listeners.started(context);
       // 第八步:执行Runners
       this.callRunners(context, applicationArguments);
       stopWatch.stop();
       // 返回容器
       return context;
  }
   catch (Throwable ex) {
       handleRunFailure(context, listeners, exceptionReporters, ex);
       throw new IllegalStateException(ex);
  }
}

这里过一下其中的核心步骤。

获取并启动监听器(SpringApplicationRunListener)

SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();

Springboot启动中涉及到的事件机制组件

  • 事件源 – SpringApplication
  • 事件 – ApplicationEvent

SpringApplication的生命周期。

事件名作用
ApplicationStartingEvent框架启动事件
ApplicationEnvironmentPreparedEvent环境准备完毕事件
ApplicationContextInitializedEvent上下文初始化
ApplicationPreparedEvent上下文创建完毕,但是Bean还没有加载完毕
ApplicationStartedEventbean 实例化完成,但是未调用 Runners接口
ApplicationReadyEvent调用 Runners 接口完毕
ApplicationFailedEvent启动失败事件
  • 事件发布者 – ApplicationEventPublisherApplicationEventMulticaster
  • 监听器 – java.util.EventListener子类接口ApplicationListener
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
   void onApplicationEvent(E event);
   static <T> ApplicationListener<PayloadApplicationEvent<T>> forPayload(Consumer<T> consumer) {
       return (event) -> {
           consumer.accept(event.getPayload());
      };
  }
}

实际上Springboot中用到的Listener默认只有EventPublishingRunListener

 public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
     private final SpringApplication application;
     private final String[] args;
     //广播器
     private final SimpleApplicationEventMulticaster initialMulticaster;
 
     public EventPublishingRunListener(SpringApplication application, String[] args) {
        this.application = application;
        this.args = args;
        this.initialMulticaster = new SimpleApplicationEventMulticaster();
        Iterator var3 = application.getListeners().iterator();
        while(var3.hasNext()) {
            ApplicationListener<?> listener = (ApplicationListener)var3.next();
            //将上面设置到SpringApplication的十一个监听器全部添加到SimpleApplicationEventMulticaster这个广播器中
            this.initialMulticaster.addApplicationListener(listener);
        }
    }
    //略...
}

获取SpringApplicationRunListener

 private SpringApplicationRunListeners getRunListeners(String[] args) {
       Class<?>[] types = new Class[]{SpringApplication.class, String[].class};
       return new SpringApplicationRunListeners(logger, this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args), this.applicationStartup);
}

getRunListeners返回的是一个SpringApplicationRunListeners,就是对获取到监听器的包装。

这里想要获取的监听器是SpringApplicationRunListener.class类型的监听器,看得出来就是对SpringApplication整个运行过程中的监听器。

这里调用了SpringApplication#getSpringFactoriesInstances,最后获取的就是一个EventPublishingRunListener

image-20250622135046636

这个是Springboot获取bean的一个重要方法:

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.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
   AnnotationAwareOrderComparator.sort(instances);
   return instances;
}

SpringFactoriesLoader.loadFactoryNames(type, classLoader)这一步,就是我们熟知的,从META-INF/spring.factories读取bean的全限定类名;结束后再传给下面的方法反射实例化。

这里就是从META-INF/spring.factories中读取Key为org.springframework.boot.SpringApplicationRunListener的Values:

org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

所以获取的SpringApplicationRunListeners中只有这个EventPublishingRunListener,当然你也可以自己扩展,并在META-INF/spring.factories中自己写入。

这里又出现了一个问题:我们这个如以maven构建的项目目录中并没有显式出现META-INF这个目录,哪这个META-INF/spring.factories及其里面的内容是哪来的呢?

其实,在pom.xml在引入的

   <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
   </dependency>

依赖组中,包含了一项名为spring-boot-autoconfigure-x.x.x的依赖,

image-20250622140553205

这个jar包中就包含了Springboot启动所需的META-INF/spring.factories等一系列配置,当然,并不会与项目目录中手动建立的META-INF/spring.factories冲突,两个文件会合并解析。

启动SpringApplicationRunListener

要了解一个逻辑复杂的类就先去了解它的接口:

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
   private final SpringApplication application;
   private final String[] args;
   private final SimpleApplicationEventMulticaster initialMulticaster;
  ...
}
public interface SpringApplicationRunListener {

   // 在run()方法开始执行时,该方法就立即被调用,可用于在初始化最早期时做一些工作
   void starting();
   // 当environment构建完成,ApplicationContext创建之前,该方法被调用
   void environmentPrepared(ConfigurableEnvironment environment);
   // 当ApplicationContext构建完成时,该方法被调用
   void contextPrepared(ConfigurableApplicationContext context);
   // 在ApplicationContext完成加载,但没有被刷新前,该方法被调用
   void contextLoaded(ConfigurableApplicationContext context);
   // 在ApplicationContext刷新并启动后,CommandLineRunners和ApplicationRunner未被调用前,该方法被调用
   void started(ConfigurableApplicationContext context);
   // 在run()方法执行完成前该方法被调用
   void running(ConfigurableApplicationContext context);
   // 当应用运行出错时该方法被调用
   void failed(ConfigurableApplicationContext context, Throwable exception);
}

先来看看SpringBoot启动时第一个启动事件listeners.starting():

@Override
public void starting() {
   //关键代码,先创建application启动事件`ApplicationStartingEvent`
   this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}

调用封装好的SimpleApplicationEventMulticaster对象的multicastEvent,准备利用广播器发布事件并又启用监听器。其大部分对事件的处理都是调用Multicaster,可见得这个监听器的职责更像是作为Multicaster的补充。

跟进SimpleApplicationEventMulticaster:

@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
   ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
   //通过事件类型ApplicationStartingEvent获取对应的监听器
   for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
       //获取线程池,如果为空则同步处理。这里线程池为空,还未没初始化。
       Executor executor = getTaskExecutor();
       if (executor != null) {
           //异步发送事件
           executor.execute(() -> invokeListener(listener, event));
      }
       else {
           //同步发送事件
           invokeListener(listener, event);
      }
  }
}

这个getApplicationListeners方法会把入参事件的.class取出来,并看哪些监听器(监听器也是从META-INF/spring.factories中取出实例化)对这个事件event和事件源source”感兴趣”(在每个Listener中定义了列表,对应具体事件、事件源类,传入的事件在这列表里面就表示监听器对其”感兴趣”)并将所有对该事件”感兴趣”的监听器返回。

这里就有如下4种监听器:

image-20250622144531608

以日志监听器:LoggingApplicationListener为例:

这就是提到的支持的source和type:

image-20250622144756408

这是其最后调用的方法:

public void onApplicationEvent(ApplicationEvent event) {
   //在springboot启动的时候
   if (event instanceof ApplicationStartedEvent) {
       onApplicationStartedEvent((ApplicationStartedEvent) event);
  }
   //springboot的Environment环境准备完成的时候
   else if (event instanceof ApplicationEnvironmentPreparedEvent) {
       onApplicationEnvironmentPreparedEvent(
              (ApplicationEnvironmentPreparedEvent) event);
  }
   //在springboot容器的环境设置完成以后
   else if (event instanceof ApplicationPreparedEvent) {
       onApplicationPreparedEvent((ApplicationPreparedEvent) event);
  }
   //容器关闭的时候
   else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event)
          .getApplicationContext().getParent() == null) {
       onContextClosedEvent();
  }
   //容器启动失败的时候
   else if (event instanceof ApplicationFailedEvent) {
       onApplicationFailedEvent();
  }
}

这个类就是在springboot启动时日志输出的实现类。

至此

    SpringApplicationRunListeners listeners = getRunListeners(args);
   listeners.starting();

这两串就介绍完了。这个EventPublishingRunListener会贯穿在SpringApplication整个运行过程。

环境准备

  ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);

SpringApplication:

private ConfigurableEnvironment prepareEnvironment(
       SpringApplicationRunListeners listeners,
       ApplicationArguments applicationArguments) {
   //获取对应的ConfigurableEnvironment
   ConfigurableEnvironment environment = getOrCreateEnvironment();
   configureEnvironment(environment, applicationArguments.getSourceArgs());
   //发布环境已准备事件,这是第二次发布事件
   listeners.environmentPrepared(environment);
   bindToSpringApplication(environment);
   ConfigurationPropertySources.attach(environment);
   return environment;
}

可以看到还是利用EventPublishingRunListener去发布事件,那么就继续跟进到其内部的SimpleApplicationEventMulticaster#multicast,会调用到的监听器:

image-20250623153555549

其中,重点来看ConfigFileApplicationListener,该监听器非常核心,主要用来处理项目配置。项目中的 properties 和yml文件都是其内部类所加载。

image-20250623154407103

先从META-INF/spring.factories中读取几个Processors,再执行其postProcessEnvironment,最后执行该监听器本身的逻辑,即加载其定义路径下的配置文件:

private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";

可以看到,在发布事件处理结束后,environment变量存放了我们在resources\application.properties配置的参数:

image-20250623170329712

应用上下文的初始化、后处理

    context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
afterRefresh(context, applicationArguments);

这里的ApplicationContext也常被成为Spring容器,它是基于 Spring 框架定义的 IoC 容器,是负责 Bean 生命周期管理的核心组件。

1.createApplicationContext

先根据容器类型判断,此处是SERVLET类型,所以会通过反射装载对应的字节码,也就是AnnotationConfigServletWebServerApplicationContext

2.prepareContext(初始化1)

然后prepareContext开始配置容器

private void prepareContext(ConfigurableApplicationContext context,
       ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
       ApplicationArguments applicationArguments, Banner printedBanner) {
   //设置容器环境,包括各种变量
   context.setEnvironment(environment);
   //执行容器后置处理
   postProcessApplicationContext(context);
   //执行容器中的ApplicationContextInitializer(包括 spring.factories和自定义的实例)
   applyInitializers(context);
  //发送容器已经准备好的事件,通知各监听器
   listeners.contextPrepared(context);
   //注册启动参数bean,这里将容器指定的参数封装成bean,注入容器
   context.getBeanFactory().registerSingleton("springApplicationArguments",
           applicationArguments);
   //设置banner
   if (printedBanner != null) {
       context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
  }
   //获取我们的启动类指定的参数,可以是多个
   Set<Object> sources = getAllSources();
   Assert.notEmpty(sources, "Sources must not be empty");
   //加载我们的启动类,将启动类注入容器
   load(context, sources.toArray(new Object[0]));
   //发布容器已加载事件。
   listeners.contextLoaded(context);
}

调用postProcess、循环调用ApplicationContextInitializer中的initialize方法、将启动类注入容器、发布容器已加载(打印日志)。

3.refresh(初始化2)

public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
       // 启动步骤标记
       StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
       // 准备刷新 Prepare this context for refreshing.
       prepareRefresh();
       // 通知子类刷新内部bean工厂 Tell the subclass to refresh the internal bean factory.
       ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
       // 准备工厂以便在此上下文中使用 Prepare the bean factory for use in this context.
       prepareBeanFactory(beanFactory);
       try {
           // Allows post-processing of the bean factory in context subclasses.
           // Bean工厂后置处理
           postProcessBeanFactory(beanFactory);
           // 步骤标记
           StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
           // Invoke factory processors registered as beans in the context.
           // 执行Bean工厂后置处理 核心大哥有个是ConfigurationClassPostProcessor @Configuration @ComponentScan都是这大哥
           invokeBeanFactoryPostProcessors(beanFactory);
           // 注册Bean 后置处理器 Register bean processors that intercept bean creation.
           registerBeanPostProcessors(beanFactory);
           beanPostProcess.end();
           // 国际化 Initialize message source for this context.
           initMessageSource();
           // 注册事件发布器 Initialize event multicaster for this context.
           initApplicationEventMulticaster();
           // Initialize other special beans in specific context subclasses.
           // 这里对于SpringBoot主要就是创建WebServer
           onRefresh();
           // 注册监听器 Check for listener beans and register them.
           registerListeners();
           // 初始化非延迟加载Bean Instantiate all remaining (non-lazy-init) singletons.
           finishBeanFactoryInitialization(beanFactory);
           // 完成刷新 Last step: publish corresponding event.
           finishRefresh();
      }
       catch (BeansException ex) {
           if (logger.isWarnEnabled()) {
               logger.warn("Exception encountered during context initialization - " +
                       "cancelling refresh attempt: " + ex);
          }
           // 销毁Bean Destroy already created singletons to avoid dangling resources.
           destroyBeans();
           // 取消刷新 Reset 'active' flag.
           cancelRefresh(ex);
           // Propagate exception to caller.
           throw ex;
      }
       finally {
           // Reset common introspection caches in Spring's core, since we
           // might not ever need metadata for singleton beans anymore...
           resetCommonCaches();
           // 上下文刷新结束
           contextRefresh.end();
      }
  }
}

这段代码是spring架构中的核心,是实现ioc和aop的关键;负责刷新应用程序上下文,这里主要涉及到准备刷新上下文,调用上下文注册为 bean 的工厂处理器,初始化上下文的消息源,初始化特定上下文子类中的其他特殊 bean,检查监听器 bean 并注册,最后发布相应的事件并销毁已经创建的单例及重置 active 标志。

大概做了这些事:

  1. 准备刷新prepareRefresh())—— 初始化环境变量、启动时间等。
  2. 获取 BeanFactoryobtainFreshBeanFactory())—— 只是将DefaultListableBeanFactory Bean 工厂刷新状态为已刷新。
  3. 配置 BeanFactoryprepareBeanFactory())—— 获取到Bean 工厂后,开始准备Bean 工厂,主要是进行功能扩展,入设置类加载器、Aware 接口支持等,这里还设置了SPEL 表达式解析器
  4. 执行 Bean工厂后置处理器 invokeBeanFactoryPostProcessors())—— 修改或增强 Bean 定义(如 @Configuration 类处理)。
  5. 注册 Bean 后置处理器registerBeanPostProcessors())—— 主要是将这些后置处理器进行分类,并添加到Bean 工厂中)。
  6. 初始化事件广播器、消息源等initMessageSource(), initApplicationEventMulticaster())。
  7. 初始化非懒加载的单例 BeanfinishBeanFactoryInitialization())—— 触发依赖注入和初始化。
  8. 完成刷新finishRefresh())—— 发布 ContextRefreshedEvent 事件,标志容器就绪。

4.afterRefresh(后处理)

扩展接口,设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理。

Bean的注册、实例化、初始化

应用上下文的启动中也包括了对Bean的注册以及实例化,由于比较重要,这里就单独将对Bean的Bean的注册、实例化、初始化提出来说下:

扫描主类

在配置容器(prepareContext)load(context, sources.toArray(new Object[0]));这一步,调用了BeanDefinitionLoader#load

private void load(Object source) {
Assert.notNull(source, "Source must not be null");
if (source instanceof Class<?>) {
load((Class<?>) source);
return;
}
if (source instanceof Resource) {
load((Resource) source);
return;
}
if (source instanceof Package) {
load((Package) source);
return;
}
if (source instanceof CharSequence) {
load((CharSequence) source);
return;
}
throw new IllegalArgumentException("Invalid source type " + source.getClass());
}

这里根据不同的传参走不同的重载,也是Spring扫描和构建bean的分支点。这里的source是主类.class,所以走的是第一个if分支:

private int load(Class<?> source) {
   if (isGroovyPresent()
           && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
       // Any GroovyLoaders added in beans{} DSL can contribute beans here
       GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source,
               GroovyBeanDefinitionSource.class);
       load(loader);
  }
   if (isComponent(source)) {
       //以注解的方式,将启动类bean信息存入beanDefinitionMap,也就是将HelloWorldMainApplication.class存入了beanDefinitionMap
       this.annotatedReader.register(source);
       return 1;
  }
   return 0;
}

这里只将启动类注入到了beanDefinitionMap。

扫描并注册bean

刷新容器(refresh)这一步,和Bean的装配有关的步骤主要实现是invokeBeanFactoryPostProcessors()。它调用所有实现了BeanDefinitionRegistryPostProcessor接口的类的postProcessBeanDefinitionRegistry()方法,这个方法嵌套和调用太多,是spring的核心ioc,aop实现:

其中最核心的一个实现类是ConfigurationClassPostProcessor。这个流程对于Bean,大概做了这样几件事:

处理注解

我们的Controller、Filter等服务类是怎么被扫描到的?Spring中的Servlet容器(Tomcat)是怎么启动的?其实也就是在这个步骤。

这里会去扫描并处理传入source的注解,如主类的@SpringbootApplication。@SpringbootApplication注解包含了三个重要的注解:

1.@SpringBootConfiguration

本质上就是 @Configuration,把主类当成一个配置类注册到 Spring 容器里(将主类的 @Bean 方法(如果有)纳入容器管理)。

2.@EnableAutoConfiguration

通过 @Import(AutoConfigurationImportSelector.class),调用 AutoConfigurationImportSelector.selectImports(...)

  1. 利用 SpringFactoriesLoader.loadFactoryNames(...) 去读取所有依赖 JAR 中的 META‑INF/spring.factories,拿到自动配置类列表。
  2. 过滤、去重、排除不符合条件的配置,然后把最终的配置类名返回给容器去注册(@AutoConfigurationPackage` 只是把主类所在包及子包也作为“可自动配置的基础包”注册一遍,确保自定义配置也能被扫描到)。

3.@ComponentScan

直接触发组件扫描,扫描主类所在包及以下所有 @Controller@Service@Repository@Component 等注解,注册成 BeanDefinition。

把那些带注解的类包括 @Controller——都抓出来,包装成BeanDefinition,实例化并放到 IOC 容器里。

并且这里额外提到一个注解EmbeddedServletContainerAutoConfiguration:它是是嵌入式Servlet容器的自动配置类,会先自动扫描当前环境是否引入Tomcat(如依赖配置),如果是则获取工程类、实例化Tomcat并运行Tomcat.start()

对@SpringBootApplication注解处理后,大部分的bean便都被注册到beanDefinitionMap了。当然我们知道,除了主类有这种”配置型注解”,其它类包括用户自定义类、spring内部类也会有相应的注解,哪怎么样扫描到它们呢?这就要提到第二步了:

调用BeanDefinitionLoader#load

这里调用的与上一步不用,调用的是package的重载:

private void load(Package source) {
   this.scanner.scan(source.getName());
}

传入主类的包名,作为ClassPathBeanDefinitionScanner.scan的入参。这个类又会去调用doScan,又将主类包下递归搜索,将扫描的注解进行处理,如处理常见注解(@Lazy@Primary@DependsOn 等);结合 includeFiltersexcludeFilters 判断哪些类是候选对象(例如标注了 @Component@Controller@Service 等);扫描注解的作用域@Scope,为封装的BeanDefinition提供信息,最后注入到beanDefinitionMap。

bean的初始化和实例化

这一步就不细讲了,可以去看【spring容器启动】之bean的实例化和初始化(文末附:spring循环依赖原理)_实例化bean和初始化bean-CSDN博客。从整体过程简单来说,就是从上面步骤提到的把beanDefinitionMap注册的BeanDefinition进行解析,得到bean的元信息,再经过一系列深化处理,最终存放到三级缓存中(处理好的bean放在singletonObjects,半成品bean放在earlySingletonObjects,半成品bean工厂放在singletonFactories)

具体实现大概来说分为四步:

1. 实例化(createBeanInstance()

Spring 根据 BeanDefinition 中的信息(构造器、factory-methodSupplier),使用反射或 CGLIB 实例化对象,但此时仅是“空壳”,尚无属性。

2. 依赖填充(populateBean()

扫描需要注入的属性(来自 PropertyValues、@Autowired、@Value 等),处理依赖。如果遇到未完成的 Bean,会利用三级缓存机制支持循环依赖。

3. 初始化(initializeBean()

处理 Aware 接口回调(如 BeanNameAware, BeanFactoryAware)。

执行 BeanPostProcessor.beforeInitialization()

执行初始化方法:@PostConstruct、afterPropertiesSet()(来自 InitializingBean)、以及自定义 init-method

执行 BeanPostProcessor.afterInitialization()

这里还对AOP做了一些处理,如果检测到有切面注解(@Aspect、@Poincut…),会生成一个代理–createProxy(),作为后续AOP访问的获取到的代理,作为执行函数的入口(其实的源码实现这里有点像RMI,只是本地调用”本地方法“)。

4. 注册销毁方法

将实现 DisposableBean、配置 destroy-method 的 Bean 用 DisposableBeanAdapter 包装后,注册到销毁队列中。

循环依赖 & 三级缓存

上面提到了三级缓存,简单来说这个三级缓存就是后续例如调用getBean(String beanName)方法会去访问的缓存,用于快速地取出bean,这里复制文章中的话简短介绍一下:

singletonObjects:缓存所有构建好的bean
earlySingletonObjects:在bean构建过程中提前暴露出来的“半成品”bean
singletonFactories:用于创建“半成品”bean的工厂
(1)singletonObjects:singletonObjects中存储的都是完全构建好的bean,容器启动后在项目代码中我们通过getBean()从容器中获取bean实例时,就是直接从该缓存中获取,所以效率非常高,同时性能非常好。
(2)earlySingletonObjects:earlySingletonObjects主要保存在对象构建过程中提前暴露出来的“半成品”bean,是解决大部分循环依赖的关键。假设有两个Bean A和B,这两个Bean间存在相互依赖,且都不存在动态代理。那么当容器构建A时,实例化好A后,容器会把尚未注入依赖的A暂时放入earlySingletonObjects中,然后去帮A注入依赖。这时容器发现A依赖于B,但是B还没有构建好,那么就会先去构建B。容器在实例化好B时,同样需要帮B注入依赖,此时B发现自己依赖A,在获取A时就可以从earlySingletonObjects中获取尚未注入依赖的A引用,这样就不会阻塞B的构建,B完成构建后就又可以注入到A中,这样就解决了简单的循环依赖问题。
(3)singletonFactories:singletonFactories用来保存创建“半成品”bean的工厂实例,在Bean实例化好后,并不会直接将尚未注入依赖的bean直接放入到earlySingletonObjects中,而是将能够创建该“半成品”bean的工厂实例放入到singletonFactories中。这样我们在对外暴露“半成品”bean时就可以加入一些自定义的特殊处理逻辑。例如下图中普通切面代理就会在此处动些“手脚”。

至此,Springboot启动中对于bean的配置到此就结束了

发出结束执行的事件

listeners.started(context);

利用EventPublishingRunListener监听器,并执行其started方法,并且将创建的Spring容器(ApplicationContext)传进去了,创建一个ApplicationStartedEvent事件,并执行ConfigurableApplicationContext 的publishEvent方法,也就是说这里是在Spring容器中发布事件,并不是在SpringApplication中发布事件,和前面的starting是不同的,前面的starting是直接向SpringApplication中的11个监听器发布启动事件。

执行Runners

 this.callRunners(context, applicationArguments);

会获取容器中所有的ApplicationRunner的Bean实例、以及所用CommandLineRunner的Bean实例,并分别执行其中的run方法。这里也多用于自定义,可以自定义Springboot在启动流程时想要执行什么代码。


至此,Springboot启动流程结束,SpringApplication.run方法最终将构架的ApplicationContext返回。

Springboot的MVC流程

Springboot的mvc部分就和Spring没什么区别了,都是基于同一个 DispatcherServlet、同一套 HandlerMapping → HandlerAdapter → Controller → ViewResolver 的架构。

Springboot MVC服务的启动

上文中Springboot的run方法已经启动完毕,干的事情好像也只是返回一个ApplicationContext,那Springboot的监听端口,等待请求连接等web服务是从哪里启动的呢?

要回答这个问题,就得先搞清楚 Spring Boot 和底层 Servlet 容器(如 Tomcat)是怎么协同工作的。

Tomcat处理web请求靠的就是Servlet,如每一个.jsp在代码中都被映射成一个jsp_Servlet,而Spring mvc处理web请求并返回视图用的都是Controller。而众所周知,Spring Boot 中的 DispatcherServlet 是处理 HTTP 请求、映射到不同 @Controller 并返回视图(或数据)的核心类。

所以从Springboot的提供web服务的架构来看,Springboot MVC,Spring MVC就相当于(或者说集成在了)一个特殊的Tomcat Servlet(DispatcherServlet)。

如此而言,寻找Springboot MVC的服务启动点,就可以把眼光放在启动流程中Tomcat的启动之处。

简单来说,结合上文,Spring Boot 在启动时会创建一个内嵌的 Tomcat 容器,然后通过自动配置生成一个 ServletRegistrationBean<DispatcherServlet>,并将其注册到这个容器中。Tomcat 在启动时会遍历所有的 ServletContextInitializer(包括我们刚才说的 ServletRegistrationBean),调用它们的 onStartup() 方法,将 DispatcherServlet 加入到容器的 servlet 映射表里。至此,DispatcherServlet 便正式“绑定”在指定的 URL 上,开始监听并等待客户端的连接。

在这个架构中,DispatcherServlet 则变成了Tomcat的一个特殊 Servlet,而所有的 @Controller 和其他 MVC 组件,则由 DispatcherServlet 进一步调用,完成最终的业务逻辑处理。

Springboot MVC处理请求

请求的前半部分走的是Tomcat处理,Tomcat最后会在ApplicationFilterChain#doFilter最后的internalDoFilter方法,调用Servlet#service,进入到DispatcherServlet#service

这里对于我们学习安全值得注意的一点的是,原生的Spring MVC只有Tomcat流程对url的字符拦截、处理做了工作。也就是在Tomcat源码流程中说的:

0–31、127 是所有 C0 控制字符, false

! " # $ < > [ \ ] ^ \ { | } ~ 。false

所有字母、数字,以及 - . / : = ? @ _ 字符都为 true

而Spring并没有对URL的安全处理多加逻辑;只有Spring Security是新加了几个Filter,其中才有Spring实现的对URL字符的拦截。


这里会先调用其父类FrameworkServlet以及FrameworkServlet父类HttpServlet先进行一个简单的处理,依据请求方法,分别doGet,doPost…先进行一个简单的解析。

最后就来到了DispatcherServlet#doDispatch

它做了这样几件事:

1.构造HandlerExecutionChain和寻找HandlerAdapter:根据请求方式的不同找到相应的Handlermapping;再根据url请求参数,去获取handlermapping中的Handlermethod(此处为Controller方法对应的bean),再和系统拦截器封装成一个HandlerChain;再根据这个HandlerChain去寻找适配的HandlerAdapter。

这里有很多XXXHandler,HandlerXXX。先要理清楚这些东西以及Handler和Controller的关系具体可以去看看Springboot技术文档或者说文章的适配器模式有关部分https://blog.csdn.net/zxd1435513775/article/details/103000992

2.调用HandlerAdapter:HandlerAdapter调用对应的Handler去获得一个ModelAndView对象;这里实际上调用的就是Controller方法,得到返回值,根据返回值填写ModelAndView对象中的viewname变量。

这一步还有个值得注意的地方,viewname这个变量不仅仅是获得这个returnvalue这么简单。实际上Springboot还会在HandlerAdapter的执行链条中,获取返回值后,到ServletInvocableHandlerMethod#invokeAndHandle中去returnValueHandlers.handleReturnValue

image-20241015204005764

这里也会体现出ControllerRestController的区别–以及为什么我们宏观上用Controller会返回一个解析后的视图,而RestController只会返回一个字符串。

这里来过一下这个逻辑:

    public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
          ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

      HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
      if (handler == null) {
          throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
      }
      handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
  }

前面通过invoke HandlerMethod(controller/restcontroller)返回一个字符串作为returnvalue。

image-20241006100004124

会根据returnType去选择处理类型的handler。返回类型不就是个字符串吗?其实不然,returnType还包含了Handler等等更多信息:

image-20241006100620494

而根据selectHandler

    private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
      boolean isAsyncValue = isAsyncReturnValue(value, returnType);
      for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
          if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
              continue;
          }
          if (handler.supportsReturnType(returnType)) {
              return handler;
          }
      }
      return null;
  }

这里如果你用的RestController那么你会select到一个RequestResponseBodyMethodProcessor:

image-20241006101034645

且由于后续返回逻辑用的是它,最后mv = ha.handle(processedRequest, response, mappedHandler.getHandler());会返回null:

image-20241006101301331

而如果用的是Controller会select到ViewNameMethodReturnValueHandler

image-20241006122826589

最后会返回:

image-20241006123303698

3.applyDefaultViewName():对当前ModelAndView做判断,如果为null则进入defalutViewName部分处理,将URI path作为mav的viewname值。这里实际上也是第三种payload的原因。

4.processDispatchResult():渲染视图的最终点。

先处理ModelAndView,通过适配的ViewResolver将viewname解析,把mav里的viewname等等封装到一个具体的view里面,比如我们这里的Thymeleafview,其viewTemplateName里面就封装了我们的viewname。再调用Thymeleaf#render来解析视图。

如果传进去的viewname值包含::的话,即识别到这是片段表达式,会先进行预处理-即解析在变量表达式中的SPEL语句。最后根据文件名返回相应的模板文件(还会对模板文件里面的SPEL表达式进行解析–Springboot路径穿越文件上传拿shell的一个点)

最后将html写入response域。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇