1,首先我们来看看spring-web与spring-webmvc的关系
`spring-web` provides core HTTP integration, including some handy Servlet filters, Spring HTTP Invoker, infrastructure to integrate with other web frameworks and HTTP technologies e.g. Hessian, Burlap.
`spring-webmvc` is an implementation of Spring MVC. `spring-webmvc` [depends on](http://repo1.maven.org/maven2/org/springframework/spring-webmvc/3.1.3.RELEASE/spring-webmvc-3.1.3.RELEASE.pom) on `spring-web`, thus including it will transitively add `spring-web`. You don't have to add `spring-web`explicitly.
You should depend only on `spring-web` if you don't use Spring MVC but want to take advantage of other web-related technologies that Spring supports.
2,可完成初始化Springmvc的几种途径
spring-mvc 基本逻辑是通过DispatcherServlet来处理客户端的http请求,所以其核心是要将DispatcherServlet添加到Servlet容器的Servlet链中。
2.1,在web.xml中配置DispatcherServlet
使用父ApplicationContext管理所有bean的方式
<web-app>
<!-- 通过Listener在容器启动后创建ApplicationContext,并作为父级上下文 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 指定Listener中创建ApplicationContext所使用的配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
<!-- 配置spring webmvc执行特定请求的servlet【可以配置多个的哦】-->
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!-- 不指定子ApplicationContext初始化使用的配置文件-->
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
使用父ApplicationContext管理公共bean(例如:Service,Dao等),使用子ApplicationContext管理与web相关bean(例如:Controller,HandlerMapping等)的方式。
<web-app>
<!-- 通过Listener在容器启动后创建ApplicationContext,并作为父级上下文 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 指定Listener中创建ApplicationContext所使用的配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/root-context.xml</param-value>
</context-param>
<!-- 配置spring webmvc执行特定请求的servlet【可以配置多个的哦】-->
<servlet>
<servlet-name>app1</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!-- 指定了子ApplicationContext初始化使用的配置文件-->
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app1-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app1</servlet-name>
<url-pattern>/app1/*</url-pattern>
</servlet-mapping>
</web-app>
更多可参考:https://docs.spring.io/spring-framework/docs/current/reference/html/web.html
2.2,通过Servlet3+规范来初始化
从Servlet3.0开始我们可以通过编程的方式来配置servlet或filter。第一种方法是通过ServletContextListener监听器来添加;另一种是通过ServletContainerInitializer接口来添加。
2.2.1 通过Listener来添加
@WebListener
public class InitServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent contextEvent) {
ServletContext context = contextEvent.getServletContext() ;
//添加过滤器
FilterRegistration.Dynamic filterRegistration = context.addFilter("filterName","className") ;
//filterRegistration.addMappingForUrlPatterns();
//添加servlet
ServletRegistration.Dynamic servletRegistration = context.addServlet("servletName","className") ;
servletRegistration.addMapping("/*") ;
servletRegistration.setInitParameter("name","custom") ;
servletRegistration.setLoadOnStartup(1);
//servletRegistration.setMultipartConfig();
//添加监听器
context.addListener(HttpSessionIdListener.class);
}
}
2.2.2 通过ServletContainerInitializer来添加
在spring-web\META-INF\services\javax.servlet.ServletContainerInitializer文件中配置了一个实现类org.springframework.web.SpringServletContainerInitializer。
通过Java提供的SPI机制,Servlet容器可以得到jar包中配置的ServletContainerInitializer实现类,然后通过Servlet容器自己的ServletContainerInitializer实现类来调用通过SPI机制得到的ServletContainerInitializer实现类的实例。
例如在Tomcat中就有一个ServletContainerInitializer 的实现类TomcatStarter :
/**
* {@link ServletContainerInitializer} used to trigger {@link ServletContextInitializer
* ServletContextInitializers} and track startup errors.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
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;
}
@Override
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
try {
for (ServletContextInitializer initializer : this.initializers) {
initializer.onStartup(servletContext);
}
}
catch (Exception ex) {
this.startUpException = ex;
// Prevent Tomcat from logging and re-throwing when we know we can
// deal with it in the main thread, but log for information here.
if (logger.isErrorEnabled()) {
logger.error("Error starting Tomcat context. Exception: " + ex.getClass().getName() + ". Message: "
+ ex.getMessage());
}
}
}
Exception getStartUpException() {
return this.startUpException;
}
}
期间会调用SpringServletContainerInitializer实例对象的onStartup方法。
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
SpringServletContainerInitializer作为了WebApplicationInitializer类型的委托,由于该类添加了 @HandlesTypes(WebApplicationInitializer.class) 注解,所以servlet容器会扫描类路径下WebApplicationInitializer类型的实现类,并将所有的实现类放入Set集合中传递给方法的参数。
那么只要提供一个WebApplicationInitializer的实现类便可以驱动spring webmvc框架的初始化了。
2.3,通过ServletRegistrationBean来完成初始化
在前面的章节《SpringBoot内嵌Servlet容器启动过程》中有讲过在springboot中servlet容器的启动过程,我们可以通过 org.springframework.boot.web.servlet.ServletContextInitializer 的子类来完成spring-webmvc的初始化工作,而其子类DispatcherServletRegistrationBean便可以很好的完成将DispatcherServlet注册到servlet链的工作。
在spring-boot-autoconfigure-2.4.1.jar!\META-INF\spring.factories文件中有几个有关web的自动状态的配置类
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
rg.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
而其中的DispatcherServletAutoConfiguration配置类并可完成DispatcherServletRegistrationBean创建,从而实现DispatcherServlet的注册。
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
/*
* The bean name for a DispatcherServlet that will be mapped to the root URL "/"
*/
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
/*
* The bean name for a ServletRegistrationBean for the DispatcherServlet "/"
*/
public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {
@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
@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;
}
}
@Configuration(proxyBeanMethods = false)
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {
@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;
}
}
}