什么是SpringMVC
SpringMVC是一個基于MVC的web框架,屬于Spring中的一個模塊,它和Spring不需要通過中間層進行整合就可以一起使用。
SpringMVC框架是以請求為驅(qū)動,圍繞Servlet設(shè)計,將請求發(fā)給控制器,然后通過模型對象,分派器來展示請求結(jié)果視圖。其中核心類是DispatcherServlet,它是一個Servlet,頂層是實現(xiàn)的Servlet接口。
SpringMVC請求處理流程

流程說明:
1、客戶端(瀏覽器)發(fā)送請求,直接請求到DispatcherServlet。
2、DispatcherServlet根據(jù)請求信息調(diào)用HandlerMapping,解析請求對應(yīng)的Handler。
3、解析到對應(yīng)的Handler后,開始由HandlerAdapter適配器處理。
4、HandlerAdapter會根據(jù)Handler來調(diào)用真正的處理器開處理請求,并處理相應(yīng)的業(yè)務(wù)邏輯。
5、處理器處理完業(yè)務(wù)后,會返回一個ModelAndView對象,Model是返回的數(shù)據(jù)對象,View是個邏輯上的View。
6、ViewResolver會根據(jù)邏輯View查找實際的View。
7、DispaterServlet把返回的Model傳給View。
8、通過View返回給請求者(瀏覽器)
SpringMVC使用

傳統(tǒng)的XML配置方式
傳統(tǒng)的XML配置方式,需要在webapp/WEB-INF目錄下web.xml文件,在文件里面需要配置Context和Servlet,主要是讓那個Tomcat Context初始化的時候去初始化Spring容器相關(guān)的服務(wù),隨后保存在Context實例里面,以供其servlet來使用。
<web-app>
<!--上下文參數(shù),在監(jiān)聽器中被使用,實際就是key-value,key=contextConfigLocation寫死-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--監(jiān)聽器配置,初始化WebApplicationContext-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--前端控制器-->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springMVC.xml</param-value>
</init-param>
<!--是啟動順序,讓這個Servlet隨Servletp容器一起啟動。-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
ContextLoaderListener
ContextLoaderListener可以指定在Web應(yīng)用程序啟動時載入Ioc容器,正是通過ContextLoader來實現(xiàn)的,可以說是Ioc容器的初始化工作。
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent event) {
//初始化webApplicationCotext
initWebApplicationContext(event.getServletContext());
}
}
ContextLoaderListener實現(xiàn)了ServletContextListener,其本質(zhì)是一個Servlet的監(jiān)聽器,Tomcat會優(yōu)先加載Servlet的監(jiān)聽器組件,以保證在Servlet被創(chuàng)建之前,即Context組件進行初始化的時候,去調(diào)用其里面的contextInitialized方法來根據(jù)contextConfigLocation去讀取并解析Spring容器的配置,去創(chuàng)建并刷新出容器的實例來。
initWebApplicationContext
public class ContextLoader {
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//從ServletContext中查找,是否存在以
// WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE為Key的值
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
servletContext.log("Initializing Spring root WebApplicationContext");
Log logger = LogFactory.getLog(ContextLoader.class);
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
// 創(chuàng)建web上下文,默認是XmlWebApplicationContext
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
// 如果該容器還沒有刷新過
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
// 配置并刷新容器
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//將ApplicationContext放入ServletContext中,
// 其key為<WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
//將ApplicationContext放入ContextLoader的全局靜態(tài)常量Map中,
// 其中key為:Thread.currentThread().getContextClassLoader()即當(dāng)前線程類加載器
currentContextPerThread.put(ccl, this.context);
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException | Error ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}
}
該方法會調(diào)用
configureAndRefreshWebApplicationContext(cwac, servletContext)去創(chuàng)建并刷新Spring容器,刷新完成之后,就會將容器的實例保存到Tomcat的Context組件實例里,以供后續(xù)要創(chuàng)建出來的DispatcherServlet實例來調(diào)用。隨后在Servlet里面配置DispatcherServlet,并且給Servlet的contextConfigLocation初始化變量賦值上SpringMVC的配置文件路徑,以方便在DispatcherServlet初始化的時候去調(diào)用init方法去讀取contextConfigLocation里面的配置文件并加載相關(guān)的配置。通常情況下會加載RequestMapping標(biāo)記的類,以及對應(yīng)的方法和請求路徑、請求方法的映射。
注解配置方式
// 完整命名: javax.servlet.ServletContainerInitializer
public interface ServletContainerInitializer {
void onStartup(Set<Class<?>> var1, ServletContext var2) throws ServletException;
}
ServletContainerInitializer 是 Servlet 3.0 新增的一個接口,主要用于在容器啟動階段通過編程風(fēng)格注冊Filter、Servlet以及Listener,以取代通過web.xml配置注冊。這樣就利于開發(fā)內(nèi)聚的web應(yīng)用框架。
Servlet3通過SPI的機制允許自定義一個ServletContainerInitializer的實現(xiàn)類,Servlet容器會在啟動的時候自動調(diào)用實現(xiàn)類的onStartup方法,我們可以在該方法中進行一些Servlet對象的注冊。
另外在實現(xiàn)ServletContainerInitializer時還可以通過@HandlesTypes注解定義本實現(xiàn)類希望處理的類型,容器會將當(dāng)前應(yīng)用中所有這一類型(繼承或者實現(xiàn))的類放在ServletContainerInitializer接口的集合參數(shù)中傳遞進來。如果不定義處理類型,或者應(yīng)用中不存在相應(yīng)的實現(xiàn)類,則集合參數(shù)為空。
@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<>();
// webAppInitializerClasses 就是servlet3.0規(guī)范中為我們收集的 WebApplicationInitializer 接口的實現(xiàn)類的class
// 從webAppInitializerClasses中篩選并實例化出合格的相應(yīng)的類
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");
// 這行代碼說明我們在實現(xiàn)WebApplicationInitializer可以通過繼承Ordered, PriorityOrdered來自定義執(zhí)行順序
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
// 迭代每個initializer實現(xiàn)的方法
initializer.onStartup(servletContext);
}
}
}
1、通過@HandlesType注解在onStartup方法的參數(shù)列表中注入感興趣的類,即WebApplicationInitializer;
2、將WebApplicationInitializer的每個實現(xiàn)類,都新建一個實例,并放入initializers列表中;
3、遍歷initializers列表,對每個WebApplicationInitializer實例執(zhí)行其onStartup方法。
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
registerDispatcherServlet(servletContext);
}
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return null or empty");
//創(chuàng)建Spring web ioc 容器
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
//創(chuàng)建DispatcherServlet實例
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
if (registration == null) {
throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
"Check if there is another servlet registered under the same name.");
}
registration.setLoadOnStartup(1);
//路徑映射
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
//請求攔截器,可以在此處控制編碼
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
customizeRegistration(registration);
}
}
WebApplicationInitializer的實現(xiàn)類及其功能

WebApplicationInitializer的實現(xiàn)類有很多,重點看一下AbstractAnnotationConfigDispatcherServletInitializer
public abstract class AbstractAnnotationConfigDispatcherServletInitializer
extends AbstractDispatcherServletInitializer {
@Override
@Nullable
protected WebApplicationContext createRootApplicationContext() {
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(configClasses);
return context;
}
else {
return null;
}
}
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
Class<?>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
context.register(configClasses);
}
return context;
}
@Nullable
protected abstract Class<?>[] getRootConfigClasses();
@Nullable
protected abstract Class<?>[] getServletConfigClasses();
}
這個類提供了兩個方法的實現(xiàn),以及兩個抽象方法供子類繼承
1、createRootApplicationContext:創(chuàng)建根容器。
2、createServletApplicationContext:創(chuàng)建servlet容器。
3、getRootConfigClasses:抽象類,用于注冊根容器的配置類,相當(dāng)于spring.xml。
4、getServletConfigClasses:抽象的類,用于注冊servlet容器的配置類,相當(dāng)于springmvc.xml。
5、它還從AbstractDispatcherServletInitializer類繼承了getServletMappings方法,用于注冊servlet的映射。
6、還從AbstractDispatcherServletInitializer類中繼承了getServletFilters方法,用于攔截并處理請求的編碼。
因此,我們可以自定義一個WebApplicationInitializer的實現(xiàn)類,繼承AbstractAnnotationConfigDispatcherServletInitializer;在servlet容器啟動時,會創(chuàng)建spring根容器和servlet容器,代替web.xml配置文件。同時,我們可以看到,在基于注解的springmvc開發(fā)中,真正用于代替web.xml的是WebApplicationInitializer,而并不是ServletContainerInitializer,這與本文開頭提到的基于注解的servlet開發(fā)有些區(qū)別。
根容器和servlet容器
- 根容器用于管理@Service、@Repository等業(yè)務(wù)邏輯層和數(shù)據(jù)庫交互層組件。
- servlet容器用于管理@Controller、視圖解析器、攔截器等跟頁面處理有關(guān)的組件。
使用步驟
1、導(dǎo)包或添加依賴:spring-web、spring-webmvc。
2、編寫數(shù)據(jù)庫訪問層、業(yè)務(wù)邏輯層、控制層等組件,這個跟基于配置文件的springmvc沒有區(qū)別。
3、編寫根容器和servlet容器的配置類,這里不需要添加@Configuration注解。
4、自定義WebApplicationInitializer,繼承AbstractAnnotationConfigDispatcherServletInitializer。
5、在3的實現(xiàn)類中注冊根容器和servlet容器的配置類,以及servlet映射。
6、在servlet容器中中注冊視圖解析器、攔截器等組件,用法是使servlet容器配置類實現(xiàn)WebMvcConfigurer接口,然后選擇相應(yīng)的方法進行注冊。
添加依賴
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
compile group: 'org.springframework', name: 'spring-webmvc', version: '5.2.6.RELEASE'
自定義springmvc攔截器
/**
* 自定義的springmvc攔截器
*/
public class CustomedInterceptor implements HandlerInterceptor {
/**
* 重寫preHandle方法
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle正在執(zhí)行...");
return true;
}
/**
* 重寫postHandle方法
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle正在執(zhí)行...");
}
/**
* 重寫afterCompletion方法
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion正在執(zhí)行...");
}
}
SpringRootConfig
根容器的配置類,用于管理數(shù)據(jù)庫訪問層、業(yè)務(wù)邏輯層等組件,相當(dāng)于spring.xml
@Configuration
@ComponentScan("com.yibo.service")
public class SpringRootConfig {
}
MVCConfig
- servlet容器的配置類,用于管理控制層、視圖解析器等組件,相當(dāng)于springmvc.xml
- 可以在其中配置視圖解析器、靜態(tài)資源解析器、攔截器等組件
@Configuration
@ComponentScan("com.yibo.controller")
@EnableWebMvc
public class MVCConfig implements WebMvcConfigurer {
/**
* 注冊視圖解析器
* @return
*/
/*@Bean
public InternalResourceViewResolver viewResolver(){
InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
internalResourceViewResolver.setPrefix("/WEB-INF/jsp/");
internalResourceViewResolver.setSuffix(".jsp");
return internalResourceViewResolver;
}*/
/**
* 注冊視圖解析器
* @param registry
*/
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/jsp/", ".jsp");
}
/**
* 注冊靜態(tài)資源解析器
* 將springmvc處理不了的請求交給tomcat
* @param configurer
*/
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
/**
* 注冊攔截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CustomedInterceptor());
}
}
StartWebApplicationInitializer
自定義的WebApplicationInitializer,用于注冊根容器、servlet容器、servlet映射、攔截器、監(jiān)聽器等,相當(dāng)于web.xml
/**
* 自定義web容器啟動器
*/
public class StartWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* SpringContext中相關(guān)的bean
* 注冊根容器配置類
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{SpringRootConfig.class};
}
/**
* DispatcherServlet中上下文相關(guān)的bean
* 注冊servlet容器(mvc子容器)配置類
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{MVCConfig.class};
}
/**
* Servlet請求映射路徑
* @return
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
/**
* 攔截并處理請求的編碼
* @return
*/
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
encodingFilter.setEncoding("UTF-8");
encodingFilter.setForceEncoding(true);
return new Filter[]{encodingFilter};
}
}