SpringBoot 源码解析 - 持续更新

开始

  1. spring initilizer:根据依赖构建工具、springboot 版本等生成 Java 工程。
  2. Spring Boot starter的介绍以及原理手把手教你手写一个最简单的 Spring Boot StarterSpringBoot starter 演进过程实战编写 SpringBoot 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 容器中。

在使用 spring boot 搭建项目时,只需要引入相应的 starter 坐标,免去了引入相关的依赖以及设置初始化的配置等繁琐操作。

源码分析

入口程序

@SpringBootApplication
public class SpringDemoApplication {

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

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

在这里插入图片描述

自动装配如何实现

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
// 启动 Servlet 容器
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 容器;

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 组件注入

  1. 可通过使用 Servelet 3.0+ 的注解 @WebServlet(urlPatterns = {“/path”})、@WebFilter(urlPatterns = {“/path”}) 以及 @WebListener,以及结合 SpringBoot 注解 @ServletComponentScan(basePackages = {“com.demo.test.servlet”}) 分别向 Web 容器中注入 Java Web 原生组件 Servlet、Servlet 监听器以及 Filter。
@Slf4j
@WebFilter("/test")
public class MyFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {
        log.info("do filter: " + servletRequest.getServletContext().getContextPath());
    }
}

@SpringBootApplication
@ServletComponentScan
public class SpringDemoApplication {

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

}
  1. SpringBoot 可通过 ServletRegistrationBean、ServletListenerRegistrationBean 以及 FilterRegistrationBean 分别将 Java Web 的原生组件 Servlet、Servlet 监听器以及 Filter 注入到 Servlet 容器中。
@Slf4j
public class MyFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {
        log.info("do filter");
    }
}

@Configuration
public class ServletConfiguration {
    @Bean
    public FilterRegistrationBean filterRegistration() {
        FilterRegistrationBean<MyFilter> registrationBean = new FilterRegistrationBean<>(
                new MyFilter());
        registrationBean.setUrlPatterns(Arrays.asList("/test"));
        return registrationBean;
    }
}

注意: 由于 NativeServet 是与 DispatcherServlet 并立的 Servlet,所以访问上面两种方式注入的 Servlet (计为 NativeServet)对应的 path 时,拦截器、参数解析器等 SpringMVC 的特性不会生效。

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 的正整数。

DispatcherServlet

通过 SpringBoot 的 SPI 机制,通过读取 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 中的 org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,将 DispatcherServlet 注入到 Spring 容器和 Servlet 容器。

  1. 将 DispatcherServlet 注入 Spring 容器
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
	DispatcherServlet dispatcherServlet = new DispatcherServlet();
	dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
	dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
	dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
	dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
	dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
	return dispatcherServlet;
}

通过 @Bean 方法的参数可以看出,在 spring.properties 或 spring.yml 配置文件中可通过修改 spring.mvc. 开头的配置影响 spring-mvc. 的行为。
在这里插入图片描述
2. 将 DispatcherServlet 注入到 Servlet 容器

@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@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());
	multipartConfig.ifAvailable(registration::setMultipartConfig);
	return registration;
}

profile / environment

在这里插入图片描述
在这里插入图片描述

案列

通过 @Profile 实现条件装配,限制只能在开发环境才能使用 Swagger

  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. 视频讲解 - SpringBoot 启动 Tomcat
    在这里插入图片描述
    在这里插入图片描述
  2. 视频讲解 - SpringBoot 定制化套路:WebMvcConfigurer
    在这里插入图片描述
    在这里插入图片描述
  3. Spring Boot 源码分析 - 内嵌Tomcat容器的实现
  4. 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启动过程
  • 20
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Springboot源码解析PDF是一本深入解析Springboot框架的技术书籍,涵盖了Springboot的背景、原理、设计思路、运行机制、开发实践等方面。全书主要分为三部分:第一部分介绍Springboot的基础知识,包括Spring框架的常用注解、Springboot的配置、自动配置原理等;第二部分深入探讨Springboot的主要功能,如数据访问、Web开发、缓存、消息、安全等;第三部分着重介绍开发Springboot应用的最佳实践,包括Springboot与其他框架的结合使用、集成测试、监控与诊断等。 阅读Springboot源码解析PDF可以让开发者更深入理解Springboot的设计理念、技术实现以及应用场景,在实际项目开发中更加灵活、高效地使用Springboot。该书对于有一定JavaSpring框架基础的开发者来说是一本非常优秀的参考资料,也是Java开发者必不可少的技术读物。同时,该书也是借助源码解析的方式,让读者更加系统化地学习Springboot技术,具有很高的实用性和参考价值。总之,阅读Springboot源码解析PDF有助于开发者更好地掌握Springboot技术,提高应用开发效率和代码质量。 ### 回答2: Spring Boot源码解析pdf是一本介绍Spring Boot框架的开源书籍,该书的目的是帮助开发者深入了解Spring Boot的内部工作原理和实现细节。 该书首先从Spring Boot框架的源码结构和核心模块入手,详细介绍了Spring Boot的MVC、ORM、缓存、安全等核心功能的实现原理。同时,该书还介绍了Spring Boot对微服务的支持和整合Spring Cloud的方式,让开发者更深入了解Spring Boot在分布式架构中的应用。 在讲解源码实现原理的同时,该书还指出了一些常见的开发问题和易错点,并提供了相应的解决方案。此外,该书还通过一系列的案例,全面展示了Spring Boot的实际开发应用场景,帮助开发者更好地应用Spring Boot框架。 总的来说,Spring Boot源码解析pdf是一本非常实用的书籍,能够帮助开发者快速掌握Spring Boot框架的内部实现原理,提高开发效率和代码质量。同时,该书还可以作为学习Spring Boot的参考资料,对于想要深入学习和研究Spring Boot的开发者来说是非常有用的。 ### 回答3: Spring Boot 是一个很受欢迎的 Java 框架,它简化了开发者的工作,允许他们更快速地构建和部署应用程序。Spring Boot 的优点包括简洁的配置、内嵌的 Web 容器和现成的插件,让开发者可以更专注于业务逻辑。Spring Boot源码解析是学习它的关键。 Spring Boot源码解析Java初学者来说可能会比较复杂,但是它对于学习框架和原理是非常有益的。一个好的 Spring Boot 项目需要基于很好的基础,这就要求开发者了解其源码源码解析可以帮助开发者了解各种设计模式和组件的原理,有助于解决实际问题。 在 Spring Boot源码解析中,我们将会找到很多有用的信息。例如,我们可以看到 Spring Boot 如何通过注解处理器创建并配置 bean,这可以帮助我们更好地理解依赖注入和 IoC 容器。此外,我们还可以了解 Spring Boot 如何处理 HTTP 请求、创建模板、处理安全性等。这些知识可以帮助我们更好地理解 Spring Boot 内部的工作机制。 总之,Spring Boot源码解析是必不可少的一部分,它可以帮助开发者更好地了解和使用该框架,掌握在实践中所需的知识和技能。如果您是一名 Spring 开发者,那么深入了解 Spring Boot源码将会是一个很好的学习过程。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值