Spring MVC源码剖析

引言

一直以来都在使用Spring mvc,能够熟练使用它的各种组件。但是,它一直像个黑盒一样,我并不知道它内部是如何工作的,这几天一直在研究它的源码,今天已经把它的工作原理弄清了,因此把我的这个研究过程记录下来。现在让我们进入源码的世界,来看看这个黑盒中到底有什么神奇的东西。

spring MVC 总览

学一门新的知识,首先要大致了解它的全貌,然后在深入自己感兴趣的细节。
那么在这一小节中,我不会去深入具体的细节了解spring MVC,而是去了解它大致的流程,它是如何工作起来的。首先,让我们先看看下面这张流程图。
spring MVC 流程图
图片来源:我从Google Image

在接下来的文章中我会去深入源码来解释上图中的各个过程,现在大家对这张图有个概念就行。相信用过spring MVC的人都知道,客户端的请求要通过前端控制器(DispatcherServlet),然后前端控制器去找到请求相应的Controller. 因此我们可以猜到前端控制器一定要在Servlet容器启动时被实例化,所以我们需要把DispatcherServlet配置到web.xml文件中,部分配置如下:

<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

如果你是Spring Team的一员,在实例化对象的时候,你会做些什么,大家好好想一想?Spring Team 一定会把一些后续要用到的东西进行初始化。接下来,让我们进入源码的世界吧,一层层揭开Spring MVC的面纱。

DispatcherServlet的实例化之旅

DispatcherServlet的实例化之旅在这一小节中,我会通过查看Spring MVC的源码来看看在DispatcherServlet实例化的过程中,Spring到底初始化了一些什么的东西。
* DispatcherServlet中的静态代码块

/**
* Name of the class path resource (relative to the DispatcherServlet class)
* that defines DispatcherServlet's default strategy names.
*/
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
private static final Properties defaultStrategies;
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
}
}

当一个类被JVM加载、链接过后,JVM会调用类构造器会初始化一些静态域对象。因此上面的静态代码块会被执行,其中的代码只有一个目的,那就是从属性文件中加载默认的strategy实现,最后赋值给defaultStrategies变量。查看spring-webmvc jar包内部的结构。结果如下图:

image.png
大家可以打开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

相信大家一定熟悉里面的一些类吧,别着急,下面会用到这些东西,大家现在有个印象就行。在context成功的refresh过后,onRefresh方法就会被调用,然后它会调用initStrategies方法。下面让我们来看看initStrategies方法具体都初始化哪些strategy对象。

  • strategy对象的初始化)strategy对象的初始化
/**
* 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);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}

对于上面的各个初始化方法,我只讲解几个与我们开发者最密切的初始化方法,它们分别是initHandlerMappings, initHandlerAdapters, initHandlerExceptionResolvers,initViewResolvers。 如果大家对其它方法感兴趣,自己去查看一下相应的源码。下面让我们来深入各个初始化方法的细节。
* initHandlerMappings 方法
这个方法的作用是初始化我们程序将要用到的HandlerMapping对象,下面让我们来看看源码。源码如下:

/** List of HandlerMappings used by this servlet */
private List<HandlerMapping> handlerMappings;

/** Detect all HandlerMappings or just expect "handlerMapping" bean? */
private boolean detectAllHandlerMappings = true;

/**
* Initialize the HandlerMappings used by this class.
* <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
* we default to BeanNameUrlHandlerMapping.
*/
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;

if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
OrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}

// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
}

在了解上面代码的意义之前,让我们来看看我的spring.xml
文件中都配置了什么。

<context:component-scan base-package="me.xurtle" />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/" />
<property name="suffix" value=".jsp" />
</bean>

大家可以看到,我没有在spring的配置文件配置任何的HandlerMapping对象。从上面的代码我们可以看出detectAllHandlerMappings默认为true,也就是默认会去检测配置文件中所有的HandlerMapping,接下来是beansOfTypeIncludingAncestors方法,如果你去查看这个方法的源码,你会看到它的第一个参数给定的是ListableBeanFactory接口,对于这个接口的实现类来说,它可以枚举出所有定义的bean实例,而不是仅仅可以通过bean名称去获得定义的bean实例。那beansOfTypeIncludingAncestors具体的做法就是枚举出所有定义的bean,筛选出给定类型或其子类的bean,如果第一个参数是HierarchicalBeanFactory的子类,它也会去查找父类工厂中的HandlerMapping,最后返回一个Map对象。

如果返回的Map对象中存在HandlerMapping对象,那么接下来会把这些对象存入到ArrayList中。同样它也会保持HandlerMapping对象在集合中的顺序,大家可能会觉得奇怪,为什么要保持这些对象的顺序呢?其实很简单,等我下面分析到映射请求的时候,大家就会明白了。

如果一直都没有获得到HandlerMapping对象,那么接下来的getDefaultStrategies方法会给我们生成一些默认的HandlerMapping对象。其实这个方法也很简单,它其实就是用我们给定的strategy对象接口的名字作为key,接着去属性文件中加载对应的值。而这个属性文件就是上面我分析的静态代码块中初始化的那个属性文件。接着它会用StringUtils
工具类把拿到的value根据逗号分开存入到String数组中,然后遍历这些名字,根据相应的名字加载相应的类,创建相应的对象。不得不说,这个方法的实现真的很好,它只写这一个方法,就可以加载不同的strategy对象,实现了代码的重用,值得我们学习。

如果你Debug一下程序,你可以看到handlerMappings
列表中其实有两个实例,第一个是BeanNameUrlHandlerMapping,第二个是DefaultAnnotationHandlerMapping,就象我前面说的那样,Spring MVC会使用列表中的第一个对象,即BeanNameUrlHandlerMapping的对象,如果通过这个对象并没有找到相应的handler,然后才会使用DefaultAnnotationHandlerMapping的对象。

至此,我们还一个逻辑没有介绍。如果想让initHandlerMappings方法走这个逻辑,我们需要把detectAllHandlerMappings设置为false.其实这个很简单,只要在web.xml文件中配置一下就行了。部分代码如下:

<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</init-param>
<init-param>
<param-name>detectAllHandlerMappings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

除了上面的代码外,我们还需要在spring.xml
文件中定义一个HandlerMapping,部分代码如下:

<bean name="handlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />

你的bean名称一定为handlerMapping,不可以是其它的值。如果你把这个名称指定为其它的值,Spring框架不能得到这个bean,它依然会给你默认的HandlerMapping对象。

总结:通过上面源码的分析可以看出,在spring配置文件中明确指定一个bean名称为handlerMapping的做法更有效,因为它不需要去遍历配置文件中所有的bean,所以这个做法会加快initHandlerMappings方法的执行。

initHandlerAdapters 方法

这个方法的作用是初始化我们程序将要用到的HandlerAdapter对象,下面让我们来看看源码。源码如下:

/** List of HandlerAdapters used by this servlet */
private List<HandlerAdapter> handlerAdapters;

/** Detect all HandlerAdapters or just expect "handlerAdapter" bean? */
private boolean detectAllHandlerAdapters = true;

/**
* Initialize the HandlerAdapters used by this class.
* <p>If no HandlerAdapter beans are defined in the BeanFactory for this namespace,
* we default to SimpleControllerHandlerAdapter.
*/
private void initHandlerAdapters(ApplicationContext context) {
this.handlerAdapters = null;

if (this.detectAllHandlerAdapters) {
// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
Map<String, HandlerAdapter> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());
// We keep HandlerAdapters in sorted order.
OrderComparator.sort(this.handlerAdapters);
}
}
else {
try {
HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
this.handlerAdapters = Collections.singletonList(ha);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerAdapter later.
}
}

// Ensure we have at least some HandlerAdapters, by registering
// default HandlerAdapters if no other adapters are found.
if (this.handlerAdapters == null) {
this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
}
}
}

看完上面的代码是否有种似曾相识的感觉?对的,上面代码的逻辑和initHandlerMappings一样,同样的你可以设置detectAllHandlerAdapters来改变代码的逻辑,你也可以看看我上面给你的默认strategy属性文件,就可以知道Spring MVC给你加载了哪些默认的HandlerAdapter对象。只要你理解了我上面initHandlerMappings方法的分析,这个方法就没有什么可说的了,和它一样。

InitHandlerExceptionResolvers 方法

这个方法中初始化的对象都为HandlerExceptionResolver的子类,对于Controller中出现的异常,会调用processHandlerException方法来统一处理异常。稍后我会详细介绍这些对象在处理异常时扮演的角色,大家现在有个印象就行。还有一点大家应该注意的就是,如果你自己并没有定义一个处理HandlerExceptionResolver的子类来处理异常,即使Spring MVC给你加载了默认的子类,它们也不会帮你处理异常的。这个方法的执行逻辑和上面的一样,这里我也就不多说了。

initViewResolvers 方法

逻辑依然和上面的初始化方法一样,你可以实现ViewResolver
接口来定义自己的解析视图的方法。如果你并没有指定自己的类,那么默认的类为 InternalResourceViewResolver.

DispatcherServlet如何“统领指挥”?

上面的初始化方法都是为真正的“战役”做准备的。在这一小节中,我会带大家看看DispatcherServlet是如何调动“千军万马”来打仗(处理来自客户端的请求)的。正如它的名字一样,DispatcherServlet也是一个Servlet,它间接继承自HttpServlet ,它也重写了doService方法。当从客户端发出一个请求时,它会首先执行它的doService方法,如果大家去看看这个方法,它其实就是在requset域中发布一些属性,然后调用doDispatch方法。这个方法才是实际做事情的方法。下面,来让我们看看doDispatch方法的真面目吧。由于这个方法中涉及到的方法很多并且它本身的方法也很长,因此在这一小节中我不会整段整段的复制代码,而是抽出重要的代码片段,对于一些方法来说,我也会去除掉一些没有用的代码,比如记录日志的代码。因此我建议大家打开自己的源码结合着下文一起看。

找到当前请求的handler

从下面的代码中可以看到,doDispatch方法中调用getHandler方法找到相应请求的Handler,奇怪的是,返回的是一个HandlerExecutionChain对象,其实它很好理解,只不过是框架把找到的Handler(即我们处理请求的Controller)和一些个拦截器包装到这个对象中。

HandlerExecutionChain mappedHandler = null;

mappedHandler = getHandler(processedRequest, false);

/**
* Return the HandlerExecutionChain for this request.
* <p>Tries all handler mappings in order.
* @param request current HTTP request
* @return the HandlerExecutionChain, or <code>null</code> if no handler could be found
*/
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}

上面代码中的getHandler方法实际就是遍历你上面初始化的HandlerMapping对象,然后用其找到相应的Handler,大家可以看到它的返回值实际是HandlerExecutionChain对象,如果大家继续Debug程序,它其实会把你定义的Handler和拦截器包装起来,一并返回。还有一点我想强调的是,每个HandlerMapping找到Controller的方式不同,如果大家自己跟踪一下断点,发现无论哪个HandlerMapping最终都会到AbstractUrlHandlerMapping类中的lookupHandler方法,这个类中其实还有个私有变量handlerMap,这个变量在容器启动的时候,Spring MVC已经把URL作为key,对应的Controller作为value存入到这个变量中。下面我给大家举个例子,假设现在我们定义的HandlerMapping为DefaultAnnotationHandlerMapping对象,下面我来介绍一下它映射URL到Controller大致的流程。
* 当Servlet容器启动时,Spring MVC会实例化你所有定义的Bean,当然这绝对包括DefaultAnnotationHandlerMapping对象在实例化的过程中,它会调用AbstractDetectingUrlHandlerMapping中的detectHandlers方法,这个方法中会遍历你所有的Bean对象,甚至都会包括DefaultAnnotationHandlerMapping对象,从而找到URL以及相对应的Handler
* 在第2步中的detectHandlers方法中有一个determineUrlsForHandler方法,这个方法有主要作用是为给定的bean找到相应的URL,这个方法是抽象的,因此它会分派给具体的子类做这件事情,因为我用的是DefaultAnnotationHandlerMapping对象,所以会调用它里面的determineUrlsForHandler方法DefaultAnnotationHandlerMapping中的 determineUrlsForHandler
方法首先会查看你给定的bean对象上是否有RequestMapping
注解,然后会调用determineUrlsForHandlerMethods
方法查找你这个bean中的方法上是否有RequestMapping
注解,如果在第4步中得到了一个URL和Controller的映射,那么AbstractDetectingUrlHandlerMapping中的detectHandlers方法会调用AbstractUrlHandlerMapping中的 registerHandler方法,把这个映射会放入它的域变量handlerMap中

从上面的步骤中可以看出,Spring MVC在容器启动完毕以后就已经把所有的URL和Controller的映射放入到AbstractUrlHandlerMapping中的handlerMap域变量中,当请求到来时,它用URL当作key来取得对应的Controller就行了。BeanNameUrlHandlerMappingDefaultAnnotationHandlerMapping是同样的道理,只不过它们的determineUrlsForHandler方法不同。

SimpleUrlHandlerMapping这个类有点和它们不一样,它是基于配置把URL与Controller对应起来,而不是用determineUrlsForHandler方法来获取映射。剩下的步骤就一样了,用它里面的registerHandlers方法把这些映射注册到它的父类AbstractUrlHandlerMapping中。还有一些其它的HandlerMapping都是大同小异,这里我就不过多解释了。

找到HandlerAdapter

上面已经根据URL找到相应的Handler,接下来我们需要找到与当前Handler相匹配的HandlerAdapter来调用方法处理请求。大家可能会想,我们已经找到了相应的Handler,直接调用它里面的方法处理请求不就行了吗?这样做是肯定不行的。如果大家对Spring MVC熟悉的话,就会知道定义一个Controller可以有要多种多样的方式。比如,我们可以实现Controller
接口,也可以用注解的方式来定义Controller, 因此不同的定义方式会导致不同的调用方式。现在让我们来看看源码是怎么做的吧。

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

/**
* Return the HandlerAdapter for this handler object.
* @param handler the handler object to find an adapter for
* @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
*/
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (ha.supports(handler)) {
return ha;
}
}
}

源码看起来很简单,调用getHandlerAdapter方法就可以得到一个HandlerAdapter, 但是实际上并没有这么简单。这个方法就是遍历集合中的HandlerAdapter, 找到支持当前Handler的一个HandlerAdapter,那么怎么才算支持呢?实际上不同的HandlerAdapter所支持的方式不一样。下面,我拿出2个(AnnotationMethodHandlerAdapter
SimpleControllerHandlerAdapter)大家最熟悉的HandlerAdapter的子类来看看到底哪里不一样?如果是SimpleControllerHandlerAdapter,这个就很简单了,它的supports方法只是去看看当前的Handler是否为Controller
接口的实例,如果是就支持。如果是AnnotationMethodHandlerAdapter,这个就有点复杂了。下面是具体的源码。

private final Map<Class<?>, ServletHandlerMethodResolver> methodResolverCache =
new ConcurrentHashMap<Class<?>, ServletHandlerMethodResolver>();

public boolean supports(Object handler) {
return getMethodResolver(handler).hasHandlerMethods();
}

/**
* Build a HandlerMethodResolver for the given handler type.
*/
private ServletHandlerMethodResolver getMethodResolver(Object handler) {
Class handlerClass = ClassUtils.getUserClass(handler);
ServletHandlerMethodResolver resolver = this.methodResolverCache.get(handlerClass);
if (resolver == null) {
synchronized (this.methodResolverCache) {
resolver = this.methodResolverCache.get(handlerClass);
if (resolver == null) {
resolver = new ServletHandlerMethodResolver(handlerClass);
this.methodResolverCache.put(handlerClass, resolver);
}
}
}
return resolver;
}

在理解上面的代码之前,让我们先看看ServletHandlerMethodResolverHandlerMethodResolver这2个类。ServletHandlerMethodResolver是 AnnotationMethodHandlerAdapter的内部类,它继承了HandlerMethodResolver。在实例化ServletHandlerMethodResolver的同时,它会调用HandlerMethodResolver中的init()方法,在这个init()方法中会解析所有带有RequestMapping注解的方法,并把它存入到它的域变量handlerMethods中。 每个Handler都对应着一个ServletHandlerMethodResolver实例,这个实例中包含着一切关于当前Handler中的方法信息。
那么上面代码中的supports方法首先做的就是调用getMethodResolver方法,用一个Handler实例去获取其对应的一ServletHandlerMethodResolver
实例,如果存在这个实例,直接返回,如果不存在,新建一个实例,并把它放入到Map缓存中。P.S. getMethodResolver方法的同步代码写的很漂亮,直得学习。
总结来说,当第一次用到Handler中的方法处理请求时,它会一次性解析里面会用到的方法,存到一个ServletHandlerMethodResolver实例(当然了,这个实例不仅仅只有这些信息),然后用这个Handler类作为key,用ServletHandlerMethodResolver实例作为value存入到Map缓存中,等到下一个请求再一次用到这个Handler中的方法时,它直接从这个缓存中取得相应的信息就ok了。

应用注册拦截器的preHandle方法

这一步没有什么好说的,大家自己看看下面的代码就全明白了。

int interceptorIndex = -1;

// Apply preHandle methods of registered interceptors.
HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
if (interceptors != null) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
return;
}
interceptorIndex = i;
}
}

实际上,除了我们自己的拦截器外,Spring MVC还给了一个拦截器为AbstractUrlHandlerMapping$PathExposingHandlerInterceptor

调用处理请求的方法

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

上面的代码用相应的HandlerAdapter来调用Handler来处理请求了并返回一个ModelAndView对象。不同的HandlerAdapter调用方式也不相同,对于上面我介绍的SimpleControllerHandlerAdapter来说,它只是把Handler强转成了一个Controller,然后调用handleRequest方法就行了。 对于AnnotationMethodHandlerAdapter来说,如果你能理解上面“找到HandlerAdapter”的过程,相信这个也难不倒你。

ModelAndView是什么呢?它仅仅是一个容器存储Model 和 View,它们是完全不相同的东西,这所以这样做的原因就是在Controller
当中可以用一个返回值同时返回Model 和 View,下面我来举个例子。

@Controller
public class HelloWorldController {
@RequestMapping("/hello")
public ModelAndView helloWorld() {
String message = "Hello Spring MVC";
return new ModelAndView("index.jsp", "info", message);
}
}

上面是我Controller中的代码,它返回的View名称为index.jsp, 而Model为{info=Hello Spring MVC}

应用注册拦截器的postHandle方法

这一步也没有什么好说的,大家自己看看下面的代码就全明白了。

// Apply postHandle methods of registered interceptors.
if (interceptors != null) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);
}
}

在postHandle方法中,我们可以操纵从Controller中返回的ModelAndView对象,你可以替换它,清空它,向里面加入属性等。

渲染ModelAndView

如果ModelAndView
对象不为空,并且没有调用clear
方法清空它,那么接下来它就会被渲染。

if (mv != null && !mv.wasCleared()) {
render(mv, processedRequest, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}

上面的render方法会从给定的ModelAndView对象中解析出一个View对象,然后就调用了render方法,这个方法是要属于AbstractView类的。如果大家看一下这个类的子类,你会发现各种各样的View对象,实在是太多了,比如:FreeMarkerView VelocityView InternalResourceView. AbstractView
类中的render方法的目的就是用给定的Model来预处理View对象,把静态属性和request域中的属性合并到模型当中,最后把这个合并过后的模型传递到具体子类的renderMergedOutputModel方法中,进行渲染。

应用注册拦截器的triggerAfterCompletion方法

到达这个阶段,整个doDispatch
方法已经全部完毕了。

triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);

异常处理

在上文中提到的initHandlerExceptionResolvers
方法中,我已经提到了关于统一异常的处理。原理就在下面的代码之中。

catch (ModelAndViewDefiningException ex) {
logger.debug("ModelAndViewDefiningException encountered", ex);
mv = ex.getModelAndView();
}
catch (Exception ex) {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(processedRequest, response, handler, ex);
errorView = (mv != null);
}

上面的代码中有2个异常,一个是ModelAndViewDefiningException,对于这个异常来说,如果在你的Controller中抛出了这个异常并给定相应的ModelAndView, 它依然会在下面的代码中解析这个返回的ModelAndView
对象,如果没有指定ModelAndView,那么程序会出错。对于其它的异常来说,都会被Exception所捕获,接着会用processHandlerException方法去处理调用我们自己定义的异常处理方法。

总结

至此,我已经把Spring MVC工作的细节以源码的形式说完了。如果Spring MVC是个黒盒,那么这篇文章已经为大家打开了盒子并看到了里面主要的一些东西。这篇文章也为大家探索Spring MVC开了个好头,它让我们的开发者更进一步地了解了Spring MVC,而不仅仅是单纯地使用它。Spring MVC还有很多细节、优秀地设计思想以及漂亮地编码风格值得我们去探索和学习。下图是我Google一张关于Spring MVC的流程图,画得很详细,供大家参考。
spring MVC 流程图图片来源:http://www.programering.com/a/MDMyETNwATM.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
弃用了struts,用spring mvc框架做了几个项目,感觉都不错,而且使用了注解方式,可以省掉一大堆配置文件。本文主要介绍使用注解方式配置的spring mvc,之前写的spring3.0 mvc和rest小例子没有介绍到数据层的内容,现在这一篇补上。下面开始贴代码。 文中用的框架版本:spring 3,hibernate 3,没有的,自己上网下。 先说web.xml配置: [java] view plaincopy 01.<?xml version="1.0" encoding="UTF-8"?> 02.<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> 03. <display-name>s3h3</display-name> 04. <context-param> 05. <param-name>contextConfigLocation</param-name> 06. <param-value>classpath:applicationContext*.xml</param-value> 07. </context-param> 08. <listener> 09. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 10. </listener> 11. 12. <servlet> 13. <servlet-name>spring</servlet-name> 14. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 15. <load-on-startup>1</load-on-startup> 16. </servlet> 17. <servlet-mapping> 18. <servlet-name>spring</servlet-name> <!-- 这里在配成spring,下边也要写一个名为spring-servlet.xml的文件,主要用来配置它的controller --> 19. <url-pattern>*.do</url-pattern> 20. </servlet-mapping> 21. <welcome-file-list> 22. <welcome-file>index.jsp</welcome-file> 23. </welcome-file-list> 24.</web-app> spring-servlet,主要配置controller的信息 [java] view plaincopy 01.<?xml version="1.0" encoding="UTF-8"?> 02. <beans 03. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" 04. xmlns:context="http://www.springframework.org/schema/context" 05. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 06. http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd 07. http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd 08. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> 09. 10. <context:annotation-config /> 11. <!-- 把标记了@Controller注解的类转换为bean --> 12. <context:component-scan base-package="com.mvc.controller" /> 13. <!-- 启动Spring MVC的注解功能,完成请求和注解POJO的映射 --> 14. <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" /> 15. 16. <!-- 对模型视图名称的解析,即在模型视图名称添加前后缀 --> 17. <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" 18. p:prefix="/WEB-INF/view/" p:suffix=".jsp" /> 19. 20. <bean id="multipartResolver" 21. class="org.springframework.web.multipart.commons.CommonsMultipartResolver" 22. p:defaultEncoding="utf-8" /> 23. </beans> applicationContext.xml代码 [java] view plaincopy 01.<?xml version="1.0" encoding="UTF-8"?> 02.<beans 03. xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" 04. xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx" 05. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 06. xsi:schemaLocation=" 07. http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 08. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd 09. http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd 10. http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> 11. 12. <context:annotation-config /> 13. <context:component-scan base-package="com.mvc" /> <!-- 自动扫描所有注解该路径 --> 14. 15. <context:property-placeholder location="classpath:/hibernate.properties" /> 16. 17. <bean id="sessionFactory" 18. class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> 19. <property name="dataSource" ref="dataSource" /> 20. <property name="hibernateProperties"> 21. <props> 22. <prop key="hibernate.dialect">${dataSource.dialect}</prop> 23. <prop key="hibernate.hbm2ddl.auto">${dataSource.hbm2ddl.auto}</prop> 24. <prop key="hibernate.hbm2ddl.auto">update</prop> 25. </props> 26. </property> 27. <property name="packagesToScan"> 28. <list> 29. <value>com.mvc.entity</value><!-- 扫描实体类,也就是平时所说的model --> 30. </list> 31. </property> 32. </bean> 33. 34. <bean id="transactionManager" 35. class="org.springframework.orm.hibernate3.HibernateTransactionManager"> 36. <property name="sessionFactory" ref="sessionFactory" /> 37. <property name="dataSource" ref="dataSource" /> 38. </bean> 39. 40. <bean id="dataSource" 41. class="org.springframework.jdbc.datasource.DriverManagerDataSource"> 42. <property name="driverClassName" value="${dataSource.driverClassName}" /> 43. <property name="url" value="${dataSource.url}" /> 44. <property name="username" value="${dataSource.username}" /> 45. <property name="password" value="${dataSource.password}" /> 46. </bean> 47. <!-- Dao的实现 --> 48. <bean id="entityDao" class="com.mvc.dao.EntityDaoImpl"> 49. <property name="sessionFactory" ref="sessionFactory" /> 50. </bean> 51. <tx:annotation-driven transaction-manager="transactionManager" /> 52. <tx:annotation-driven mode="aspectj"/> 53. 54. <aop:aspectj-autoproxy/> 55.</beans> hibernate.properties数据库连接配置 [java] view plaincopy 01.dataSource.password=123 02.dataSource.username=root 03.dataSource.databaseName=test 04.dataSource.driverClassName=com.mysql.jdbc.Driver 05.dataSource.dialect=org.hibernate.dialect.MySQL5Dialect 06.dataSource.serverName=localhost:3306 07.dataSource.url=jdbc:mysql://localhost:3306/test 08.dataSource.properties=user=${dataSource.username};databaseName=${dataSource.databaseName};serverName=${dataSource.serverName};password=${dataSource.password} 09.dataSource.hbm2ddl.auto=update 配置已经完成,下面开始例子 先在数据库建表,例子用的是mysql数据库 [java] view plaincopy 01.CREATE TABLE `test`.`student` ( 02. `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 03. `name` varchar(45) NOT NULL, 04. `psw` varchar(45) NOT NULL, 05. PRIMARY KEY (`id`) 06.) 建好表后,生成实体类 [java] view plaincopy 01.package com.mvc.entity; 02. 03.import java.io.Serializable; 04. 05.import javax.persistence.Basic; 06.import javax.persistence.Column; 07.import javax.persistence.Entity; 08.import javax.persistence.GeneratedValue; 09.import javax.persistence.GenerationType; 10.import javax.persistence.Id; 11.import javax.persistence.Table; 12. 13.@Entity 14.@Table(name = "student") 15.public class Student implements Serializable { 16. private static final long serialVersionUID = 1L; 17. @Id 18. @Basic(optional = false) 19. @GeneratedValue(strategy = GenerationType.IDENTITY) 20. @Column(name = "id", nullable = false) 21. private Integer id; 22. @Column(name = "name") 23. private String user; 24. @Column(name = "psw") 25. private String psw; 26. public Integer getId() { 27. return id; 28. } 29. public void setId(Integer id) { 30. this.id = id; 31. } 32. 33. public String getUser() { 34. return user; 35. } 36. public void setUser(String user) { 37. this.user = user; 38. } 39. public String getPsw() { 40. return psw; 41. } 42. public void setPsw(String psw) { 43. this.psw = psw; 44. } 45.} Dao层实现 [java] view plaincopy 01.package com.mvc.dao; 02. 03.import java.util.List; 04. 05.public interface EntityDao { 06. public List<Object> createQuery(final String queryString); 07. public Object save(final Object model); 08. public void update(final Object model); 09. public void delete(final Object model); 10.} [java] view plaincopy 01.package com.mvc.dao; 02. 03.import java.util.List; 04. 05.import org.hibernate.Query; 06.import org.springframework.orm.hibernate3.HibernateCallback; 07.import org.springframework.orm.hibernate3.support.HibernateDaoSupport; 08. 09.public class EntityDaoImpl extends HibernateDaoSupport implements EntityDao{ 10. public List<Object> createQuery(final String queryString) { 11. return (List<Object>) getHibernateTemplate().execute( 12. new HibernateCallback<Object>() { 13. public Object doInHibernate(org.hibernate.Session session) 14. throws org.hibernate.HibernateException { 15. Query query = session.createQuery(queryString); 16. List<Object> rows = query.list(); 17. return rows; 18. } 19. }); 20. } 21. public Object save(final Object model) { 22. return getHibernateTemplate().execute( 23. new HibernateCallback<Object>() { 24. public Object doInHibernate(org.hibernate.Session session) 25. throws org.hibernate.HibernateException { 26. session.save(model); 27. return null; 28. } 29. }); 30. } 31. public void update(final Object model) { 32. getHibernateTemplate().execute(new HibernateCallback<Object>() { 33. public Object doInHibernate(org.hibernate.Session session) 34. throws org.hibernate.HibernateException { 35. session.update(model); 36. return null; 37. } 38. }); 39. } 40. public void delete(final Object model) { 41. getHibernateTemplate().execute(new HibernateCallback<Object>() { 42. public Object doInHibernate(org.hibernate.Session session) 43. throws org.hibernate.HibernateException { 44. session.delete(model); 45. return null; 46. } 47. }); 48. } 49.} Dao在applicationContext.xml注入 <bean id="entityDao" class="com.mvc.dao.EntityDaoImpl"> <property name="sessionFactory" ref="sessionFactory" /> </bean> Dao只有一个类的实现,直接供其它service层调用,如果你想更换为其它的Dao实现,也只需修改这里的配置就行了。 开始写view页面,WEB-INF/view下新建页面student.jsp,WEB-INF/view这路径是在spring-servlet.xml文件配置的,你可以配置成其它,也可以多个路径。student.jsp代码 [xhtml] view plaincopy 01.<%@ page language="java" contentType="text/html; charset=UTF-8" 02. pageEncoding="UTF-8"%> 03.<%@ include file="/include/head.jsp"%> 04.<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 05.<html> 06.<head> 07.<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 08.<title>添加</title> 09.<mce:script language="javascript" src="<%=request.getContextPath()%><!-- 10./script/jquery.min.js"> 11.// --></mce:script> 12.<mce:style><!-- 13.table{ border-collapse:collapse; } 14.td{ border:1px solid #f00; } 15.--></mce:style><style mce_bogus="1">table{ border-collapse:collapse; } 16.td{ border:1px solid #f00; }</style> 17.<mce:script type="text/javascript"><!-- 18.function add(){ 19. [removed].href="<%=request.getContextPath() %>/student.do?method=add"; 20.} 21. 22.function del(id){ 23.$.ajax( { 24. type : "POST", 25. url : "<%=request.getContextPath()%>/student.do?method=del&id;=" + id, 26. dataType: "json", 27. success : function(data) { 28. if(data.del == "true"){ 29. alert("删除成功!"); 30. $("#" + id).remove(); 31. } 32. else{ 33. alert("删除失败!"); 34. } 35. }, 36. error :function(){ 37. alert("网络连接出错!"); 38. } 39.}); 40.} 41.// --></mce:script> 42.</head> 43.<body> 44. 45.<input id="add" type="button" value="添加"/> 46.<table > 47. <tr> 48. <td>序号</td> 49. <td>姓名</td> 50. <td>密码</td> 51. <td>操作</td> 52. </tr> 53. <c:forEach items="${list}" var="student"> 54. <tr id="<c:out value="${student.id}"/>"> 55. <td><c:out value="${student.id}"/></td> 56. <td><c:out value="${student.user}"/></td> 57. <td><c:out value="${student.psw}"/></td> 58. <td> 59. <input type="button" value="编辑"/> 60. <input type="button" value="${student.id}"/>')" value="删除"/> 61. </td> 62. </tr> 63. </c:forEach> 64. 65.</table> 66.</body> 67.</html> student_add.jsp [xhtml] view plaincopy 01.<%@ page language="java" contentType="text/html; charset=UTF-8" 02. pageEncoding="UTF-8"%> 03.<%@ include file="/include/head.jsp"%> 04.<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 05.<html> 06.<head> 07.<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 08.<title>学生添加</title> 09.<mce:script type="text/javascript"><!-- 10.function turnback(){ 11. [removed].href="<%=request.getContextPath() %>/student.do"; 12.} 13.// --></mce:script> 14.</head> 15.<body> 16.<form method="post" action="<%=request.getContextPath() %>/student.do?method=save"> 17.<div><c:out value="${addstate}"></c:out></div> 18.<table> 19. <tr><td>姓名</td><td><input id="user" name="user" type="text" /></td></tr> 20. <tr><td>密码</td><td><input id="psw" name="psw" type="text" /></td></tr> 21. <tr><td colSpan="2" align="center"><input type="submit" value="提交"/><input type="button" value="返回" /> </td></tr> 22.</table> 23. 24.</form> 25.</body> 26.</html> controller类实现,只需把注解写上,spring就会自动帮你找到相应的bean,相应的注解标记意义,不明白的,可以自己查下@Service,@Controller,@Entity等等的内容。 [java] view plaincopy 01.package com.mvc.controller; 02. 03.import java.util.List; 04. 05.import javax.servlet.http.HttpServletRequest; 06.import javax.servlet.http.HttpServletResponse; 07. 08.import org.apache.commons.logging.Log; 09.import org.apache.commons.logging.LogFactory; 10.import org.springframework.beans.factory.annotation.Autowired; 11.import org.springframework.stereotype.Controller; 12.import org.springframework.ui.ModelMap; 13.import org.springframework.web.bind.annotation.RequestMapping; 14.import org.springframework.web.bind.annotation.RequestMethod; 15.import org.springframework.web.bind.annotation.RequestParam; 16.import org.springframework.web.servlet.ModelAndView; 17. 18.import com.mvc.entity.Student; 19.import com.mvc.service.StudentService; 20. 21.@Controller 22.@RequestMapping("/student.do") 23.public class StudentController { 24. protected final transient Log log = LogFactory 25. .getLog(StudentController.class); 26. @Autowired 27. private StudentService studentService; 28. public StudentController(){ 29. 30. } 31. 32. @RequestMapping 33. public String load(ModelMap modelMap){ 34. List<Object> list = studentService.getStudentList(); 35. modelMap.put("list", list); 36. return "student"; 37. } 38. 39. @RequestMapping(params = "method=add") 40. public String add(HttpServletRequest request, ModelMap modelMap) throws Exception{ 41. return "student_add"; 42. } 43. 44. @RequestMapping(params = "method=save") 45. public String save(HttpServletRequest request, ModelMap modelMap){ 46. String user = request.getParameter("user"); 47. String psw = request.getParameter("psw"); 48. Student st = new Student(); 49. st.setUser(user); 50. st.setPsw(psw); 51. try{ 52. studentService.save(st); 53. modelMap.put("addstate", "添加成功"); 54. } 55. catch(Exception e){ 56. log.error(e.getMessage()); 57. modelMap.put("addstate", "添加失败"); 58. } 59. 60. return "student_add"; 61. } 62. 63. @RequestMapping(params = "method=del") 64. public void del(@RequestParam("id") String id, HttpServletResponse response){ 65. try{ 66. Student st = new Student(); 67. st.setId(Integer.valueOf(id)); 68. studentService.delete(st); 69. response.getWriter().print("{/"del/":/"true/"}"); 70. } 71. catch(Exception e){ 72. log.error(e.getMessage()); 73. e.printStackTrace(); 74. } 75. } 76.} service类实现 [java] view plaincopy 01.package com.mvc.service; 02. 03.import java.util.List; 04. 05.import org.springframework.beans.factory.annotation.Autowired; 06.import org.springframework.stereotype.Service; 07.import org.springframework.transaction.annotation.Transactional; 08. 09.import com.mvc.dao.EntityDao; 10.import com.mvc.entity.Student; 11. 12.@Service 13.public class StudentService { 14. @Autowired 15. private EntityDao entityDao; 16. 17. @Transactional 18. public List<Object> getStudentList(){ 19. StringBuffer sff = new StringBuffer(); 20. sff.append("select a from ").append(Student.class.getSimpleName()).append(" a "); 21. List<Object> list = entityDao.createQuery(sff.toString()); 22. return list; 23. } 24. 25. public void save(Student st){ 26. entityDao.save(st); 27. } 28. public void delete(Object obj){ 29. entityDao.delete(obj); 30. } 31.} OK,例子写完。有其它业务内容,只需直接新建view,并实现相应comtroller和service就行了,配置和dao层的内容基本不变,也就是每次只需写jsp(view),controller和service调用dao就行了。 怎样,看了这个,spring mvc是不是比ssh实现更方便灵活。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值