这里是目录
- 1. 初始化配置
- 2 请求处理
1. 初始化配置
SpringMVC的请求处理依赖DispatcherServlet,而bean的管理依赖自身的Spring容器,配置管理现在依赖于SpringBoot.
- 对于在servlet 3.0之前,Spring要应用于Web项目,启动的过程要依赖容器,在容器启动完成后触发Spring的初始化,而Web容器启动状态的监听是通过Listener来完成的,具体的接口就是ServletContextListener,其属于Servlet的规范,Spring的ContextLoaderListener类实现了ServletContextListener接口,从而我们可以想到,如果要在Web容器启动后自动初始化Spring则需要把ServletContextListener配置到web.xml中去。
- SpringBoot的诞生改变了Spring的初始化方式,SpringBoot首先启动Spring容器,然后再根据配置启动嵌入的容器。(其实早在SpringBoot诞生之前就可以这样干了, 我们可以利用ClassPathXmlApplicationContext或者XmlBeanDefinitionReader 来读取Spring bean的配置文件,从而进行Spring的初始化)。
- 那么SpringMVC的DispatcherServlet是怎么初始化的呢。首先它是一个Servlet,在Servlet 3.0之前,直接在web.xml中配置servlet,因为servlet规范中Listener是先于servlet初始化的,所以在初始化servlet的时候Spring容器已经启动,从而当请求到达DispatcherServlet后可以利用Spring容器中的bean。
- 在servlet3.0即之后,由于可以编程式的动态实例化servlet,其通过ServletRegistration.Dynamic类注册新的servlet,所以在SpringBoot中DispacherServlet的注入就可以直接交给autoConfigure模块的DispatcherServletAutoConfiguration类来完成了。其实servlet新规范中还有一套注解用来取代以前的web.xml配置,在SpringBoot的场景下也可以使用这套注解,Spring完全可以给DispatcherServlet加上@WebServlet(配合@ServletComponentScan)注解,在Spring初始化web容器之后,容器会自动装配上servlet,但是看了下SpringBoot的代码并没有这样做。
SpringMVC的配置包含两部分,一部分是MVC相关配置,一部分是DispatcherServlet与容器相关的配置。
1.1 servlet容器相关配置DispatcherServletAutoConfiguration
在SpringBoot中,是通过DispatcherServletAutoConfiguration来对DispatcherServlet进行配置的。
首先我们看一下类的配置:
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) //以最高优先级启动
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET) //应用是基于servlet的才启动
@ConditionalOnClass(DispatcherServlet.class)//DispatcherServlet必须要存在
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class) //在ServletWebServerFactoryAutoConfiguration初始化之后
public class DispatcherServletAutoConfiguration {
}
这里首先我们要来关心一下ServletWebServerFactoryAutoConfiguration,因为DispatcherServlet的配置在它之后。
1.1.1 ServletWebServerFactoryAutoConfiguration
@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) //最高优先级启动
@ConditionalOnClass(ServletRequest.class) //ServletRequest类存在
@ConditionalOnWebApplication(type = Type.SERVLET) //servlet服务
@EnableConfigurationProperties(ServerProperties.class) //引入ServerProperties配置
@Import({
//导入以下类 (@Import将对于的类实例化后注入IOC容器)
ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class
})
public class ServletWebServerFactoryAutoConfiguration {
}
很明显可以看出,该类是为servlet容器相关配置来服务的,它要求必须存在ServletRequest类,应用类型是SERVLET。这里我们注意到首先引入了BeanPostProcessorsRegistrar,应该是要左一些bean初始化的后置处理,然后引入了具体的servlet容器的实现配置。
具体看下这个自动配置类做了些什么:
//实例化ServletWebServerFactoryCustomizer
@Bean
public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {
return new ServletWebServerFactoryCustomizer(serverProperties);
}
//在有Tomcat类存在(即引入了tomcat的jar)的情况下实例化TomcatServletWebServerFactoryCustomizer
@Bean
@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
ServerProperties serverProperties) {
return new TomcatServletWebServerFactoryCustomizer(serverProperties);
}
/**
* 通过实现ImportBeanDefinitionRegistrar接口来实现早期的WebServerFactoryCustomizerBeanPostProcessor注入
* 其实就是注入一个bean后置处理器
* */
public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
private ConfigurableListableBeanFactory beanFactory;
//设置beanFactory
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof ConfigurableListableBeanFactory) {
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
}
//注册web服务自定义配置后置处理器和错误页面后置处理器
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
if (this.beanFactory == null) {
return;
}
registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
WebServerFactoryCustomizerBeanPostProcessor.class);
registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor",
ErrorPageRegistrarBeanPostProcessor.class);
}
//注入类具体实现
private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name, Class<?> beanClass) {
//在BeanFactory中找不到对应的bean时,则将对应Class的bean注入
if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
beanDefinition.setSynthetic(true);
registry.registerBeanDefinition(name, beanDefinition);
}
}
}
1.1.2 ServletWebServerFactoryConfiguration
再来看引入的ServletWebServerFactoryConfiguration具体做了哪些配置:
/**servlet web服务器的配置类
这些类应该被Import到一个常规的自动配置类中以此来保证他们的执行顺序。
这里就是被引入到了ServletWebServerFactoryAutoConfiguration 类里面。
**/
@Configuration
class ServletWebServerFactoryConfiguration {
//嵌入tomcat
@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();
}
}
/**
* jetty的配置.
*/
@Configuration
@ConditionalOnClass({
Servlet.class, Server.class, Loader.class, WebAppContext.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedJetty {
@Bean
public JettyServletWebServerFactory JettyServletWebServerFactory() {
return new JettyServletWebServerFactory();
}
}
/**
* Undertow配置.
*/
@Configuration
@ConditionalOnClass({
Servlet.class, Undertow.class, SslClientAuthMode.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedUndertow {
@Bean
public UndertowServletWebServerFactory undertowServletWebServerFactory() {
return new UndertowServletWebServerFactory();
}
}
}
从以上代码可以看出,ServletWebServerFactoryConfiguration其实就是根据classpath引入的jar包判断并实例化对应的servlet容器,分别是Tomcat, Jetty,Undertow。主要是分别初始化他们对应的xxServlectWebServerFactory。
1.1.3 ServletWebServerFactoryAutoConfiguration 总结
该类做了以下的事情:
1.初始化TomcatServletWebServerFactoryCustomizer
2.xxServletWebServerFactoryCustomizer通过ServerProperties 进行服务器客户化配置,配置文件前缀是server,例如server.port=8080
3.注入webServerFactoryCustomizerBeanPostProcessor和errorPageRegistrarBeanPostProcessor 后置处理器
4.初始化TomcatServletWebServerFactory(或者其它容器)
1.2 真正的DispatcherServletAutoConfiguration配置
看完了servlet容器相关的配置,然后我们再来看一下DispatcherServletAutoConfiguration做的事情:
1.2.1 实例化DispatcherServlet并传入配置
@Configuration
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties({
HttpProperties.class, WebMvcProperties.class })
protected static class DispatcherServletConfiguration {
private final HttpProperties httpProperties;
private final WebMvcProperties webMvcProperties;
//注入配置
public DispatcherServletConfiguration(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {
this.httpProperties = httpProperties;
this.webMvcProperties = webMvcProperties;
}
//实例化DispatcherServlet
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(this.webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(this.webMvcProperties.isDispatchTraceRequest());
dispatcherServlet
.setThrowExceptionIfNoHandlerFound(this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
dispatcherServlet.setEnableLoggingRequestDetails(this.httpProperties.isLogRequestDetails());
return dispatcherServlet;
}
@Bean
@ConditionalOnBean(MultipartResolver.class)
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
public MultipartResolver multipartResolver(MultipartResolver resolver) {
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}
}
1.2.2 第二步 :将DispatcherServlet实例注入Servlet容器
@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;
//传入MVC配置和文件上传配置
public DispatcherServletRegistrationConfiguration(WebMvcProperties webMvcProperties,
ObjectProvider<MultipartConfigElement> multipartConfigProvider) {
this.webMvcProperties = webMvcProperties;
this.multipartConfig = multipartConfigProvider.getIfAvailable();
}
//将servlet动态配置到ServletContext中, 这是servlet 3.0+的规范
@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;
}
}
这里我们可以详细看一下DispatcherServletRegistrationBean是如何实现Servlet的注入的,首先看一下它的依赖图:
我们先看一下顶层接口的定义:
/**
* 用于编程式配置Servlet 3.0+的ServletContext,它不像WebApplicationInitializer,
* 实现了本接口(但是没有实现WebApplicationInitializer)将不会被SpringServletContainerInitializer(它实现了servlet 3.0规范的ServletContainerInitializer接口)主动发现,
* 因此后续也不会被Servlet容器自动的启动
* <p>
* 这个接口的设计初衷就是要让Spring来管理它的实现而部署Servlet容器
**/
@FunctionalInterface
public interface ServletContextInitializer {
/**
给ServletContext配置任何的servlet、filter、listener、context-params 以及 attribute等必要的初始化条件。
**/
void onStartup(ServletContext servletContext) throws ServletException;
}
总结一下就是ServletContextInitializer是Spring提供的用来实现动态扩展ServletContext的接口,它的实现由Spring管理,并非Servlet容器。
它的实现RegistrationBean抽象类如下:
public abstract class RegistrationBean implements ServletContextInitializer, Ordered {
private int order = Ordered.LOWEST_PRECEDENCE;
private boolean enabled = true;
@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);
}
//返回一个本次注册的描述
protected abstract String getDescription();
//注册
protected abstract void register(String description, ServletContext servletContext);
//--------setter and getter-----------------
}
这个类RegistrationBean其实也很简单,在这一层它增加了两个东西,一个是注册的优先级,默认最低,一个是注册是否开起的标志,实际上的注册动作通过模板模式让子类来提供具体的实现。
其子类 DynamicRegistrationBean的实现如下:
/**
基于Servlet 3.0+ javax.servlet.Registration.Dynamic 类实现动态注册的基础类。
这里 Registration就是servlet的规范了,而它的实现由具体的servlet容器来提供,例如tomcat
*/
public abstract class DynamicRegistrationBean<D extends Registration.Dynamic> extends RegistrationBean {
//servlet name,如果不指定,后面会通过getOrDeduceName进行推导,其实就是disparcherServlet
private String name;
//servlet是否开启异步 3.0+ 规范
//这个很重要,说明DispatcherServlet默认是开启异步请求功能的
private boolean asyncSupported = true;
//servlet初始化参数
private Map<String, String> initParameters = new LinkedHashMap<>();
/**以上参数的set get方法,此处省略………… **/
//对父类register的实现
@Override
protected final void register(String description, ServletContext servletContext) {
//注册,委托servletContext完成注册
D registration = addRegistration(description, servletContext);
if (registration == null) {
logger.info(
StringUtils.capitalize(description) + " was not registered " + "(possibly already registered?)");
return;
}
configure(registration);
}
//同样,具体的注册方法还是交由子类来实现,并且会返回用于注册的Registration.Dynamic
protected abstract D addRegistration(String description, ServletContext servletContext);
//配置异步和初始化参数
protected void configure(D registration) {
registration.setAsyncSupported(this.asyncSupported);
if (!this.initParameters.isEmpty()) {
registration.setInitParameters(this.initParameters);
}
}
//…………省略不重要代码…………
}
在这里统一管理了servlet是否异步模式,以及提供了初始化参数的配置。
再看看addRegistration是如何实现的 。到目前为止,以上的抽象类是供所有的Filter/Listener/Servlet等注册使用的,再往下就是具体的某种类型注册的实现了,这里即ServletRegistrationBean。
/**
* 一个ServletContextInitializer的实例,用于注册Servlet到Servlet 3.0+容器。
* 类似于ServletContext提供的ServletContext.addServlet(String, Servlet)特性,但是对Spring Bean更友好。
* <p>
* setServlet(Servlet servlet)方法在onStartup方法被调用前必须先指定它。
* URL mapping可以用setUrlMappings来配置,或者当配置/*时直接省略(除非alwaysMapUrl被设置为false), servlet 名称如果没有指定则会被推断得出。
*/
public class ServletRegistrationBean<T extends Servlet> extends DynamicRegistrationBean<ServletRegistration.Dynamic> {
//当没有指定urlMappings的时候默认使用改urlMapping
private static final String[] DEFAULT_MAPPINGS = {
"/*" };
//本次要注入的servlet
private T servlet;
//可以配置urlMapping
private Set<String> urlMappings = new LinkedHashSet<>();
//是否必须指定urlMapping(也不知道干嘛的,后面再看)
private boolean alwaysMapUrl = true;
//是否启动即加载
private int loadOnStartup = -1;
//文件上传配置
private MultipartConfigElement multipartConfig;
//构造方法
public ServletRegistrationBean() {
}
//构造方法
public ServletRegistrationBean(T servlet, String... urlMappings) {
this(servlet, true, urlMappings);
}
//构造方法
public ServletRegistrationBean(T servlet, boolean alwaysMapUrl, String... urlMappings) {
Assert.notNull(servlet, "Servlet must not be null");
Assert.notNull(urlMappings, "UrlMappings must not be null");
this.servlet = servlet;
this.alwaysMapUrl = alwaysMapUrl;
this.urlMappings.addAll(Arrays.asList(urlMappings));
}
//设置要注册的servlet
public void setServlet(T servlet) {
Assert.notNull(servlet, "Servlet must not be null");
this.servlet = servlet;
}
public T getServlet() {
return this.servlet;
}
/** 省略一些set get方法 **/
//此处实现了父类的addRegistration方法,完成了servlet的最终注册,即通过ServletContext.addServlet方法
@Override
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
String name = getServletName();
return servletContext.addServlet(name, this.servlet);
}
//该方法是基于addRegistration返回的ServletRegistration.Dynamic来做进一步的配置
@Override
protected void configure(ServletRegistration.Dynamic registration) {
super.configure(registration);
String[] urlMapping = StringUtils.toStringArray(this.urlMappings);
if (urlMapping.length == 0 && this.alwaysMapUrl) {
urlMapping = DEFAULT_MAPPINGS;
<