SpringBoot 源码解析 - 持续更新

开始

  1. spring initilizer:根据依赖构建工具、springboot 版本等生成 Java 工程。
  2. 手把手教你手写一个最简单的 Spring Boot Starter
  1. Starter 命名规则
    Spring 官方定义的 Starter 通常命名遵循的格式为 spring-boot-starter-{name},例如 spring-boot-starter-data-mongodb。Spring 官方建议,非官方 Starter 命名应遵循 {name}-spring-boot-starter 的格式,例如,myjson-spring-boot-starter。
  2. Starter 提供了以下功能
    整合了模块需要的所有依赖,统一集合到 Starter 中。
    提供了默认配置,并允许我们调整这些默认配置。
    提供了自动配置类对模块内的 Bean 进行自动装配,注入 Spring 容器中。
  3. SpringBoot 项目启动时,类加载器会从 META-INF/spring.factories 加载给定类型的工厂实现的完全限定类名。也就是说类加载器得到工程中所有 jar 包中的 META-INF/spring.factories 文件资源,从而得到了一些包括自动配置相关的类的集合,然后将它们实例化,放入 Spring 容器中。

源码分析

入口程序

@SpringBootApplication
public class SpringDemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringDemoApplication.class, args);
	}
}

SpringApplication.run 根据入参 SpringDemoApplication 找到当前 SpringBoot 应用的配置类(即标注了 @SpringBootApplication 的类)

在这里插入图片描述

SpringFactoriesLoader

Java SPI VS Spring SPI

自动装配如何实现

todo:注解上的注解 Spring 或 Java 是怎么解析的?

从 @SpringBootApplication 入手

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {//...}

其中 @EnableAutoConfiguration 的实现 AutoConfigurationImportSelector,会从 /META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 配置文件下读取自动装配的配置类

// From org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getCandidateConfigurations

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
	List<String> configurations = new ArrayList<>(
			SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
	// 从 /META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 配置文件中读取自动装配的类
	ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
	Assert.notEmpty(configurations,
			"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
					+ "are using a custom packaging, make sure that file is correct.");
	return configurations;
}

// From org.springframework.boot.context.annotation.ImportCandidates#load

private static final String LOCATION = "META-INF/spring/%s.imports";

public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
	Assert.notNull(annotation, "'annotation' must not be null");
	ClassLoader classLoaderToUse = decideClassloader(classLoader);
	String location = String.format(LOCATION, annotation.getName());
	Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
	List<String> importCandidates = new ArrayList<>();
	while (urls.hasMoreElements()) {
		URL url = urls.nextElement();
		importCandidates.addAll(readCandidateConfigurations(url));
	}
	return new ImportCandidates(importCandidates);
}

从自动装配类主要有:

org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration
org.springframework.boot.autoconfigure.netty.NettyAutoConfiguration
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
// SpringMVC 相关配置
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration

SpringBoot 启动过程

ServletWebServerApplicationContext
ServletWebServerApplicationContext 是 SpringBoot 「Web应用」对应的容器,其重写了 onRefresh(), 在 Spring 刷新容器时,会回调该方法。
AbstractApplicationContext#refresh() 模板流程

AbstractApplicationContext#refresh() 模板流程

ServletWebServerApplicationContext#onRresh() :

@Override
protected void onRefresh() {
	super.onRefresh();
	try {
		createWebServer();
	}
	catch (Throwable ex) {
		throw new ApplicationContextException("Unable to start web server", ex);
	}
}

private void createWebServer() {
	WebServer webServer = this.webServer;
	ServletContext servletContext = getServletContext();
	if (webServer == null && servletContext == null) {
		StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
		ServletWebServerFactory factory = getWebServerFactory();
		createWebServer.tag("factory", factory.getClass().toString());
		// 注册 
		this.webServer = factory.getWebServer(getSelfInitializer());
		createWebServer.end();
		// 注册优雅关闭 Servlet 容器的回调
		getBeanFactory().registerSingleton("webServerGracefulShutdown",
				new WebServerGracefulShutdownLifecycle(this.webServer));
		// 注册 启动 Servlet 容器,以及在容器 ready 后发送 ServletWebServerInitializedEvent 的回调
		getBeanFactory().registerSingleton("webServerStartStop",
				new WebServerStartStopLifecycle(this, this.webServer));
	}
	else if (servletContext != null) {
		try {
			getSelfInitializer().onStartup(servletContext);
		}
		catch (ServletException ex) {
			throw new ApplicationContextException("Cannot initialize servlet context", ex);
		}
	}
	initPropertySources();
}

该方法完成以下几个关键行为:

  1. 创建 ServletWebServerFactory 对象,并放入 Spring 容器中,并据此创建并启动 Servlet 容器;
    • ServletWebServerFactory 在 ServletWebServerFactoryAutoConfiguration、ServletWebServerFactoryConfiguration 配置类中被注入到 Spring 容器
// From ServletWebServerFactoryAutoConfiguration.java

@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, // 注入相关的 BeanPostProcessor
		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })


// From ServletWebServerFactoryConfiguration.java

@Configuration(proxyBeanMethods = false)
// 在类路径中包含了「指定类:Tomcat.class 等」时,向 Spring 容器中注入 TomcatServletWebServerFactory
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {

	@Bean
	TomcatServletWebServerFactory tomcatServletWebServerFactory(
			ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
			ObjectProvider<TomcatContextCustomizer> contextCustomizers,
			ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
		TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
		factory.getTomcatConnectorCustomizers()
			.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
		factory.getTomcatContextCustomizers()
			.addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
		factory.getTomcatProtocolHandlerCustomizers()
			.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
		return factory;
	}

}

// From WebServerFactoryCustomizerBeanPostProcessor.java

private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
	LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory)
		.withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
		// 将 ServerProperties 中配置的 Servlet 容器属性设置到 WebServerFactory 中
		.invoke((customizer) -> customizer.customize(webServerFactory));
}
  1. 注册回调:在 Servlet 容器创建完成后,为 ServletWebServerApplicationContext 容器设置 serverContext;
  2. 注册回调:启动 Web 应用(webServer.start()),并发送 ServletWebServerInitializedEvent 事件;
  3. 注册回调:在 Spring 容器销毁后,优雅关闭 Servlet 容器;

ConfigurationProperties

Annotation for externalized configuration. Add this to a class definition or a {@code @Bean} method in a {@code @Configuration} class if you want to bind and validate some external Properties (e.g. from a .properties file).

todo:原理是啥?

SpringBoot @ConfigurationProperties详解

使用: 「@ConfigurationProperties + @Configuration 组合」 或 @EnableConfigurationProperties

ObjectProvider

A variant of {@link ObjectFactory} designed specifically for injection points, allowing for programmatic optionality and lenient not-unique handling.

ObjectProvider使用说明

如果待注入参数的Bean为空或有多个时,便是 ObjectProvider 发挥作用的时候了。

  1. 如果注入实例为空时,使用 ObjectProvider 则避免了强依赖导致的依赖对象不存在异常;
  2. 如果有多个实例,ObjectProvider 的方法会根据 Bean 实现的 Ordered 接口或 @Order 注解指定的先后顺序获取一个 Bean。从而了提供了一个更加宽松的依赖注入方式。ObjectProvider 实现了 Iterable 接口,即也可以同时注入多个实例

目前 Spring 主要在 org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency 方法中使用了它

应用
// From ServletWebServerFactoryConfiguration.java

@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory(
		ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
		ObjectProvider<TomcatContextCustomizer> contextCustomizers,
		ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
	TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
	factory.getTomcatConnectorCustomizers()
		.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
	factory.getTomcatContextCustomizers()
		.addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
	factory.getTomcatProtocolHandlerCustomizers()
		.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
	return factory;
}

可以看出 TomcatServletWebServerFactory 是通过 ObjectProvider 注入相关依赖的,在容器中没有相关的 Bean 对象时也能正常运行,而且使得程序有很好的扩展性,即程序员可以通过实现接口 TomcatConnectorCustomizer、TomcatContextCustomizer、TomcatProtocolHandlerCustomizer ,并手动创建其 Bean 对象来对创建出的 TomcatServer 对象施加影响。

Servlet 组件注入

ServletContainerInitializer
  1. Tomcat 是怎样处理 SpringBoot应用的?

在Servlet 3.0 之后,可以通过 ServletContainerInitializer 动态向 Tomcat 中新增 Servlet、 Filter,这样 Tomcat 就不必强依赖 web.xml。

除了以 Jar 的形式直接执行 Main 方法外, Spring Boot 还支持将 Boot 应用打包成 War 文件,部署到标准容器中,不使用 Embedded 容器。相比执行 main 方法(SpringApplication.run(SpringDemoApplication.class, args);)来启动 Spring Boot 应用,以 Web 应用提供时, Boot 的能力是如何提供的呢?
SpringServletContainerInitializer
Tomcat 启动时会依次遍历通过 SPI 提供的 ServletContainerInitializer 实现类,首先解析 @HandlesTypes 得到其属性 value 值对应类的所有实现类(解析过程由 Servlet容器 提供支持:利用字节码扫描框架(例如ASM、BCEL)从classpath中扫描出来),然后传递给 ServletContainerInitializer#onStartup 方法。

在 TomcatServletWebServerFactory#configureContext 中,向 Tomcat 代表当前 Web应用程序的容器组件添加了 ServletContainerInitializer(TomcatStarter)。TomcatStarter#onStartUp 中遍历 ServletContextInitializer 数组,将相关 Servlet 组件 Servlet(DispatcherServlet)、Filter、Listener 注入到 Tomcat 中。

protected void configureContext(Context context, ServletContextInitializer[] initializers) {
    // TomcatStarter 实现了 ServletContainerInitializer 接口
	TomcatStarter starter = new TomcatStarter(initializers);
	if (context instanceof TomcatEmbeddedContext) {
		// 代表当前 Web应用程序(Tomcat 容器组件:Server -> Service -> Engine -> Host -> Context -> Wrapper(Servlet))
		TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
		embeddedContext.setStarter(starter);
		embeddedContext.setFailCtxIfServletStartFails(true);
	}
	// 将 ServletContainerInitializer 添加到 Web 应用程序中
	context.addServletContainerInitializer(starter, NO_CLASSES);
	// 省略后面的代码 ...
}

然后在 TomcatWebServer#initialize 会启动 tomcat,触发初始化过程(此时就会触发 TomcatStarter#onStartUp 方法的执行),向 Tomcat 中注入Servlet 组件。

private void initialize() throws WebServerException {
		logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
		synchronized (this.monitor) {
			try {
				addInstanceIdToEngineName();

				Context context = findContext();
				context.addLifecycleListener((event) -> {
					if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
						// Remove service connectors so that protocol binding doesn't
						// happen when the service is started.
						removeServiceConnectors();
					}
				});

				// Start the server to trigger initialization listeners(启动 tomcat)
				this.tomcat.start();
				
				/*
					省略后面的代码 ...
				*/
			}
		}
}

SpringBoot 中的 TomcatStarter(ServletContainerInitializer)#onStartUp 调用栈:
在这里插入图片描述

Q1:这里已经通过 tomcat.start() 启动了 Tomcat(Spring容器刷新 onRefresh 阶段),TomcatWebServer#start (Spring容器刷新 finishRefresh 阶段)作用是什么?

A1:Tomcat.start() 触发 TomcatStarter#onStartUp 的执行完成的是:

  1. 将 Servlet、Listener、Filter 添加到 ServletContext 中。
// From RegistrationBean#onStartup
public final void onStartup(ServletContext servletContext) throws ServletException {
	String description = getDescription();
	if (!isEnabled()) {
		logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
		return;
	}
	register(description, servletContext);
}

// From ServletRegistrationBean#addRegistration
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
	String name = getServletName();
	// 将 Servlet 添加到 ServletContext 中
	return servletContext.addServlet(name, this.servlet);
}

// From ServletRegistrationBean#configure

// 完成 path 到 servlet 的映射
protected void configure(ServletRegistration.Dynamic registration) {
	super.configure(registration);
	String[] urlMapping = StringUtils.toStringArray(this.urlMappings);
	if (urlMapping.length == 0 && this.alwaysMapUrl) {
		urlMapping = DEFAULT_MAPPINGS;
	}
	if (!ObjectUtils.isEmpty(urlMapping)) {
		registration.addMapping(urlMapping);
	}
	registration.setLoadOnStartup(this.loadOnStartup);
	if (this.multipartConfig != null) {
		registration.setMultipartConfig(this.multipartConfig);
	}
}

DispatcherServlet 作为 ServletRegistrationBean 的子类,通过 SpringBoot 的自动化配置类 DispatcherServletRegistrationConfiguration 中被自动注入。

	// 作为一个配置类,不进行 CGLIB 提升
	@Configuration(proxyBeanMethods = false)
	// 满足条件则注入当前 DispatcherServletRegistrationBean
	@Conditional(DispatcherServletRegistrationCondition.class)
	// 存在 ServletRegistration 这个类才注入当前 Bean
	@ConditionalOnClass(ServletRegistration.class)
	// 注入一个配置对象
	@EnableConfigurationProperties(WebMvcProperties.class)
	// 先注入上面的 DispatcherServletConfiguration 对象
	@Import(DispatcherServletConfiguration.class)
	protected static class DispatcherServletRegistrationConfiguration {

		// 为 DispatcherServlet 定义一个 RegistrationBean 对象,目的是往 ServletContext 上下文中添加 DispatcherServlet
		@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
		// 需要存在名称为 `dispatcherServlet` 类型为 DispatcherServlet 的 Bean
		@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
		public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
				WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
			DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
					webMvcProperties.getServlet().getPath());
			registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
			registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
			// 如果有 MultipartConfigElement 配置则进行设置
			multipartConfig.ifAvailable(registration::setMultipartConfig);
			return registration;
		}
	}
}

所以, 通过 SpringBoot 注入的 Servlet 默认为 DispatcherServlet

  1. 将 ServletWebServerApplicationContext (Spring 容器)注入到 ServletContext (Servlet 容器)中;同时也将 ServletContext 注入到 ServletWebServerApplicationContext 中。
// From ServletWebServerApplicationContext.java

protected void prepareWebApplicationContext(ServletContext servletContext) {
	Object rootContext = servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
	if (rootContext != null) {
		if (rootContext == this) {
			throw new IllegalStateException(
					"Cannot initialize context because there is already a root application context present - "
							+ "check whether you have multiple ServletContextInitializers!");
		}
		return;
	}
	servletContext.log("Initializing Spring embedded WebApplicationContext");
	try {
		
		// 将 ServletWebServerApplicationContext 注入到 ServletContext
		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this);
		if (logger.isDebugEnabled()) {
			logger.debug("Published root WebApplicationContext as ServletContext attribute with name ["
					+ WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
		}
		// 将 ServletContext 注入到 ServletWebServerApplicationContext 中
		setServletContext(servletContext);
		if (logger.isInfoEnabled()) {
			long elapsedTime = System.currentTimeMillis() - getStartupDate();
			logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
		}
	}
	catch (RuntimeException | Error ex) {
		logger.error("Context initialization failed", ex);
		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
		throw ex;
	}
}

在 ServletWebServerApplicationContext#createWebServer 方法中,向 Spring 容器中注入了 WebServerStartStopLifecycle 实例,即在容器中的所有 Bean 完全创建成功后,才会调用 TomcatWebServer#start 真正完成 Tomcat 的启动(TomcatWebServer#performDeferredLoadOnStartup 完成容器组件 Wrapper 的加载:执行 org.apache.catalina.core.StandardWrapper#initServlet,即执行 Servlet 的 init 方法。)

private void createWebServer() {
	WebServer webServer = this.webServer;
	// 省略代码...
		getBeanFactory().registerSingleton("webServerStartStop",
				new WebServerStartStopLifecycle(this, this.webServer));
	}
	// 省略代码...
}

// 实现了 SmartLifecycle 接口,会在 Spring finishRefresh 阶段调用 start()
class WebServerStartStopLifecycle implements SmartLifecycle {

	private final ServletWebServerApplicationContext applicationContext;

	private final WebServer webServer;

	private volatile boolean running;

	WebServerStartStopLifecycle(ServletWebServerApplicationContext applicationContext, WebServer webServer) {
		this.applicationContext = applicationContext;
		this.webServer = webServer;
	}

	@Override
	public void start() {
		this.webServer.start();
		this.running = true;
		this.applicationContext
			.publishEvent(new ServletWebServerInitializedEvent(this.webServer, this.applicationContext));
	}
	// 省略代码...
}

注意:Wrapper 的 loadOnStartUp >= 0 时才会在 Tomcat 启动完成就立即执行 Wrapper 里面的 Servlet#init 方法,但 SpringBoot 向 Tomcat 注入 DispatcherServlet 时,loadOnStartUp = -1 (servlet.WebMvcProperties.Servlet#loadOnStartup 默认值为 -1),所以在初次访问时,才会执行 DispatcherServlet#init 方法。在 SpringBoot 完成启动后,就调用 DispatcherServlet#init 的设置方法:将spring.mvc.servlet.load-on-startup 设置成 >= 0 的正整数。

profile / environment

使用

  1. pom.xml
<project>
	<profiles>
        <profile>
            <id>dev</id>
            <properties>
                <!-- 用于替换 application.yml 中的变量 -->
                <spring.profiles.active>dev</spring.profiles.active>
            </properties>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <modules>
                <module>moudle-1</module>
                <module>moudle-2</module>
                <module>moudle-3</module>
            </modules>
        </profile>
        <profile>
            <id>prod</id>
            <modules>
                <module>moudle-1</module>
                <module>moudle-2</module>
                <module>moudle-3</module>
                <module>moudle-4</module>
            </modules>
            <properties>
                <!-- 用于替换 application.yml 中的变量 -->
                <spring.profiles.active>prod</spring.profiles.active>
            </properties>
        </profile>
</project>
  1. application.yml
spring:
  profiles:
    active: @spring.profiles.active@
  1. swagger
/*
	只有在 dev 环境下,才会向 Spring 容器中注入 SwaggerConfig 配置
	http://localhost:8080/swagger-ui.html
*/
@Profile({"dev"})
@EnableSwagger2
@Configuration
public class SwaggerConfig {
    @Bean
    public UiConfiguration uiConfig() {
        return UiConfigurationBuilder.builder()
                .deepLinking(true)
                .displayOperationId(false)
                // 隐藏UI上的Models模块
                .defaultModelsExpandDepth(-1)
                .defaultModelExpandDepth(0)
                .defaultModelRendering(ModelRendering.EXAMPLE)
                .displayRequestDuration(false)
                .docExpansion(DocExpansion.NONE)
                .filter(false)
                .maxDisplayedTags(null)
                .operationsSorter(OperationsSorter.ALPHA)
                .showExtensions(false)
                .tagsSorter(TagsSorter.ALPHA)
                .validatorUrl(null)
                .build();
    }

    @Bean
    public Docket createApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                // 比如:com.demo.controller
                .apis(RequestHandlerSelectors.basePackage("projectFullDir"))
                .paths(PathSelectors.any())
                .build();
    }


    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("项目接口文档")
                .version("1.0")
                .build();
    }
}

Spring Profiles

  1. Consider a basic scenario: We have a bean that should only be active during development but not deployed in production.We annotate that bean with a dev profile(such as @Profile(“dev”)), and it will only be present in the container during development. In production, the dev simply won’t be active. As a quick side note, profile names can also be prefixed with a NOT operator, e.g., !dev, to exclude them from a profile.

  2. Threre are variety of ways to activate and set the profiles:WebApplicationInitializer、ConfigurableEnvironment、web.xml、JVM System Parameter、Unix Environment Variable、Maven Profile

  3. mvn clean package -Pprod:This command will package the application for the prod profile. It also applies the spring.profiles.active value prod for this application when it is running.

其它

  1. Spring Boot 源码分析 - 内嵌Tomcat容器的实现
  2. Spring Boot 2.1.6.RELEASE embed tomcat启动过程

TomcatEmbeddedContext (StandardContext 子类) 就是tomcat的child容器,里面有servletContext

  1. tomcat:LifecycleListener
  2. Spring Boot 2.1.6.RELEASE embed tomcat启动过程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/355959.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

目标检测数据集制作(VOC2007格式数据集制作和处理教程)

VOC2007数据集结构&#xff08;目标检测图像分割&#xff09; #VOC2007数据集结构如下&#xff1a; VOC2007|-Annotations#里面存放的是每一张图片对应的标注结果&#xff0c;为XML文件&#xff0c;#标注完成后JPEGImages每张图片在此都有一一对应的xml文件|-ImageSets#存放的是…

uniapp v3组合式语法-项目创建

打开uni编译器HbuildX,右键新建项目,出现如下界面 输入完项目名称和地址后.选择红色箭头所指处的vue版本3 然后点击确认,即可创建uniapp,v3项目 创建完项目后,可以看到如下项目结构 项目结构uniapp官网上已经写的很详细了,这里就使用uniapp官网的介绍 一个uni-app工程&…

用BufferedReader快速输入有巨坑!!大佬们都是这么输入的

刷洛谷题单第二天&#xff0c;今天第一道题还是挺简单的&#xff0c;但是又被现实给了一级大逼兜子&#xff0c;做这题关键还是想熟悉Java的快速输入。还有做这道题很容易出现RE错误&#xff0c;下面总结了常见的RE错误导致情况&#xff08;点击跳转下文&#xff09; 我的上篇…

增存量市场大爆发!国产通信中间件「反攻」

梳理2023年智能驾驶的发展脉络可见&#xff0c;消费者对智能驾驶的认可度和接受度越来越高&#xff0c;带动高速NOA迈向了规模化普及新阶段&#xff0c;城市NOA初露锋芒。 从更长远的行业变革周期来看&#xff0c;智能驾驶的技术迭代还在继续&#xff0c;叠加电子电气架构的深…

软件测试人员常用的功能测试方法分享

功能测试就是对产品的各功能进行验证&#xff0c;根据功能测试用例&#xff0c;逐项测试&#xff0c;检查产品是否达到用户要求的功能。 常用的测试方法如下&#xff1a; 1. 页面链接检查 每一个链接是否都有对应的页面&#xff0c;并且页面之间切换正确。 2. 相关性检查 删除/…

基于springboot的房屋交易系统

文章目录 项目介绍主要功能截图&#xff1a;部分代码展示设计总结项目获取方式 &#x1f345; 作者主页&#xff1a;超级无敌暴龙战士塔塔开 &#x1f345; 简介&#xff1a;Java领域优质创作者&#x1f3c6;、 简历模板、学习资料、面试题库【关注我&#xff0c;都给你】 &…

工业自动化步进电机用电机驱动芯片TMC5160

TMC5160 数据手册 TMC5160 集成了完整的运动控制功能、高质量的电流调节功能和强大的外部 MOSFET 驱动&#xff0c;涵盖了多种从电池供电的高效系统到 20 安培电机线圈电流的嵌入式应用。TMC5160 包含驱动电机所需的全部智能。配置 TMC5160 目 标 位 置 就 可 以 驱 动 电 机 。…

C++初阶:C/C++内存管理、new与delete详解

之前结束了类与对象&#xff1a;今天进行下面部分内容的学习 文章目录 1.C/C内存分布2.C语言中动态内存管理方式&#xff1a;malloc/calloc/realloc/free3.C动态内存管理方式3.1new/delete操作内置类型3.2new和delete操作自定义类型 4.operator new与operator delete函数5.new和…

题目:冒险者公会(蓝桥OJ 3611)

问题描述&#xff1a; 解题思路&#xff1a; 官方&#xff1a; 注意点&#xff1a; 在前期的排序操作&#xff0c;因为需要找到如样例所示的每轮最大&#xff0c;因此我们需要用0代替没有的委托&#xff08;即样例斜杠&#xff09;。如何用0代替&#xff1a;将村庄委托数量默认…

基于springboot校园交友网站源码和论文

随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&#xff0c;各行各业相继进入信息管理时代&#xf…

ArcgisForJs快速入门

文章目录 0.引言1.前端代码编辑工具2.使用ArcgisForJs创建一个简单应用3.切片地图服务图层4.动态地图服务图层5.地图事件 0.引言 ArcGIS API for JavaScript是一款由Esri公司开发的用于创建WebGIS应用的JavaScript库。它允许开发者通过调用ArcGIS Server的REST API&#xff0c…

C++笔记之RTTI、RAII、MVC、MVVM、SOLID在C++中的表现

C++笔记之RTTI、RAII、MVC、MVVM、SOLID在C++中的表现 —— 杭州 2024-01-28 code review! 文章目录 C++笔记之RTTI、RAII、MVC、MVVM、SOLID在C++中的表现1.RTTI、RAII、MVC、MVVM、SOLID简述2.RAII (Resource Acquisition Is Initialization)3.RTTI (Run-Time Type Informat…

ABeam Insight | 大语言模型系列 (1) : 大语言模型概览

大语言模型系列 引入篇 ABeam Insight 自从图灵测试在20世纪50年代提出以来&#xff0c;人类一直不断探索机器如何掌握语言智能。语言本质上是一个由语法规则支配的错综复杂的人类表达系统。 近年来&#xff0c;具备与人对话互动、回答问题、协助创作等能力的ChatGPT等大语…

江科大stm32学习笔记6——GPIO输入准备

一、按键消抖 由于按键内部使用的是机械式弹簧片&#xff0c;所以在按下和松开时会产生5~10ms的抖动&#xff0c;需要通过代码来进行消抖。 二、滤波电容 在电路中&#xff0c;如果见到一端接在电路中&#xff0c;一端接地的电容&#xff0c;则可以考虑它的作用为滤波电容&am…

python爬虫实战——获取酷我音乐数据

嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 开发环境: 版 本&#xff1a; python 3.8 编辑器&#xff1a;pycharm 2022.3.2 模块使用: requests >>> pip install requests 如何安装python第三方模块: win R 输入 cmd 点击确定, 输入安装命令 pip install…

职业身份来认同自己对吗?

人们常常以自己的职业身份来认同自己。这是一个巨大的错误。 你的职业身份只是一个外壳&#xff1b;它不能定义你是一个人。你可以期待你的职业媒介会随着时间而改变&#xff0c;但是你传达的信息会应该更加稳定。你的信息就是回答你是谁&#xff0c;你应该通过三十几年的工作…

linux中常用的命令

一&#xff1a;tree命令 &#xff08;码字不易&#xff0c;关注一下吧&#xff0c;w~~w) 以树状形式查看指定目录内容。 tree --树状显示当前目录下的文件信息。 tree 目录 --树状显示指定目录下的文件信息。 注意&#xff1a; tree只能查看目录内容&#xff0c;不能…

【Axure高保真原型】随机抽取案例

今天和大家分享随机抽取点餐案例的原型模板&#xff0c;包括2种效果&#xff0c;第一种是手动暂停效果&#xff0c;点击开始后随机抽取食物&#xff0c;手动点击暂停按钮后停止&#xff1b;第二种是自动暂停效果&#xff0c;点击开始按钮后随机抽取食物&#xff0c;并且开始倒计…

webassembly003 whisper.cpp的python绑定实现+Cython+Setuptools的GUI程序

CODE python端的绑定和本文一样&#xff0c;还需要将cdef char* LANGUAGE b’en’改为中文zh&#xff08;也可以在函数中配置一个参数修改这个值&#xff09;。ps:本来想尝试cdef whisper_context* whisper_init_from_file_with_params_no_state(char*, whisper_full_params)…

Gitlab7.14 中文版安装教程

Gitlab7.14 中文版安装教程 注&#xff1a; 本教程由羞涩梦整理同步发布&#xff0c;本人技术分享站点&#xff1a;blog.hukanfa.com转发本文请备注原文链接&#xff0c;本文内容整理日期&#xff1a;2024-01-28csdn 博客名称&#xff1a;五维空间-影子&#xff0c;欢迎关注 …