【Java EE】Spring MVC的初始化

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文件作为配置便可以了,然后重启服务器。 

发布了280 篇原创文章 · 获赞 169 · 访问量 32万+

猜你喜欢

转载自blog.csdn.net/ARPOSPF/article/details/80297682