SpringBoot 源码解析 - 持续更新

news2024/12/31 6:42:20

开始

  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启动过程

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

蓝牙----蓝牙GAP层

蓝牙协议栈----GAP GAP的角色连接过程连接参数 GAP&#xff1a;通用访问配置协议层 gap的角色发现的模式与过程连接模式与过程安全模式与过程 CC2640R2F的GAP层抽象 GAP的角色 Broadcaster 广播电台 -不可连接的广播者。Observer 观察者 -扫描广播者但无法启动连接。Periphe…

QA-GNN: 使用语言模型和知识图谱的推理问答

Abstract 使用预训练语言模型&#xff08;LMs&#xff09;和知识图谱&#xff08;KGs&#xff09;的知识回答问题的问题涉及两个挑战&#xff1a;在给定的问答上下文&#xff08;问题和答案选择&#xff09;中&#xff0c;方法需要&#xff08;i&#xff09;从大型知识图谱中识…

React一学就会(4): 强化练习二

书接上回&#xff0c;是不是感觉已经有点入门了。不过别急&#xff0c;码哥我准备了很多干货&#xff0c;等我们把这些基本几个章节的学完&#xff0c;码哥带着你一起装逼一起飞。我不大可能只是带着你们入门&#xff0c;那不是我的风格。码哥会教你如何开发一个完整中后台。前…

知识增强系列 ERNIE: Enhanced Representation through Knowledge Integration,论文解读

论文全称&#xff1a;通过知识集成增强语义表达 1. motivation ERNIE 目的在于通过知识屏蔽策略增强语言表示&#xff0c;其中屏蔽策略包括实体级屏蔽(Entity-level strategy)和短语级屏蔽(Phrase-level strategy)。 entity-level 策略通常会掩盖由多个单词组成的实体; Phrase…

智能加湿器数据分析:预计2025年市场规模将达到164.18亿元

随着经济的发展和人民生活水平的提高&#xff0c;人们对生活质量和健康的要求愈来愈高。空气加湿器慢慢的走进家庭当中&#xff0c;预计2023年中国线上超声波加湿器零售额同比下降4.9%;线上纯净型加湿器零售额同比增长44.8%。随着社会科技的不断进步和居民消费水平的不断提高&a…

【网络】WireShark过滤 | WireShark实现TCP三次握手和四次挥手

目录 一、开启WireShark的大门 1.1 WireShark简介 1.2 常用的Wireshark过滤方式 二、如何抓包搜索关键字 2.1 协议过滤 2.2 IP过滤 ​编辑 2.3 过滤端口 2.4 过滤MAC地址 2.5 过滤包长度 2.6 HTTP模式过滤 三、ARP协议分析 四、WireShark之ICMP协议 五、TCP三次握…

容器和虚拟机的对比

容器和虚拟机的对比 容器和虚拟机在与硬件和底层操作系统交互的方式上有所不同 虚拟化 使多个操作系统能够同时在一个硬件平台上运行。 使用虚拟机监控程序将硬件分为多个虚拟硬件系统&#xff0c;从而允许多个操作系统并行运行。 需要一个完整的操作系统环境来支持该应用。…

Rust循环和函数

下面聊聊以下主题&#xff1a; 基于条件的分支循环函数属性测试 基于条件的分支 基于条件的分支&#xff0c;可以通过常见的 if、if else 或 if else if else 构造来完成&#xff0c;例如下面的示例&#xff1a; fn main() { let dead false; let health 48; if dead { p…

JVM问题排查手册

三万字长文&#xff1a;JVM内存问题排查Cookbook 一、Heap快照 # jmap命令保存整个Java堆&#xff08;在你dump的时间不是事故发生点的时候尤其推荐&#xff09; jmap -dump:formatb,fileheap.bin <pid> # jmap命令只保存Java堆中的存活对象, 包含live选项&#xff0c;…

golang:beego的简单介绍和TiDB数据库的客户端实现

查阅官方文档和源码可以知道&#xff0c;beego库中有一个orm包负责数据库接口的封装。这个包支持若干个数据库引擎&#xff1a; 看到了一个文档&#xff0c;对ORM(Object-Relational Mapping)这个东西解释得比较清楚&#xff1a; 具体的客户端实现见下&#xff1a; package …

【JavaScript基础入门】04 JavaScript基础语法(二)

JavaScript基础语法&#xff08;二&#xff09; 目录 JavaScript基础语法&#xff08;二&#xff09;变量变量是什么声明变量变量类型动态类型注释 数字与运算符数字类型算术运算符操作运算符比较运算符逻辑运算符运算符的优先级 变量 变量是什么 在计算机中&#xff0c;数据…

Cesium 问题:遇到加载Cesium时各组件飞出

致敬爱的读者&#xff1a;该问题出现后暂时未找到最优的解决方案&#xff0c;而是将所有组件状态均进行隐藏&#xff0c;大家如果有解决方案可以留言、评论大家一起探讨解决&#xff0c;欢迎大家踊跃说出自己的想法 文章目录 问题分析 问题 在加载 Cesium 时出现各组件的位置不…

HarmonyOS模拟器启动失败,电脑蓝屏解决办法

1、在Tool->Device Manager管理界面中&#xff0c;通过Wipe User Data清理模拟器用户数据&#xff0c;然后重启模拟器&#xff1b;如果该方法无效&#xff0c;需要Delete删除已创建的Local Emulater。 2、在Tool->SDK Manager管理界面的PlatForm选项卡中&#xff0c;取消…

云畅科技入选国家超级计算长沙中心生态合作伙伴

为更好地服务国家战略和区域经济社会高质量发展&#xff0c;打造数据驱动经济发展的新态势&#xff0c;国家超级计算长沙中心面向全国开展了生态合作伙伴的征集工作。经企业申报、专家评审等环节&#xff0c;湖南云畅网络科技有限公司顺利通过审核&#xff0c;成功入选“国家超…

面试经典150题——找出字符串中第一个匹配项的下标

找出字符串中第一个匹配项的下标 思路分析&#xff1a; 思路一&#xff1a;直接调用String的API&#xff1a;indexOf 大道至简&#xff0c;String中的IndexOf是这样描述的&#xff1a; /*** Returns the index within this string of the first occurrence of the* specified…

牛客——小红又战小紫(概率dp和逆元)

链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 来源&#xff1a;牛客网 小红上次输给了小紫&#xff0c;表示不服&#xff0c;于是又约来小紫来玩一个游戏。 这次是取石子游戏&#xff1a;共有nnn堆石子&#xff0c;两人轮流使用以下两种技能中的一种进行取石子&#x…

如何在Win系统安装Jupyter Notbook并实现无公网ip远程访问本地笔记

文章目录 1.前言2.Jupyter Notebook的安装2.1 Jupyter Notebook下载安装2.2 Jupyter Notebook的配置2.3 Cpolar下载安装 3.Cpolar端口设置3.1 Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 1.前言 在数据分析工作中&#xff0c;使用最多的无疑就是各种函数、图表、…

《WebKit 技术内幕》学习之十四(1):调式机制

第14章 调试机制 支持调试HTML、CSS和JavaScript代码是浏览器或者渲染引擎需要提供的一项非常重要的功能&#xff0c;这里包括两种调试类型&#xff1a;其一是功能&#xff0c;其二是性能。功能调试能够帮助HTML开发者使用单步调试等技术来查找代码中的问题&#xff0c;性能调…

【漏洞复现】零视技术H5S视频平台信息泄漏漏洞

Nx01 产品简介 零视技术(上海)有限公司是以领先的视频技术服务于客户&#xff0c;致力于物联网视频开发简单化&#xff0c;依托于HTML5 WebRTC 等新的技术&#xff0c;实现全平台视频播放简单化。 Nx02 漏洞描述 零视技术(上海)有限公司H5S CONSOLE存在未授权访问漏洞&#xf…

ElasticSearch重建/创建/删除索引操作 - 第501篇

历史文章&#xff08;文章累计500&#xff09; 《国内最全的Spring Boot系列之一》 《国内最全的Spring Boot系列之二》 《国内最全的Spring Boot系列之三》 《国内最全的Spring Boot系列之四》 《国内最全的Spring Boot系列之五》 《国内最全的Spring Boot系列之六》 E…