Spring MVC的初始化
Spring Web MVC是Spring提供给Web应用的框架设计,实际上也是一个设计理念。对于Spring MVC,它的流程和各个组件的应用和改造Spring MVC的根本。
1. MVC设计概述
MVC设计不仅限于Java Web应用,还包括许多应用,比如前端、PHP、.NET等语言。之所以这么做的根本原因在于解耦各个模块。
早期的MVC模型多了一个Servlet组件,首先是用户的请求到达Servlet,Servlet组件主要作为控制器,这样Servlet就接受了这个请求,可以通过它调用java Bean,来读写数据库的数据,然后将结果放到JSP中,这样就可以获取数据并展现给用户了。这样的模式称为MVC模式,而Servlet扮演控制器(Controller)的功能,Java Bean则是一个专门操作数据库组件的模型层(Model)。JSP主要是展示给用户看的,所以是一个视图(View)的功能。
1.1 Spring MVC的架构
对于持久层而言,随着软件发展,迁移数据库的可能性很小,所以在大部分情况下都用不到Hibernate的HQL来满足移植数据库的要求。与此同时,性能对互联网更为重要,不可优化SQL、不够灵活成了Hibernate难以治愈的伤痛,这样MyBatis就崛起了。无论是Hibernate还是MyBatis都没处理好数据库事务的编程,同时随着各种NoSQL的强势崛起,使得Java Web应用不仅能够在数据库获取数据,也可以从NoSQL中获取数据,这些已经不是持久层框架能够处理的了,而Spring MVC给出了方案,如图所示:
图中展示了传统的模型层被拆分为业务层(Service)和数据访问层(DAO,Data Access Object)。在Service下可以通过Spring的声明式事务操作数据访问层,而在业务层上还允许我们访问NoSQL,这样就能够满足现今异军崛起的NoSQL的使用了,它的使用将大大提高互联网系统的性能。对于Spring MVC而言,其最大的特色是结构松散,比如几乎可以在Spring MVC中使用各类视图,包括JSON、JSP、XML、PDF等,所以它能够满足手机端、页面端和平板电脑端的各类请求,这就是现在它如此流行的原因。
1.2 Spring MVC组件与流程
Spring MVC的核心在于其流程,这是使用Spring MVC框架的基础,Spring MVC是一种基于Servlet的技术,它提供了核心控制器DispatcherServlet和相关的组件,并制定了松散的结构,以适合各种灵活的需要。首先给出其组件和流程图,如图所示:
图中的数字给出了Spring MVC的服务流程及其各个组件运行的顺序,这是Spring MVC的核心。
首先,Spring MVC框架是围绕着DispatcherServlet而工作的,所以这个类是其最为重要的类。从它的名字来看,它是一个Servlet,它可以拦截HTTP发送过来的请求,在Servlet初始化(调用init方法)时,Spring MVC会根据配置,获取配置信息,从而得到统一资源标识符(URI,Uniform Resource Identifier)和处理器(Handler)之间的映射关系(HandlerMapping),为了更加灵活和增强功能,Spring MVC还会给处理器加入拦截器,所以还可以在处理器执行前后加入自己的代码,这样就构成了一个处理器的执行链(HandlerExecutionChain),并且根据上下文初始化视图解析器等内容,当处理器返回的时候就可以通过视图解析器定位视图,然后将数据模型渲染到视图中,用来响应用户的请求了。
当一个请求到来时,DispatcherServlet首先通过请求和事先解析好的HandlerMapping配置,找到对应的处理器(Handler),这样就准备开始运行处理器和拦截器组成的执行链,而运行处理器需要有一个对应的环境,这样它就有了一个处理器的适配器(HandlerAdapter),通过这个适配器就能运行对应的处理器及其拦截器,这里的处理器包含了控制器的内容和其他增强的功能,在处理器返回模型和视图给DispacherServlet后,DispacherServlet就会把对应的视图信息传递给视图解析器(ViewResolver)。注意,这一步取决于是否使用逻辑视图,如果是逻辑视图,那么视图解析器就会解析它,然后把模型渲染到视图中去,最后响应用户的请求;如果不是逻辑视图,则不会进行处理,而是直接通过视图渲染数据模型。这就是一个SpringMVC完整的流程,它是一个松散的结构,所以可以满足各类请求的需要,为此它也实现了大部分的请求所需的类库,拥有较为丰富的类库供我们使用,所以流程中的大部分组件并不需要我们去实现,只是我们应该知道整个流程,熟悉它们的使用就可以构建出强大的互联网系统了。
1.3 Spring MVC入门实例
首先介绍以XML配置的方式学习Spring MVC,这里首先需要配置Web工程的web.xml文件,代码(web.xml)如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
<!-- 配置Spring IoC配置文件路径 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<!-- 配置ContextLoaderListener用以初始化Spring IoC容器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 配置DispatcherServlet -->
<servlet>
<!-- 注意:Spring MVC 框架会根据 servlet-name 配置,找到/WEB-INF/dispatcher-servlet.xml作为配置文件载入Web工程中 -->
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 使得Dispatcher在服务器启动的时候就初始化 -->
<load-on-startup>2</load-on-startup>
</servlet>
<!-- Servlet拦截配置 -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
其中的:
- 系统变量contextConfigLocation的配置,它会告诉Spring MVC其Spring IoC的配置文件在哪里,这样Spring就会找到这些配置文件去加载它们。如果是多个配置文件,可以使用逗号将它们分隔开来,并且它还能支持正则表达式匹配,进行模糊匹配,这样就更加灵活了,其默认值为/WEB-INF/applicationContext.xml。
- ContextLoaderListener实现了接口ServletContextListener,通过Java Web容器的学习,知道ServletContextListener的作用是可以在整个Web工程前后加入自定义代码,所以可以在Web工程初始化之前,它先完成对Spring IoC容器的初始化,也可以在Web工程关闭之时完成Spring IoC容器的资源进行释放。
- 配置DispatcherServlet,首先是配置了servlet-name为dispatcher,这意味着需要一个/WEB-INF/dispatcher-servlet.xml文件(注意,servlet-name和文件名的对应关系)与之对应,并且我们配置了在服务器启动期间就初始化它。
- 配置DispatcherServlet拦截以后缀“do”结束的请求,这样所有的以后缀“do”结尾的请求都会被它拦截。
在最简单的入门例子中暂时不配置applicationContext.xml的任何内容,所以其代码也是空的,代码(applicationContext.xml)如下:
<?xml version='1.0' encoding='UTF-8' ?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
</beans>
这样Spring IoC容器就没有装载自己的类,根据之前的论述,它还会加载一个/WEB-INF/dispatcher-servlet.xml文件,它是与Spring MVC配置相关的内容,所以它会有一定的内容,代码(dispatcher-servlet.xml)如下:
<?xml version='1.0' encoding='UTF-8' ?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
<!-- 使用注解驱动 -->
<mvc:annotation-driven/>
<!-- 定义扫描装载的包 -->
<context:component-scan base-package="com.ssm.chapter14.*"/>
<!-- 定义视图解析器 -->
<!-- 找到Web工程/WEB-INF/JSP文件夹,且文件结尾为jsp的文件作为映射 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/WEB-INF/jsp/" p:suffix=".jsp"/>
<!-- 如果有配置数据库事务,需要开启注解事务的,需要开启这段代码 -->
<!-- <tx:annotation-driven transaction-manager="transactionManager" /> -->
</beans>
配置说明如下:
- <mvc:annotation-driven />表示使用注解驱动Spring MVC.
- 定义一个扫描的,用它来扫描对应的包,用以加载对应的控制器和其他的一些组件。
- 定义视图解析器,解析器中定义了前缀和后缀,这样视图就知道去Web工程的/WEB-INF/JSP文件作为视图响应用户请求。
这样,一个简单的Controller如下:
package com.ssm.chapter14.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
//注解@Controller表示它是一个控制器
@Controller("myController")
//表明当请求的URI在/my下的时候才有该控制器响应
@RequestMapping("/my")
public class MyController {
//表明URI是/index的时候该方法才请求
@RequestMapping("/index")
public ModelAndView index() {
//模型和视图
ModelAndView mv = new ModelAndView();
//视图逻辑名称为index
mv.setViewName("index");
//返回模型和视图
return mv;
}
}
首先注解@Controller是一个控制器。Spring MVC扫描的时候就会把它作为控制器加载进来。然后,注解@RequestMapping指定了对应的请求的URI,Spring MVC在初始化的时候就会将这些信息解析,存放起来,于是便有了HandlerMapping,当发生请求时,Spring MVC就会去使用这些信息去找到对应的控制器提供服务。
方法定义返回ModelAndView,在方法中把视图名称定义为index,在配置文件中所配置的视图解析器,由于配置前缀/WEB-INF/jsp/,后缀.jsp,加上返回的视图逻辑名称为in-dex,所以它会选择使用/WEB-INF/jsp/index.jsp作为最后的响应,于是要开发/WEB-INF/jsp/index.jsp文件,如代码(index.jsp)所示:
<%@page contentType="text/html" pageEncoding="UTF-8" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Welcome to Spring Web MVC project</title>
</head>
<body><h1>Hello, Spring MVC</h1></body>
</html>
启动服务器比如Tomcat,输入对应的URL,就可以看到对应的响应了。
由于Spring MVC组件和流程的重要性,这里展示了上述案例的流程图:
当Spring MVC启动的时候就会去解析MyController的注解,然后生成对应的URI和请求的映射关系,并注册对应的方法。当请求来到的时候,首先根据URI找到对应的HandleMapping,然后组织为一个执行链,通过请求类型找到RequestMappingHandlerAdapter,它的实例是在DispatcherServlet初始化的时候进行创建的。然后通过它去执行HandlerExecutionChain的内容,最终在MyController的方法中将index的视图返回DispatcherServlet。由于配置的视图解析器(InternalResourceViewResolver)前缀为/WEB-INF/jsp/,后缀为.jsp,视图名为index,所以最终它会找到/WEB-INF/jsp/index.jsp文件作为视图,响应最终的请求,这样整个Spring MVC的流程就走通了。
2. Spring MVC初始化
整个Spring MVC的初始化,配置了DispatcherServlet和ContextLoaderListener,那么它们是如何初始化Spring IoC容器上下文和映射请求上下文的呢?所以这里的初始化会涉及两个上下文的初始化,只是映射请求上下文是基于Spring IoC上下文扩展出来,以适应Java Web工程的需要。
2.1 初始化Spring IoC上下文
Java Web容器为其生命周期中提供了ServletContextListener接口,这个接口可以在Web容器初始化和结束期中执行一定的逻辑,换句话说,通过实现它可以使得在DispatcherServlet初始化前就可以完成Spring IoC容器的初始化,也可以在结束期完成对Spring IoC容器的销毁,只要实现ServletContextListener接口的方法就可以了。Spring MVC交给了类ContextLoaderListener,其源码如下:
package org.springframework.web.context;
/************import*******************/
public class ContextLoaderListener extends ContextLoader implements ServletContextListener{
......
/**
* Initialize the root web application context
*/
@Override
public void contextInitialized(ServletContext event){
//初始化Spring IoC容器,使用的是满足ApplicationContext接口的Spring Web IoC容器
initWebApplicationContext(event.getServletContext());
}
/**
* Close the root web application context
*/
@Override
public void contextDestoryed(ServletContextEvent event){
//关闭Web IoC容器
closeWebApplicationContext(event.getServletContext());
//清除相关参数
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
2.2 初始化映射请求上下文
映射请求上下文是通过DispatcherServlet初始化的,和普通的Servlet一样,可以根据自己的需求配置它在启动时初始化,或者等待用户第一次请求的时候进行初始化。注意,也许在Web工程中并没有注册ContextLoaderListener,这个时候DispatcherServlet就会在其初始化的时候进行对Spring IoC容器的初始化。这样也许会有一个疑问:选择在什么时候初始化DispatcherServle?
首先,初始化一个Spring IoC容器是一个耗时的操作,所以这个工作不应该放到用户请求上,没有必要让一个用户陷入长期等待中,因此大部分场景下,都应该让DispatcherServle在服务器启动期间就完成Spring IoC容器的初始化,我们可以在Web容器刚启动的时候,也可以在Web容器载入DispatcherServle的时候进行初始化。建议是在Web容器刚开始的时候对其初始化,因为在整个Web的初始化中,不只是DispatcherServle需要使用到Spring IoC的资源,其他的组件可能也需要。在最开始就初始化可以让Web中的各个组件共享资源。当然你可以指定Web容器中组件初始化的顺序,让DispatcherServle第一个初始化,来解决这个问题,但是这就加大了配置的复杂度,因此大部分的情况下都建议使用ContextLoaderListener进行初始化。
DispatcherServlet的设计如下图所示;
从图中可以看出,DispatcherServlet的父类是FrameworkServlet,而FrameworkServlet的父类则是HttpServletBean。HttpServletBean继承了Web容器所提供的HttpServlet,所以它是一个可以载入Web容器中的Servlet。
Web容器对于Servlet的初始化,首先是调用其init方法,对于DispatcherServlet也是如此,这个方法位于它的父类HttpServletBean里,代码如下:
/**
* Map config parameters onto bean properties of this servlet, and
* invoke subclass initialization.
* @throws ServletException if bean properties are invalid (or required
* properties are missing), or if subclass initialization fails.
*/
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
try {
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
throw ex;
}
// Let subclasses do whatever initialization they like.
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
在类HttpServletBean中可以看到initServletBean方法,在FrameworkServlet中也可以看到它,我们知道子类的方法会覆盖掉父类的方法,所以着重看FrameworkServlet中的initServletBean方法。代码如下:
/**
* Overridden method of {@link HttpServletBean}, invoked after any bean properties
* have been set. Creates this servlet's WebApplicationContext.
*/
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}
/**
* Initialize and publish the WebApplicationContext for this servlet.
* <p>Delegates to {@link #createWebApplicationContext} for actual creation
* of the context. Can be overridden in subclasses.
* @return the WebApplicationContext instance
* @see #FrameworkServlet(WebApplicationContext)
* @see #setContextClass
* @see #setContextConfigLocation
*/
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
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 -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
当IoC容器没有对应的初始化的时候,DispatcherServlet会尝试去初始化它,最后调度onRefresh方法,那么它就是DispatcherServlet一个十分值得关注的方法。因为它将初始化Spring MVC的各个组件,而onRefresh这个方法就在DispatcherServlet中,代码如下:
/**
* This implementation calls {@link #initStrategies}.
*/
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
// 初始化文件的解析
initMultipartResolver(context);
// 本地解析化
initLocaleResolver(context);
// 主题解析
initThemeResolver(context);
// 处理器映射
initHandlerMappings(context);
// 处理器的适配器
initHandlerAdapters(context);
// Handler的异常解析器
initHandlerExceptionResolvers(context);
// 当处理器没有返回逻辑视图名称等相关信息时,自动将请求URL映射为逻辑视图名
initRequestToViewNameTranslator(context);
// 视图逻辑名称转化器,即允许返回逻辑视图名称,然后它会找到真实的视图
initViewResolvers(context);
// 这是一个关注Flash开发的Map管理器,不再介绍
initFlashMapManager(context);
}
Spring MVC的核心组件如下:
- MultipartResolver:文件解析器,用于支持服务器的文件上传
- LocaleResolver:国际化解析器,可以提供国际化的功能。
- ThemeResolver:主题解析器,类似于软件皮肤的转换功能。
- HandlerMapping:Spring MVC中十分重要的内容,它会包装用户提供一个控制器的方法和对它的一些拦截器,通过调用它就能够运行控制器。
- handlerAdapter:处理器适配器,因为处理器会在不同的上下文中运行,所以Spring MVC会先找到合适的适配器,然后运行处理器服务方法。比如对于控制器的SimpleControllerHandlerAdapter、对于普通请求的HttpRequestHandlerAdapter等。
- HandlerExceptionResolver:处理器异常解析器,处理器有可能产生异常,如果产生异常,则可以通过异常解析器来处理它,比如出现异常后,可以转到指定的异常页面,这样使得用户的UI体验得到了改善。
- RequestToViewNameTranslator:视图逻辑名称转换器,有时候在控制器中返回一个视图的名称,通过它可以找到实际的视图。当处理器没有返回逻辑视图名等相关信息时,自动将请求URL映射为逻辑视图名。
- ViewResolver:视图解析器,当控制器返回后,通过视图解析器会把逻辑视图名称进行解析,然后定位实际视图。
事实上,对这些组件DispatcherServlet会根据其配置文件DispatcherServlet.properties进行初始化,文件内容如下:
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
由此可见,在启动期间DispatcherServlet会加载这些配置的组件进行初始化。
2.3 使用注解配置方式初始化
由于在Servlet3.0之后的规范允许取消web.xml配置,只使用注解方式便可以了,所以在Spring3.1之后的版本也提供了注解方式的配置。使用注解方式很简单,首先继承一个名字比较长的类AbstractAnnotationConfigDispatcherServletInitializer,然后实现它所定义的方法。它所定义的内容就不是太复杂,甚至是比较简单的,让我们通过一个类去继承它,如下代码所示,它实现的是入门实例的功能。
package com.ssm.chapter14.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
//Spring IoC容器配置
//getRootConfigClasses获取Spring IoC容器的Java配置类,用以装载各类Spring Bean。
@Override
protected Class<?>[] getRootConfigClasses() {
//可以返回Spring的Java配置文件数组
return new Class<?>[]{};
}
//DispatcherServlet的URI映射关系配置
//getServletConfigClasses获取各类Spring MVC的URI和控制器的配置关系类,用以生成Web请求的上下文。
@Override
protected Class<?>[] getServletConfigClasses() {
//可以返回Spring的Java配置文件数组
return new Class<?>[]{WebConfig.class};
}
//DispatcherServlet拦截内容
// getServletMappings定义DispatcherServlet拦截的请求。
@Override
protected String[] getServletMappings() {
return new String[]{"*.do"};
}
}
这里使用它来代替XML的配置,为什么只需要继承类AbstractAnnotationConfigDispatcherServletInitializer,Spring MVC就会去加载这个Java文件?Servlet 3.0之后的版本允许动态加载Servlet,只是按照规范需要实现ServletContainerIntializer接口而已。于是Spring MVC框架在自己的包内实现了一个类,它就是SpringServletContainerInitializer,它实现了ServletContainerInitializer接口,这样就能够通过它去加载开发者提供的MyWebAppInitializer了,SpringServletContainerInitializer源码如下:
// 定义初始化器的类型,只要实现WebApplicationInitializer接口则为初始化器
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
// 初始化器, 允许多个
List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();
// webAppInitializerClasses 就是servlet3.0规范中为我们收集的 WebApplicationInitializer 接口的实现类的class
// 从webAppInitializerClasses中筛选并实例化出合格的相应的类
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) 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;
}
// 这行代码说明我们在实现WebApplicationInitializer可以通过继承Ordered, PriorityOrdered来自定义执行顺序
AnnotationAwareOrderComparator.sort(initializers);
servletContext.log("Spring WebApplicationInitializers detected on classpath: " + initializers);
// 迭代每个initializer实现的方法
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
这段代码中可以看到只要实现了WebApplicationInitializer接口的onStartup方法,Spring MVC就会把类当作一个初始化器加载进来。
MyWebAppInitializer也实现了WebApplicationInitializer接口。ContextLoader和DispatcherServlet的初始化器都是抽象类,通过它们就能初始化Spring IoC上下文和映射关系上下文,这就是只要继承AbstractAnnotationConfigDispatcherServle-tInitializer类就完成了DispatcherServlet映射关系和SpringIoC容器的初始化工作的原因。
这样关注的焦点就再次回到MyWebAppInitializer配置类上,它有3种方法:
- getRootConfigClasses获取Spring IoC容器的Java配置类,用以装载各类Spring Bean。
- getServletConfigClasses获取各类Spring MVC的URI和控制器的配置关系类,用以生成Web请求的上下文。
- getServletMappings定义DispatcherServlet拦截的请求。
如果getRootConfigClasses方法返回为空,就不加载自定义的Bean到Spring IoC容器中,而getServletConfigClasses加载了WebConfig,则它就是一个URI和控制器的映射关系类。由此产生Web请求的上下文。WebConfig的内容如下所示:
package com.ssm.chapter14.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@Configuration
//定义扫描的包,加载控制器
@ComponentScan("com.ssm.chapter14.*")
//启用Spring Web MVC
@EnableWebMvc
public class WebConfig {
/***
* 创建视图解析器
* @return 视图解析器
*/
@Bean(name = "viewResolver")
public ViewResolver initViewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/jsp/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
这段代码和Spring IoC使用Java的配置也是一样的,只是多了一个注解@EnableWebMvc,它代表启动Spring MVC框架的配置。和入门实例同样也定义了视图解析器,并且设置了它的前缀和后缀,这样就能获取由控制器返回的视图逻辑名,进而找到对应的JSP文件。
如果还是使用入门实例进行测试,此时可以把web.xml和所有关于Spring IoC容器所需的XML文件都删掉,只使用上面两个Java文件作为配置便可以了,然后重启服务器。