我们在分析一个Springboot的SpringMvc时,首先我们先从简单的项目聊起
一个简单的例子
@SpringBootApplication
public class DemoProviderApplication {
public static void main(String[] args) {
SpringApplication.run(DemoProviderApplication.class, args);
}
}
@RestController
@RequestMapping("/test")
public class TestController {
@PostMapping("/demo")
public User demo(@RequestBody @Valid UserParams userParams){
User user = new User();
user.setAge(10);
user.setName("demo");
user.setId(1);
return user;
}
}
SpringApplication.run方法
一定大多数人有个疑问,Springboot是如何将Spring,SpringMvc,Tomcat联系起来的,接下来我们以项目启动的角度了解下(以下我们做核心代码分析)
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
ConfigurableApplicationContext context = null;
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext(); // 1>
context.setApplicationStartup(this.applicationStartup);
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
this.refreshContext(context); // 2>
this.afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), timeTakenToStartup);
}
listeners.started(context, timeTakenToStartup);
this.callRunners(context, applicationArguments);
} catch (Throwable var12) {
this.handleRunFailure(context, var12, listeners);
throw new IllegalStateException(var12);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
return context;
} catch (Throwable var11) {
this.handleRunFailure(context, var11, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var11);
}
}
我们仔细分析下这个run方法:
- 第一步创建了createApplicationContext,这个会根据依赖的类动态的创建实现类,如果是web,那么创建的是AnnotationConfigServletWebServerApplicationContext,因为一般我们依赖的是spring-boot-starter-web
- 第二部执行refreshContext,最终调用的是AbstractApplicationContext的refresh方法
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
this.prepareRefresh();
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
try {
this.postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh(); // 3>
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var10) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var10);
}
this.destroyBeans();
this.cancelRefresh(var10);
throw var10;
} finally {
this.resetCommonCaches();
contextRefresh.end();
}
}
}
这里调用的就是spring当中的方法了,我们只关注springmvc和Tomcat两部分
3. 我们直接看onRefresh方法,因为AnnotationConfigServletWebServerApplicationContext是ServletWebServerApplicationContext的子类,所以流程进入ServletWebServerApplicationContext的onRefresh()方法
protected void onRefresh() {
super.onRefresh();
try {
this.createWebServer(); // 4>
} catch (Throwable var2) {
throw new ApplicationContextException("Unable to start web server", var2);
}
}
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = this.getServletContext();
if (webServer == null && servletContext == null) {
StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
ServletWebServerFactory factory = this.getWebServerFactory(); // 5>
createWebServer.tag("factory", factory.getClass().toString());
this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()}); // 6>
createWebServer.end();
this.getBeanFactory().registerSingleton("webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle(this.webServer));
this.getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this, this.webServer));
} else if (servletContext != null) {
try {
this.getSelfInitializer().onStartup(servletContext);
} catch (ServletException var5) {
throw new ApplicationContextException("Cannot initialize servlet context", var5);
}
}
this.initPropertySources();
}
- 其中this.createWebServer();是关键方法
- getWebServerFactory()方法获取一个WebServer工厂类,因为我们默认用的tomcat,所以会获取到TomcatServletWebServerFactory
- factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()}); 我们再具体看下这个方法getSelfInitializer
private ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
private void selfInitialize(ServletContext servletContext) throws ServletException {
this.prepareWebApplicationContext(servletContext);
this.registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(this.getBeanFactory(), servletContext);
Iterator var2 = this.getServletContextInitializerBeans().iterator();
while(var2.hasNext()) {
ServletContextInitializer beans = (ServletContextInitializer)var2.next();
beans.onStartup(servletContext);
}
}
这里返回的是一个ServletContextInitializer ,通过源码我们可以发现ServletContextInitializer 是@FunctionalInterface ,java8提供的lambda的一种函数式接口
@FunctionalInterface
public interface ServletContextInitializer {
void onStartup(ServletContext servletContext) throws ServletException;
}
继续回到第6部那factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()})的getWebServer方法
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Iterator var4 = this.serverLifecycleListeners.iterator();
while(var4.hasNext()) {
LifecycleListener listener = (LifecycleListener)var4.next();
tomcat.getServer().addLifecycleListener(listener);
}
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
this.customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
this.configureEngine(tomcat.getEngine());
Iterator var8 = this.additionalTomcatConnectors.iterator();
while(var8.hasNext()) {
Connector additionalConnector = (Connector)var8.next();
tomcat.getService().addConnector(additionalConnector);
}
this.prepareContext(tomcat.getHost(), initializers); // 7>
return this.getTomcatWebServer(tomcat);
}
可以看到tomcat的实现,向tomcat的容器当中注入Connector ,然后设置默认Host容器的AutoDeploy属性及其他的Tomcat初始化工作,最重要的一行是7)
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
File documentRoot = this.getValidDocumentRoot();
TomcatEmbeddedContext context = new TomcatEmbeddedContext();
if (documentRoot != null) {
context.setResources(new LoaderHidingResourceRoot(context));
}
//省略部分代码
ServletContextInitializer[] initializersToUse = this.mergeInitializers(initializers); //8>
//将context作为host的子容器
host.addChild(context);
this.configureContext(context, initializersToUse); // 9>
this.postProcessContext(context);
}
第8步合并所有的ServletContextInitializer,然后调用configureContext方法
protected void configureContext(Context context, ServletContextInitializer[] initializers) {
TomcatStarter starter = new TomcatStarter(initializers); //10>
if (context instanceof TomcatEmbeddedContext) {
TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext)context;
embeddedContext.setStarter(starter);
embeddedContext.setFailCtxIfServletStartFails(true);
}
context.addServletContainerInitializer(starter, NO_CLASSES); //11>
//省略代码
}
10)创建了TomcatStarter对象,并将starter加入context的conainerInitializer列表,见11),这样在tomcat的容器启动过程中就会调用到这个TomcatStarter实例。
我们来看下TomcatStarter做了什么
class TomcatStarter implements ServletContainerInitializer {
private static final Log logger = LogFactory.getLog(TomcatStarter.class);
private final ServletContextInitializer[] initializers;
private volatile Exception startUpException;
TomcatStarter(ServletContextInitializer[] initializers) {
this.initializers = initializers;
}
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
try {
ServletContextInitializer[] var3 = this.initializers;
int var4 = var3.length;
for(int var5 = 0; var5 < var4; ++var5) {
ServletContextInitializer initializer = var3[var5];
initializer.onStartup(servletContext);
}
} catch (Exception var7) {
this.startUpException = var7;
if (logger.isErrorEnabled()) {
logger.error("Error starting Tomcat context. Exception: " + var7.getClass().getName() + ". Message: " + var7.getMessage());
}
}
}
Exception getStartUpException() {
return this.startUpException;
}
}
可以看到TomcatStarter相当于hook了context启动的事件,然后调用所有注入的initializers的onStartup方法,似曾相识是吗?这就是前面说的@FunctionalInterface函数接口,接下来我们就深入看下前面提到的那个initializer的onStartup的具体内容
private void selfInitialize(ServletContext servletContext) throws ServletException {
this.prepareWebApplicationContext(servletContext);
this.registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(this.getBeanFactory(), servletContext);
Iterator var2 = this.getServletContextInitializerBeans().iterator();
while(var2.hasNext()) {
ServletContextInitializer beans = (ServletContextInitializer)var2.next();
beans.onStartup(servletContext);
}
}
可以看到对getServletContextInitializerBeans每个ServletContextInitializer执行了onStartup方法
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
return new ServletContextInitializerBeans(this.getBeanFactory(), new Class[0]);
}
看看new ServletContextInitializerBeans(getBeanFactory())做了什么
@SafeVarargs
public ServletContextInitializerBeans(ListableBeanFactory beanFactory, Class<? extends ServletContextInitializer>... initializerTypes) {
this.initializerTypes = initializerTypes.length != 0 ? Arrays.asList(initializerTypes) : Collections.singletonList(ServletContextInitializer.class);
this.addServletContextInitializerBeans(beanFactory);
this.addAdaptableBeans(beanFactory);
List<ServletContextInitializer> sortedInitializers = (List)this.initializers.values().stream().flatMap((value) -> {
return value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE);
}).collect(Collectors.toList());
this.sortedList = Collections.unmodifiableList(sortedInitializers);
this.logMappings(this.initializers);
}
可以看到其从beanFactory中获取spring容器中所有的ServletContextInitializer实现,这里关于集成的部分在ServletRegistrationBean中,ServletRegistrationBean的注入过程参考
然后我们就可以理顺了, 我们会调用到ServletRegistrationBean的onStartup方法,最终会调用到servletContext.addServlet的Servlet3.0的标准将DispatchServlet注入到servlet容器中拦截所有的请求。
//RegistrationBean
@Override
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);
}
//DynamicRegistrationBean
@Override
protected final void register(String description, ServletContext servletContext) {
D registration = addRegistration(description, servletContext);
if (registration == null) {
logger.info(
StringUtils.capitalize(description) + " was not registered " + "(possibly already registered?)");
return;
}
configure(registration);
}
//ServletRegistrationBean
@Override
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
String name = getServletName();
return servletContext.addServlet(name, this.servlet);
}
至此所有集成完毕,启动过程交给tomcat完成。
TomcatServletWebServerFactory
spring-boot-autoconfigure/META-INF/spring.factories中有一段配置:
...
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
...
然后我们来看下ServletWebServerFactoryAutoConfiguration类
@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
...
}
其中@Import部分引入了ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,深入看一下
@Configuration
class ServletWebServerFactoryConfiguration {
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
}
...
}
这块Spring Boot根据@ConditionalOnClass判断当前运行时环境是否符合条件,即包含了tomcat的jar包,如果满足则创建TomcatServletWebServerFactory的Bean实例加入spring容器管理,后面有用
ServletWebServerApplicationContext
实际启动时,启动的是其子类AnnotationConfigServletWebServerApplicationContext,我们来看下SpringApplication类,实际上SpringApplication在运行时根据情况决定使用哪种ApplicationContext
在run方法时,创建applicationContext时
会根据传入的webApplicationType来创建applicationContext
DispatcherServletRegistrationBean
DispatcherServletRegistrationBean是保证我们的DispatcherServlet被注入到Servlet容器并生效的关键,我们来看下它是如何初始化的
spring-boot-autoconfigure/META-INF/spring.factories中有一段配置
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
@Configuration
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {
private final WebMvcProperties webMvcProperties;
private final MultipartConfigElement multipartConfig;
public DispatcherServletRegistrationConfiguration(WebMvcProperties webMvcProperties,
ObjectProvider<MultipartConfigElement> multipartConfigProvider) {
this.webMvcProperties = webMvcProperties;
this.multipartConfig = multipartConfigProvider.getIfAvailable();
}
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet) {
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
this.webMvcProperties.getServlet().getPath());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(this.webMvcProperties.getServlet().getLoadOnStartup());
if (this.multipartConfig != null) {
registration.setMultipartConfig(this.multipartConfig);
}
return registration;
}
}
}
可以看到,其像spring容器注册了DispatcherServletRegistrationBean的Bean实例,看一下它的继承关系:
其父类ServletRegistrationBean类有如下方法:
@Override
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
String name = getServletName();
return servletContext.addServlet(name, this.servlet);
}
其调用了ServletContext.addServlet方法将DispatchServlet加入到Servlet容器,这是Servlet3.0中注册servlet的方法。
那么你也许会问,addRegistration又是什么时机调用的呢?
根据继承关系,查看其父类的父类RegistrationBean,其有一个
@Override
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);
}
register方法是一个模板方法,调用子类DynamicRegistrationBean的实现
@Override
protected final void register(String description, ServletContext servletContext) {
D registration = addRegistration(description, servletContext);
if (registration == null) {
logger.info(StringUtils.capitalize(description) + " was not registered " + "(possibly already registered?)");
return;
}
configure(registration);
}
addRegistration方法又是一个模板方法,实现就是前面ServletRegistrationBean的addRegistration实现,而onStartup方法会在SpringApplication.run()方法的流程中被调用到,讲主流程的时候已经讲到,这里不再赘述
这样就将DispatchServlet与Tomcat进行了集成,DispatchServlet使用模板方法设计模式,将具体的请求分配给不同的handler处理,这个后面会讲到,本篇就主要专注在Spring Boot与Spring MVC及Tomcat的集成原理部分