Spring Boot

felix.shao2025-04-03Spring BootSpring Boot

Spring Boot

概念

谈谈你对 SpringBoot 的理解,它有哪些特性

 SpringBoot 的用来快速开发 Spring 应用的一个脚手架、其设计目的是用来简化 Spring 应用的初始搭建以及开发过程。

  • SpringBoot 提供了很多内置的 Starter 结合自动配置,对主流框架无配置集成、开箱即用。
  • SpringBoot 简化了开发,采用 JavaConfig 的方式可以使用零 xml 的方式进行开发。
  • SpringBoot 内置 Web 容器无需依赖外部 Web 服务器,省略了 Web.xml,直接运行 jar 文件就可以启动 web 应用。
  • SpringBoot 帮我管理了常用的第三方依赖的版本,减少出现版本冲突的问题。
  • SpringBoot 自带了监控功能,可以监控应用程序的运行状况,或者内存、线程池、Http 请求统计等,同时还提供了优雅关闭应用程序等功能。
Spring 和 SpringBoot 的关系和区别

 SpringBoot 是 Spring 生态的产品。Spring Framework 是一个容器框架。
 SpringBoot 它不是一个框架,它是一个可以快速构建基于 Spring 的脚手架(里面包含了 Spring 和各种框架),为开发 Spring 生态其它框架更加方便。
 两个不是同一个层面的东西,没有可比性。

SpringBoot 的核心注解

 详细注解可以打开 spring boot 源码包看下,一些核心注解如下。

  • @SpringBootApplication。标识一个 Spring Boot 工程,由 3 个注解混合组成。
  • @SpringBootConfiguration。其实就是一个 @Configuration。
  • @EnableAutoConfiguration。向容器中导入一个 Selector,用来加载 Classpath 下 SpringFactories 中所定义的自动配置类,将这些自动加载为配置 bean。
  • @ConditionalOnXXX 系列注解。方便做自定义定制开发。
SpringBoot 中有几种定义 Bean 的方式
  1. @Bean。
  2. @Component。
  3. @Controller、@RestController、@Service、@Repository。@Controller 注解在 SpringMVC 中由特殊的作用。
  4. @RestControllerAdvice、@ControllerAdvice。@ControllerAdvice 相当于对于 Controller 的切面,可以绑定 PropertyEditor。
  5. @Configuration。@Configuration 主要表示一个 Bean 是一个配置 Bean,利用这个 Bean 可以对 Spring 进行配置,比如扫描路径、定义其他的 Bean。
  6. @Import。
  7. BeanDefinition,即通过注册 BeanDefinition 的方式定义 Bean。
  8. <bean />
如何理解 spring.factories 文件的作用

 spring.factories 文件是 Spring 框架中的一个机制,用于实现在类路径下自动发现和加载扩展点的功能。这个文件的作用类似于 Java 的服务提供者接口(Service Provider Interface,SPI)机制,它允许第三方库或模块在应用程序中注册自己的实现,从而扩展或定制 Spring 框架的行为。
 我们主要从以下方面来理解其作用。

  1. 扩展点的自动发现:Spring 框架内部会在类路径下搜索并加载 spring.factories 文件,并根据文件中的配置来自动发现扩展点。这些扩展点可以是自定义的类、接口或配置类,用于在 Spring 应用程序中提供额外的功能、特性或行为。
  2. 解耦和可插拔性:spring.factories 文件的作用类似于插件机制,它允许你将自定义的实现或功能集成到 Spring 框架中,而不需要显式地修改 Spring 的源代码。这提高了代码的解耦性和可维护性,使得应用程序更具有可插拔性。
  3. 如果你的项目是一个多模块项目,每个模块都可以有自己的 spring.factories 文件。这样,每个模块都可以在应用程序中注册自己的扩展点,实现模块之间的松耦合和灵活性。
  4. 实现框架的定制和拓展:Spring 框架本身也使用了 spring.factories 文件来实现一些定制和扩展。这意味着你可以通过自定义的 spring.factories 文件来覆盖或替代 Spring 框架默认的行为,以满足特定的需求。
如何理解 SpringBoot 的 @SpringBootApplication 注解
  • 组合注解。
  • 主程序入口。
  • 约定大于配置。
  • 配置扩展。
SpringBoot 如何优化启动速度
  • 代码优化
    • 延迟初始化 Bean。
    • 创建扫描索引。即使用 spring-context-indexer。
    • 减少 @ComponentScan、@SpringBootApplication 扫描类时候的范围。
    • 关闭 SpringBoot 的 JMX 健监控,设置 Spring.jmx.enabled=false。
    • 设置 JVM 参数 -noverify,不对类进行验证。
    • 对非必要启动时加载的 Bean,延迟加载。
    • 使用 Spring Boot 的全局懒加载
    • AOP 切面尽量不使用注解方式,这会导致启动时扫描全部方法。
    • 关闭 endpoint 的一些监控功能。
    • 排除项目多余的依赖 jar。
    • swagger 扫描接口时,指定只扫描某个路径下的类。
    • Feign 客户端接口的扫描缩小包扫描范围。
  • 升级 JDK17。因为新特性,支持 JVM 内存返还给操作系统。
  • 升级 Spring Boot。spring-graalvm-native 是 SpringBoot6/SpringBoot3 非常重大的一个特性,支持 GraalVM 将 SpringBoot 的应用程序编译成本地可执行的镜像文件,可以显著提升启动速度、峰值性能以及减少内存使用。
SpringBoot 读取配置的方式
  • @Value。
  • @ConfigurationProperties。
  • Environment。可以 @Resource 注入或者实现 EnvironmentAware 接口。
  • PropertySources 读取 properties 文件。可以加载外部配置文件,该方式只能获取 properties 配置文件。
  • PropertySources 读取 yaml 文件。需要额外配置 PropertySourcesPlaceholderConfigurer 支持。
  • 使用原生的 InputStream 读取 Properties 文件。
SpringBoot 解决跨域的方式

 参考 SpringBoot 解决跨域的 5 种方式open in new window

SpringBoot 如何对敏感信息进行加密

 使用 jasypt-spring-boot-starter 第三方插件加密。

原理

SpringBoot 自动配置原理

 详细可以见源码分析部分,以下是核心步骤说明。

  1. 通过 @SpringBootApplication 引入 @EnableAutoConfiguration(负责启动自动配置功能)。
  2. @EnableAutoConfiguration 引入了 @Import(AutoConfigurationImportSelector.class)。
  3. Spring 容器启动时,加载 IOC 容器会解析 @Import 注解。
  4. @Import 导入了一个 DeferredImportSelector,它会使 SpringBoot 的自动配置类的顺序在最后,这样方便我们扩展和覆盖。
  5. 然后读取所有的 /META-INF/spring.factories
  6. 过滤出所有 AutoConfigurationClass 类型的类。
  7. 最后通过 @Condition 排除无效的自动配置类。

TIP

 以下的我们看源码可以关注的点。

  • Spring boot 启动速度做了相关优化
    • spring-boot-autoconfigure.jar 包中通过配置 spring-autoconfigure-metadata.properties,在解析 Condition 时可以不用解析 Class 后再解析注解,提高解析速度。
    • 过滤 Condition 时使用了多线程优化了处理速度。
  • 如何理解 SpringBoot 的自动配置?基于这个可以简单介绍下如 JdbcTemplateAutoConfiguration。
为什么 SpringBoot 的 jar 可以直接运行
  1. SpringBoot 提供了一个插件 spring-boot-maven-plugin 用于把程序打包成一个可执行的 jar 包。
  2. SpringBoot 应用打包之后,生成一个 Fat jar(jar 包中包含 jar),包含了应用依赖的 jar 包和 Spring Boot loader 相关的类。
  3. java -jar 会去找 jar 中的 /META-INF/MANIFEST.MF 文件,找到真正的启动类 Main-Class: org.springframework.boot.loader.JarLauncher
  4. JarLauncher 负责创建一个 LaunchedURLClassLoader 来加载 /BOOT-INF/lib 下面的 jar,并以一个新线程启动 /META-INF/MANIFEST.MF 文件中的 Start-Class: com.spring.boot.demo.DemoApplication 的 main 函数()。
SpringBoot 的启动原理(SpringBoot 启动过程有哪些步骤)

 详细可以见源码分析部分,从 SpringApplication.run(DemoApplication.class, args) 跟踪源码,以下是核心步骤说明。

  • 运行 main 方法:初始化 SpringApplication 从 spring.factories(多个 jar 包都有) 读取 ApplicationContextInitializer,SpringApplication 构造方法主要逻辑如下。
    • 将启动类放入 primarySources。
    • 推算当前 web 应用类型 webApplicationType(反射判断)。
    • 读取 ApplicationContextInitializer 初始化器。
    • 读取 ApplicationListener 监听器。
    • 将 main 方法所在的类放入 mainApplicationClass 中。
  • 运行 run 方法,几个主要步骤如下。
    • prepareEnvironment:读取环境变量、配置信息。
    • createApplicationContext:创建 SpringApplication 上下文,如 AnnotationConfigServletWebServerApplicationContext。
    • prepareContext:预初始化上下文:将启动类作为配置类进行读取,将配置注册为 BeanDefinition。
    • refreshContext:调用 refresh 加载 IOC 容器。
      • invokeBeanFactoryPostProcessors 解析 @Import,加载所有的自动配置类。
      • onRefresh 创建(内置)servlet 容器。
    • 在这个过程中,SpringBoot 会调用很多监听器对外进行扩展,各个 Spring 事件位于 Spring 不同的生命周期,可以参考 SpringApplicationRunListener 类不同方法理解。
      • 计时开始后,发布 ApplicationStartingEvent 事件。
      • 读取环境配置信息后,发布 ApplicationEnvironmentPreparedEvent 事件。
      • 启动完成后,发布 ApplicationStartedEvent 事件,比开始时多了 context 上下文信息。
      • 发布 ApplicationReadyEvent 事件。
      • 如果启动失败,则发布 ApplicationFailedEvent 事件。

 各个版本可能会有细微差异,以上主要逻辑不会变化太多。

TIP

 上述的以下两个重点一定要讲到。

  • createApplicationContext:创建 SpringApplication 上下文,如 AnnotationConfigServletWebServerApplicationContext。
  • prepareContext:预初始化上下文:将启动类作为配置类进行读取,将配置注册为 BeanDefinition
SpringBoot 内置 Tomcat 启动原理?

 可以参考 手写模拟 SpringBoot 核心流程 部分理解,以下是其核心流程。

  • 当依赖 spring-boot-starter-web 依赖(@ConditionalOnClass(ServletRequest.class))时会在应用中添加 ServletWebServerFactoryAutoConfiguration,即 servlet 容器自动配置类。
  • 该自动配置类提供 @Import 导入了可用(提供 @ConditionalOnClass 判断觉得使用哪一个)的一个 Web 容器工厂(默认 Tocmat)。
  • 在内置 Tomcat 类中配置了一个 TomcatServletWebServerFactory 的 Bean(Web 容器工厂)。
  • 它会在 SpringBoot 启动时,加载 IOC 容器(refresh),创建内嵌的 Tomcat 并启动(onRefresh)。
  • 导入 web 依赖时,同样 DispatcherServletAutoConfiguration 也会被添加,该配置类会添加 DispatcherServlet 类,同时通过其内部类 DispatcherServletRegistrationConfiguration 注册 DispatcherServlet,即给容器中添加了 ServletRegistrationBean<DispatcherServlet>

TIP

SpringBoot 外部 Tomcat 启动原理?

 参考文章理解 SpringBoot 使用外部 Tomcat 方法及启动原理open in new window
 看完上述文章有个疑问,可以参考这个理解 SpringBoot 中的 ServletInitializer?open in new window。简单来说就是外部 Tomcat 按约定会固定读取 ServletContainerInitializer 实例,但是 Spring Boot jar 启动时不会。

如何理解 SpringBoot 的 Starter 机制
  1. 提供预配置的依赖:Spring Boot Starters 是一组预配置的依赖项集合,用于启用特定类型的功能。例如:可以使用 spring-boot-starter-web 启用 Web 应用程序相关的功能,包括内嵌的 Web 服务器、Spring MVC、Jackson 等。
  2. 简化依赖管理:Spring Boot Starters 简化了项目的依赖管理。通过引入适当的 Starter,你不需要手动指定每个相关的依赖项,Spring Boot 会自动处理它们的版本兼容性和配置。这有助于避免版本冲突和配置错误。
  3. 自动配置:Spring Boot Starters 还包含了与功能相关的自动配置类。这些自动配置类根据应用程序的依赖和配置,自动配置了必要的组件和设置,这使得你可以快速地启用和使用功能,而无需手动配置每个组件。
  4. 遵循约定:Spring Boot Starters 遵循约定大于配置的原则,它们约定了如何将依赖和配置组合在一起,使得应用程序开发更加一致和高效。
  5. 自定义扩展:尽管 Spring Boot Starters 提供了默认的依赖和配置,你依赖可以根据需要进行自定义扩展,你可以在自己的项目中覆盖默认的配置,添加额外的依赖项,或者通过属性配置文件来修改 Starter 的行为。
如何理解 SpringBoot 条件注解

 如下部分条件注解,可以自行查看下对应的源码,跟下流程即可。

  • ConditionalOnClass。
  • ConditionalOnMissingClass。
  • ConditionalOnBean。
  • ConditionalOnMissingBean。
  • ConditionalOnProperty。
  • ConditionalOnExpression。
SpringBoot 的配置优先级是怎样的
 配置来源

 Spring Boot 的配置优先级是通过不同来源的配置属性值进行合并和覆盖来确定的。配置属性可以来自多个不同的来源,例如:

  1. 应用默认值(Default Values):Spring Boot 提供了许多内置的默认属性值,用于定义框架和组件的默认行为。
  2. 内部配置文件(Application Properties and YAML):你可以在应用程序的 application.properties 或 application.yaml 文件中设置配置属性值。这些属性会被加载到应用程序上下文中。
  3. 外部属性文件(External Properties):除了应用默认值和内部配置文件,你还可以使用外部的属性文件,例如通过命令行参数、环境变量或配置文件等方式传递属性值。
  4. 命令行参数(Command Line Arguments):你可以通过命令行参数来覆盖应用程序的配置属性。例如,java -jar myapp --my.property=value
  5. 系统环境变量。你可以使用系统环境变量来设置配置属性值,这些变量会被自动映射到应用程序上下文中的属性。
  6. 属性配置顺序:配置属性会按照上述顺序逐层覆盖和合并,具体来说,命令行参数和系统环境变量的优先级最高,会覆盖应用默认值、内部配置文件和外部属性文件中的值。

 我们通过源码了解下其加载顺序。

 prepareEnvironment

 注意这块发现版本问题,切换 spring boot 为 2.7.12 再进行分析。

public class SpringApplication {
    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
		// Create and configure the environment
        // environment 创建完后,我们 DEBUG 可以看到如下 environment.propertySources 属性列表,环境变量值可以具体打开看下,前面的优先级高于后面的(从前往后,拿到值就返回了)
        // StubPropertySource {name='servletConfigInitParams'} tomcat 参数配置
        // StubPropertySource {name='servletContextInitParams'} tomcat 参数配置
        // MapPropertySource {name='systemProperties'} JVM 虚拟机的参数、-D 指定的参数,比如 IDEA 的 VM Optional 添加 -Dmy.vmparam=2025 这里就可以看到
        // SystemEnvironmentPropertySource {name='systemEnvironment'} 操作系统的环境变量,比如 IDEA 的 Environment variables 添加 my.env=myEnv 这里就可以看到
		ConfigurableEnvironment environment = getOrCreateEnvironment();
        // environment.propertySources 最前面会添加 SimpleCommandLinePropertySource {name='commandLineArgs'},即优先级最高
        // 比如 IDEA 的 Program arguments 添加 --my.cmd=myCmd,这里就可以看到
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		ConfigurationPropertySources.attach(environment);

        // 发布 ApplicationEnvironmentPreparedEvent 事件,表示环境以及准备好了
        // ctrl+shift+f 搜索,scope 窗口找到 EnvironmentPostProcessorApplicationListener 如下,注意版本不同会有些差异,会从 spring.factories 拿出所有的 EnvironmentPostProcessor 进一步的处理
		listeners.environmentPrepared(bootstrapContext, environment);
		DefaultPropertiesPropertySource.moveToEnd(environment);
		Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
				"Environment prefix cannot be set via properties.");
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
			environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
		}
        // 把所有的 PropertySources 封装为一个 ConfigurationPropertySourcesPropertySource
        // 然后添加到 environment 中,放在首位
		ConfigurationPropertySources.attach(environment);
		return environment;
	}
}

public interface EnvironmentPostProcessorsFactory {
    static EnvironmentPostProcessorsFactory fromSpringFactories(ClassLoader classLoader) {
		return new ReflectionEnvironmentPostProcessorsFactory(classLoader,
				SpringFactoriesLoader.loadFactoryNames(EnvironmentPostProcessor.class, classLoader));
	}
}

public class EnvironmentPostProcessorApplicationListener implements SmartApplicationListener, Ordered {
    private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
		ConfigurableEnvironment environment = event.getEnvironment();
		SpringApplication application = event.getSpringApplication();
        // 进一步的处理 environment,debug 时只有这些
        // RandomValuePropertySourceEnvironmentPostProcessor
        // SystemEnvironmentPropertySourceEnvironmentPostProcessor
        // SpringApplicationJsonEnvironmentPostProcessor 将命令行参数添加 --spring.application.json={\"k1\":\"v1\"} 的值存到 environment.propertySources,优先级在 systemProperties 前面
        // CloudFoundryVcapEnvironmentPostProcessor
        // DebugAgentEnvironmentPostProcessor
        // ConfigDataEnvironmentPostProcessor
        // CloudFoundryVcapEnvironmentPostProcessor
        // IntegrationPropertiesEnvironmentPostProcessor
		for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(application.getResourceLoader(),
				event.getBootstrapContext())) {
			postProcessor.postProcessEnvironment(environment, application);
		}
	}
}

 注意有多个 spring.factories 文件。

org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor,\
org.springframework.boot.env.RandomValuePropertySourceEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor,\
org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor
 RandomValuePropertySourceEnvironmentPostProcessor

@Value("${random.str}") 类似,会将生成的属性放到 environment.propertySources 最后,即最低优先级别,未限制时,值包含了字符。

 SpringApplicationJsonEnvironmentPostProcessor

 解析 --spring.application.json={\"k1\":\"v1\"} 并将其对应的 KV 值添加到 environment.propertySources 中,command 参数配置时,对应优先级在 systemProperties 前面。

 ConfigDataEnvironmentPostProcessor

 spring.config.location 可以指定加载文件路径,如 -Dspring.config.location=optional:classpath:/,我们直接看源码理解。

public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
		postProcessEnvironment(environment, application.getResourceLoader(), application.getAdditionalProfiles());
	}

    void postProcessEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader,
			Collection<String> additionalProfiles) {
		try {
			this.logger.trace("Post-processing environment to add config data");
			resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
			getConfigDataEnvironment(environment, resourceLoader, additionalProfiles).processAndApply();
		}
		catch (UseLegacyConfigProcessingException ex) {
			this.logger.debug(LogMessage.format("Switching to legacy config file processing [%s]",
					ex.getConfigurationProperty()));
			configureAdditionalProfiles(environment, additionalProfiles);
			postProcessUsingLegacyApplicationListener(environment, resourceLoader);
		}
	}

    ConfigDataEnvironment getConfigDataEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader,
			Collection<String> additionalProfiles) {
		return new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, environment, resourceLoader,
				additionalProfiles, this.environmentUpdateListener);
	}
}

class ConfigDataEnvironment {
    // 指定配置加载路径,注意如果指定了就不会用默认的
    static final String LOCATION_PROPERTY = "spring.config.location";

    // 默认配置加载路径
    static final ConfigDataLocation[] DEFAULT_SEARCH_LOCATIONS;
	static {
		List<ConfigDataLocation> locations = new ArrayList<>();
        /**
         * 优先级从左到右、从上到下,具体如下顺序优先级从高到低。
         * 手动指定配置新增的
         * file:./config/* / 注意这里会被当注释,搞了个空格特殊处理 
         * file./config
         * file:/
         * classpath:/config/
         * classpath:/
         */
		locations.add(ConfigDataLocation.of("optional:classpath:/;optional:classpath:/config/"));
		locations.add(ConfigDataLocation.of("optional:file:./;optional:file:./config/;optional:file:./config/*/"));
		DEFAULT_SEARCH_LOCATIONS = locations.toArray(new ConfigDataLocation[0]);
	}

    ConfigDataEnvironment(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
			ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection<String> additionalProfiles,
			ConfigDataEnvironmentUpdateListener environmentUpdateListener) {
		Binder binder = Binder.get(environment);
		UseLegacyConfigProcessingException.throwIfRequested(binder);
		this.logFactory = logFactory;
		this.logger = logFactory.getLog(getClass());
		this.notFoundAction = binder.bind(ON_NOT_FOUND_PROPERTY, ConfigDataNotFoundAction.class)
			.orElse(ConfigDataNotFoundAction.FAIL);
		this.bootstrapContext = bootstrapContext;
		this.environment = environment;
		this.resolvers = createConfigDataLocationResolvers(logFactory, bootstrapContext, binder, resourceLoader);
		this.additionalProfiles = additionalProfiles;
		this.environmentUpdateListener = (environmentUpdateListener != null) ? environmentUpdateListener
				: ConfigDataEnvironmentUpdateListener.NONE;
		this.loaders = new ConfigDataLoaders(logFactory, bootstrapContext, resourceLoader.getClassLoader());
		this.contributors = createContributors(binder);
	}

    private ConfigDataEnvironmentContributors createContributors(Binder binder) {
		this.logger.trace("Building config data environment contributors");
		MutablePropertySources propertySources = this.environment.getPropertySources();
		List<ConfigDataEnvironmentContributor> contributors = new ArrayList<>(propertySources.size() + 10);
		PropertySource<?> defaultPropertySource = null;
		for (PropertySource<?> propertySource : propertySources) {
			if (DefaultPropertiesPropertySource.hasMatchingName(propertySource)) {
				defaultPropertySource = propertySource;
			}
			else {
				this.logger.trace(LogMessage.format("Creating wrapped config data contributor for '%s'",
						propertySource.getName()));
				contributors.add(ConfigDataEnvironmentContributor.ofExisting(propertySource));
			}
		}
        // spring.config.location 逻辑等处理
		contributors.addAll(getInitialImportContributors(binder));
		if (defaultPropertySource != null) {
			this.logger.trace("Creating wrapped config data contributor for default property source");
			contributors.add(ConfigDataEnvironmentContributor.ofExisting(defaultPropertySource));
		}
		return createContributors(contributors);
	}

    private List<ConfigDataEnvironmentContributor> getInitialImportContributors(Binder binder) {
		List<ConfigDataEnvironmentContributor> initialContributors = new ArrayList<>();
        // 如果指定了就不会用默认的
		addInitialImportContributors(initialContributors, bindLocations(binder, IMPORT_PROPERTY, EMPTY_LOCATIONS));
		addInitialImportContributors(initialContributors,
				bindLocations(binder, ADDITIONAL_LOCATION_PROPERTY, EMPTY_LOCATIONS));
		addInitialImportContributors(initialContributors,
				bindLocations(binder, LOCATION_PROPERTY, DEFAULT_SEARCH_LOCATIONS));
		return initialContributors;
	}

    void processAndApply() {
		ConfigDataImporter importer = new ConfigDataImporter(this.logFactory, this.notFoundAction, this.resolvers,
				this.loaders);
		registerBootstrapBinder(this.contributors, null, DENY_INACTIVE_BINDING);
		ConfigDataEnvironmentContributors contributors = processInitial(this.contributors, importer);
		ConfigDataActivationContext activationContext = createActivationContext(
				contributors.getBinder(null, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE));
		contributors = processWithoutProfiles(contributors, importer, activationContext);
		activationContext = withProfiles(contributors, activationContext);
		contributors = processWithProfiles(contributors, importer, activationContext);
		applyToEnvironment(contributors, activationContext, importer.getLoadedLocations(),
				importer.getOptionalLocations());
	}
}

 注意如果指定了 spring.config.location 默认的会失效,如果不想失效,则使用如下方式。

  • spring.config.import:在默认的基础上新增。
  • spring.config.additional-location:在默认的基础上新增。

 application.properties > application.yml > application.yaml 文件的优先级更高。源码略。
 spring.profiles.active 对应的文件比默认的文件优先级高,即类似 application-dev.yaml 比 application.yaml 如配置重复,则取 application-dev.yaml 的配置。

SpringBoot 为什么默认使用 Cglib 动态代理

 Spring Boot 默认使用 Cglib 动态代理是基于一些技术和设计考虑,主要包括以下几点原因。

  1. 性能和速度:Cglib 动态代理在性能上通常比标准的 JDK 动态代理更快。Cglib 直接通过字节码生成子类来实现代理,避免了一些反射操作,因此在方法调用等方面通常更加高效。
  2. 无需接口:JDK 动态代理要求目标类必须实现一个接口,而 Cglib 动态代理不需要。这使得 Cglib 更适用于那些没有接口的类,从而扩展了动态代理的适用范围。
  3. Spring Boot 选择 Cglib 动态代理可以使你的类无需实现任何接口或继承特定的类,从而减少了对源代码的侵入性。这对于集成第三方库或需要代理的现有类特别有用。
  4. 方便集成:Spring Boot 默认提供了 Cglib 相关的依赖,因此在应用程序中使用 Cglib 动态代理非常方便。
SpringBoot 可以同时处理多少请求

 以 Tomcat 举例,与这四个参数有关。

  • server.tomcat.threads.min-spare:最少的工作线程数,默认大小是 10。该参数相当于长期工,如果并发请求的数量达不到 10,就会依次使用这几个线程去处理请求。
  • server.tomcat.threads.max:最多的工作线程数,默认大小是 200。该参数相当于临时工,如果并发请求的数量在 10 到 200 之间,就会使用这些临时工线程进行处理。
  • server.tomcat.max-connections:最大连接数,默认大小是 8192。表示 Tomcat 可以处理的最大请求数量,超过 8192 的请求就会被放入到等待队列。
  • server.tomcat.accept-count:等待队列的长度,默认大小是 100。

 如果并发请求数量低于 server.tomcat.threads.max,则会被立即处理,超过的部分会先进行等待,如果数量超过 max-connections 与 accept-count 之和,则多余的部分则会被直接丢弃(HTTP 客户端连接超时时间,超时)。

 详细参考文章理解 SpringBoot 可以同时处理多少请求?open in new window

SpringBoot 的 Spring MVC 自动配置原理分析

 详细见专题小节 spring-boot2. SpringBoot 的 Spring MVC
 简要回答梳理如下。

    1. Spring Boot 启动时,扫描并注入了 WebMvcAutoConfiguration 实例到容器中。
    1. WebMvcAutoConfiguration 会注入默认的 WebMvcAutoConfigurationAdapter 配置类,其要点如下。
    • 2.1 注入内置的视图解析器。
      • 2.1.1 ContentNegotiatingViewResolver:视图解析器入口,本身不解析视图,委托给其他视图解析器处理。
      • 2.1.2 InternalResourceViewResolver:内部资源视图解析器。支持提供静态资源,包括支持 WebJars 的解析。
      • 2.1.3 BeanNameViewResolver:Bean 名称视图解析器。
    • 2.2 将容器中所有实现了 WebMvcConfigurer 接口的 Bean 都自动注入进来,添加到 configurers 中。
    • 2.3 HttpMessageConverters、格式化器的导入。
    • 2.4 MessageCodesResolver 的导入。
    1. WebMvcAutoConfiguration 会注入默认的 EnableWebMvcConfiguration 配置类,其要点如下。
    • 3.1 欢迎页的处理。
    • 3.2 校验器、ConfigurableWebBindingInitializer 的导入。

其他

SpringBoot MockMVC 测试的使用

 详见 MockMvcTestopen in new window 代码。
 关注下要点。

  • 框架可以在不启动 Web 服务下测试,不依赖于容器。
  • 可以正常测试连接 DB 服务,处理的场景。

补充

Spring Boot 启动时怎样执行 SQL。

附录一、参考文献

Last Updated 4/21/2025, 2:58:45 PM