Spring MVC学习详解

来源:互联网 发布:淘宝详情页模板怎么做 编辑:程序博客网 时间:2024/06/06 02:45

介绍一下Spring MVC的内容。
Spring提供了构建Web应用程序的全功能MVC模块。通过策略接口,Spring高度配置,且支持多种视图技术。例如JSP,Velocity,Tiles,iText和POI等。
Spring MVC功能的实现是基于Servlet功能实现的。也就是通过实现Servlet接口的DispatcherServlet来封装核心功能的实现。如果应用程序需要处理用户输入表单,可以继承AbstractFormController,如果需要把多页输入处理到同一个表单,可以继承
AbstractWizardFormController类。
下面就来简单介绍一些Spring MVC的使用方式。
1.配置web.xml。

Default WebApplicationContext implementation class for ContextLoader.

Used as fallback when no explicit context implementation has been specified as context-param.

Not meant to be customized by application developers.

org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
3.将实例记录在servletContext中。
4.映射当前的类加载器和创建的实例到全局变量currentContextPerThread中。

在Spring MVC中,ContextLoaderListener只是辅助功能,用于创建WebApplicationContext类型的实例,真正MVC的逻辑是实现在DispatcherServlet里面的。查看该类的继承结构发现,该类实现了Servlet接口,如下所示:

这里先来回顾一下servlet的生命周期:
1.初始化阶段。
servlet容器加载servlet类,把servlet类的class文件中的数据读到内存中。容器创建一个ServletConfig对象。容器创建一个servlet对象,调用servlet的方法进行初始化。
2.运行阶段。
当servlet容器接收到一个请求时,会针对这个请求创建servletRequest和servletResponse对象,然后调用service对象。生成完相应结果之后,然后销毁servletRequest和servletResponse对象。
3.销毁阶段。
Web容器被终止时,容器会先调用servlet的destroy方法,然后再销毁servlet对象,同事也会销毁改servlet对应的ServletConfig对象。
servlet框架是由javax.servlet和javax.servlet.http两个包构成的。
Servlet框架被设计成请求驱动,HTTP包含的方法为delete,get,options,put,post,和trace。分别提供了对应的服务方法。
由上面的介绍可以知道,service初始化阶段会调用其init方法,首先找到该方法的实现。该方法在HttpServletBean中实现,代码如下:

主要过程就是将当前的Servlet实例转化为BeanWrapper,然后就可以使用Spring提供的方式进行对属性的注入。
这些属性包含在FrameworkServlet类中。属性注入主要包含以下几个方面。
1.封装以及验证初始化参数。
ServletConfigPropertyValues除了封装属性之外还有对属性验证的功能。

2.将当前servlet实例转化为BeanWrapper实例。
通过调用PropertyAccessorFactory.forBeanPropertyAccess方法将当前实例转化为BeanWrapper,具体的BeanWrapper实现类为BeanWrapperImpl类。
3.注册相对于Resource的属性编辑器。
也就是用ResourceEditor去解析Resource类型的属性。
4.属性注入。
5.servletBean的初始化。
在ContextLoaderListener中已经创建了WebApplicationContext实例,并且在initServletBean方法中重要的就是对这个实例进行进一步的补充初始化。该方法写在FrameworkServlet类中。

初始化的逻辑委托给了initWebApplicationContext方法。代码如下:

上面函数中实现的主要功能有
5.1.寻找或创建对应的WebApplicationContext实例。主要包括以下几个步骤。
(1).通过构造函数的注入进行初始化。
(2).通过contextAttribute进行初始化。
(3).重新创建WebApplicationContext实例。
如果上面两种方式否没有找到WebApplicationContext实例,此时执行重新创建WebApplicationContext实例的方法:

这里面有一个方法configureAndRefreshWebApplicationContext,代码如下:

该方法的功能是对已经创建的WebApplicationContext实例进行配置以及刷新。最后调用父类的AbstractApplicationContext的refresh进行配置文件加载。
继续回到上面的initWebApplicationContext方法,前面我们把createWebApplicationContext方法分析完了,继续分析后面的代码,能看到调用了onRefresh方法。该方法在DispatcherServlet类中进行了重写。

这里完成的过程主要是:
(1).初始化MultipartResolver。
(2).初始化LocaleResolver。
(3).初始化ThemeResolver。
(4).初始化HandlerMappings。
(5).初始化HandlerAdapters。
(6).初始化HandlerExceptionResolvers。
(7).初始化RequestToViewNameTranslator。
(8).初始化ViewResolvers。
(9).初始化FlashMapManager。
下面就来详细介绍一下这里面的9个初始化操作。
(1).初始化MultipartResolver。
在Spring中,MultipartResolver主要用来处理文件上传,默认情况下,Spring是没有Multipart的处理的,如果要使用Spring的Multipart,就需要在Web的上下文中添加Multipart解析器。比如常见的配置如下:


1000000


initMultipartResolver方法的代码如下:

按照刚才的说明,之前已经存在了Spring中配置文件的解析,这里只需要使用ApplicationContext的getBean就可以获取对应的bean。进而初始化MultipartResolver中的变量。
(2).初始化LocaleResolver。
在Spring中的国际化配置一般有三种使用方式。
基于URL参数的配置。通过URL参数来控制国际化。比如在页面上加一句
简体中文
来控制项目中使用的国际化参数。而提供这个功能的就是AcceptHeaderLocaleResolver累,默认的参数名为locale。
第二种就是基于session的配置。具体的类为SessionLocaleResolver。
第三种就是基于Cookie的配置。具体的类为CookieLocaleResolver。
initLocaleResolver具体的代码为

和上面的initMultipartResolver方法类似。
(3).初始化ThemeResolver。
在Web开发中经常会遇到主题Theme来控制网页风格,这将进一步改善用户体验,简单说就是一个主题就是一组静态资源,可以影响应用程序的视觉效果。Spring中的主题功能和国际化功能非常相似,构成Spring主题功能主要包括如下内容。
(3.1).主题资源。
Spring中的ThemeSource是Spring中主题资源的接口,Spring的主题需要通过ThemeSource接口来实现存放主题信息的资源。
ResourceBundleThemeSource是ThemeSource接口默认实现类,在Spring中的配置如下:

默认状态下是在类路径根目录下查找相应的资源文件,也可以通过beasenamePrefix指定查找资源文件的包路径。
(3.2).主题解析器。
ThemeSource定义了一些主题资源,ThemeResolver是主题解析器的接口,主题解析的工作则是其子类完成的。
对于主题解析器,主要有三个比较常见的实现,以主题summer.properties为例。
FixedThemeResolver:用于选择一个固定的主题。

CookieThemeResolver:用于实现用户所选的主题,以Cookie的方式存放在客户端的机器上。

SessionThemeResolver:用户主题保存在用户的HTTP Session中。

以上配置用于设置主题名称,并将该名称保存在用户的HttpSession中。
另外,对于FixedThemeResolver和SessionThemeResolver,共同继承了AbstractThemeResolver类。用户也可以自己实现自己的解析器继承AbstractThemeResolver类。
(3.3).拦截器。
如果需要根据用户请求来噶变主题,那么Spring提供了一个已经实现的拦截器ThemeChangeInterceptor拦截器,具体的配置如下:

其中设置用户请求参数名为themeName,即URL为?themeName=具体的主题名称。此外还需要在handlerMapping中配置拦截器。

下面来看一下initThemeResolver的方法具体内容:

(4).初始化HandlerMappings。
当客户端发出的Request时,DispatcherServlet会将Request提交给HandlerMappings,然后HandlerMappings根据WeApplicationContext的配置回传给DispatcherServlet相应的Controller。
在基于Spring MVC的Web应用程序中,我们可以为DispatcherServlet提供多个HandlerMapping工供其使用,DispatcherServlet在选用HandlerMappings的过程中,将根据我们所指定的一系列HandlerMapping的优先级进行排序,然后优先使用在前面的HandlerMapping。如果当前的HandlerMapping能够返回可用的Handler,DispatcherServlet则使用当前返回的Handler进行Web请求的处理,而不再继续询问其他的HandlerMapping。
initHandlerMappings方法的代码如下:

默认情况下,Spring MVC将加载当前系统中所有实现了HandlerMapping接口的bean。如果只期望Spring MVC加载指定的HandlerMapping,可以修改web.xml中的DispatcherServlet的初始化参数,将detectAllHandlerMappings设置为false:

此时,Spring MVC将查找名为handleMapping的bean,并作为当前系统中唯一的HandlerMapping。如果没有定义HandlerMapping的话,则Spring MVC将按照DispatcherServlet所在目录下的DispatcherServlet.properties文件中所定义的HandlerMapping的内容来加载默认的handlerMapping。
(5).初始化HandlerAdapters。
望文知意,此处是适配器模式的应用。先来看一下initHandlerAdapters方法的代码:

在初始化的过程中使用到了一个变量,detectAllHandlerAdapters,该参数的作用和detectAllHandlerMappings类似,可以在配置文件中指定detectAllHandlerAdapters的值,来强制系统只加载bean name为“handlerAapter”的handlerAdapter。

如果无法找到对应的bean,系统尝试加载默认的适配器。调用的方法为getDefaultStrategies:

在getDefaultStrategies方法中,Spring会尝试从defaultStrategies中加载对应的HandlerAdapter属性,该参数的初始化过程就在DispatcherServlet中的初始化静态块中:

查看DispatcherServlet.properties文件中对应HandlerAdapter的内容:
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
也就会默认会加载这三个适配器。
下面来介绍一下这三个适配器。
HttpRequestHandlerAdapter:HTTP请求处理器适配器,该适配器仅支持对HTTP请求处理器的适配,简单的将HTTP请求对象和响应对象传递给HTTP请求处理器的实现。并且不需要返回值。主要应用在基于HTTP的远程调用的实现上。
SimpleControllerHandlerAdapter:简单控制器处理器适配器,客户化的业务逻辑通常是在控制器接口中的实现类中实现的。
AnnotationMethodHandlerAdapter:注解方法处理器适配器。这个类的实现是基于注解的实现,需要结合注解方法映射和注解方法处理协同工作。通过反射来发现探测处理器方法的参数,调用处理器方法,并且映射返回值到模型和控制器对象。
(6).初始化HandlerExceptionResolvers。
基于HandlerExceptionResolvers接口的异常处理,使用这种方式只需要实现HandlerExceptionResolvers方法。该方法返回一个ModelAndView对象,如果返回为null,则Spring会继续寻找其他的实现了HandlerExceptionResolvers接口的bean,换句话说,Spring会搜索所有注册在其环境中的实现了HandlerExceptionResolvers接口的bean,逐个执行,直到返回了一个ModelAndView对象。
初始化的代码如下:

(7).初始化RequestToViewNameTranslator。
在Controller处理器方法没有返回一个View对象或者逻辑视图名称,Spring就会采用约定好的方式提供一个逻辑视图名称。这个逻辑视图名称是通过Spring定义的RequestToViewNameTranslator接口的getViewName方法来实现的。我们也可以实现自己的RequestToViewNameTranslator接口的getViewName方法来约定没有返回视图名称的时候如何确定视图名称。Spring中的默认实现是DefaultRequestToViewNameTranslator。该类的配置信息主要有:
prefix:前缀,表示约定好的视图名称前加上的前缀,默认是空串。
suffix:后缀,如上的后缀。
separator:分隔符。默认是“/”。
stripLeadingSlash:如果首字母是分隔符,是否要去除,默认是true。
stripTrailingSlash:上面的判断尾字母的选项,默认为true。
stripExtension:如果请求路径包含扩展名,是否要去除,默认为true。
urlDecode:该参数在UrlPathHelper变量中。默认为true。是否需要对URL解码。
接下来看一下DefaultRequestToViewNameTranslator的处理过程。比如请求路径为http://localhost/app/test/index.html,改请求对应的URI为/test/index.html。
如果prefix和suffix都存在,则对应返回的视图名应该是prefix+test/index+suffix。
stripLeadingSlash和stripTrailingSlash都为false,这时候对应的应该是/test/index.html。
如果都采用默认值,返回的是test/index。
这些操作是在initRequestToViewNameTranslator方法中完成的。

(8).初始化ViewResolvers。
在Spring MVC中,当Controller将请求处理结果放入到ModelAndView中以后,DispatcherServlet会根据合适的ModelAndView选择合适的视图进行渲染。Spring MVC如何选择合适的View,View对象的创建就在ViewResolver中。
该接口定义了一个方法resolveViewName,根据viewName创建合适的View实现。
ViewResolver的配置如下,即作为Spring中的Bean存在:

ViewResolvers的初始化工作在initViewResolvers方法中完成:

(9).初始化FlashMapManager。
Spring MVC Flash attributes提供了一个请求存储属性,可供其他请求使用,在使用重定向的时候非常必要,例如Post/Get/Rediret模式,Flash attributes在重定向之前暂存以便重定向之后还能使用,并立即删除。
初始化的方法initFlashMapManager代码如下:

下面就重点来介绍一下DispatcherServlet的逻辑处理。
根据Servlet的规范,我们知道,Servlet处理Servlet请求最关键的方法就是service方法。DispatcherServlet的父类FrameworkServlet中有对service方法的实现:

由此可见,service方法将处理逻辑委托给了processRequest方法,该方法在FrameworkServlet类中:

主要过程如下:
(1).为了保证当前线程的LocaleContext以及RequestAttributes可以砸当前请求后还能恢复,提取当前线程的两个属性。
(2).根据当前request创建对应的LocaleContext和RequestAttributes,并绑定到当前线程。
(3).委托给doService方法进一步处理。
(4).请求处理结束后恢复线程到原始状态。
(5).请求处理结束后无论成功与否发布事件通知。
首先来看一下最重要的doService方法中的逻辑,doService方法在DispatcherServlet类中实现:

但是在doService方法中依然是一些准备工作,这里会把已经初始化好的辅助工具变量比如localeResolver,themeResolver等设置在request中。
最后来到了doDispatch方法中:

下面就来梳理一下这个方法的处理逻辑:
1.MultipartContent类型的request处理。
对于请求的处理,Spring首先考虑的是Multipart的处理,如果是MultipartContent类型的request,就转换为MultipartHttpServletRequest类型的request,具体代码为checkMultipart方法:

2.根据request信息寻找对应的Handler。
在Spring中最简单的映射处理器配置如下:

Spring加载的过程中,会将类型为SimpleUrlHandlerMapping的实例加载到this.handlerMappings变量中。
先来看一下getHandler的处理逻辑:

继续来到AbstractHandlerMapping类中的getHandler方法:

上面的逻辑主要是,首先会使用getHandlerInternal方法根据request信息获取对应的Handler,如果没有对应的handler则使用默认的handler。
2.1.先来看一下getHandlerInternal方法的实现逻辑,如果以SimpleUrlHandlerMapping为例的话,该类的继承结构图如下:

getHandlerInternal方法实现在AbstractUrlHandlerMapping类中:

首先根据request查找对应的Handler开始分析,先截取用于匹配的url有效路径,然后根据路径寻找Handler,如果请求路径仅仅是“/”,则使用RootHandler进行处理,无法找到就使用默认的handler,然后根据beanName获取对应的bean,接下来就是模板方法。
先来看一下截取有效路径的方法:

截取到需要的url之后,就去寻找相应的Handler。来看一下lookupHandler方法的实现。

需要处理的情况有,直接匹配的情况,通配符匹配的情况,找到之后就是调用buildPathExposingHandler方法,

该方法将Handler封装成HandlerExecutionChain对象,并且在这里加入了两个拦截器。也就是链处理机制,是Spring中非常常用的处理方式,也是AOP的重要组成部分,可以方便的对目标对象进行扩展。
接下来回到getHandler方法中,根据request信息获取到handler对象之后,就调用getHandlerExecutionChain方法,将配置中的对应拦截器加入到执行链中,以保证这些拦截器可以有效的作用于目标对象。

至此,我们把doDispatch中的
mappedHandler = getHandler(processedRequest, false);
代码分析完了。
3.如果上面的过程依然没有找到对应的handler,处理过程为:

就是直接通过response向用户返回错误信息。
4.根据当前handler寻找对应的HandlerAdapter。
调用代码为:
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
getHandlerAdapter方法的实现如下所示:

主要就是通过遍历所有的适配器来选择合适的适配器返回。判断依据就是supports方法:
查看SimpleControllerHandlerAdapter类里面的supports方法实现:

也就是说对于Spring MVC来说,会把逻辑封装至Controller中。
5.缓存处理。
在介绍缓存处理的支持之前,先来了解一个概念,Last-Modified缓存机制。
(5.1).在客户端第一次输入URL之后,服务端会返回内容和状态码200,表示请求成功,同时会添加一个Last-Modified的响应头,表示i文件在服务器上的最后更新时间。比如说在浏览器中输入https://www.baidu.com/img/bdlogo.png获取百度首页的log页面。
网络的响应情况为:

服务器的返回“Last-Modified: Fri, 01 Aug 2014 11:57:57 GMT”就是表示最后的变更时间。
(5.2).客户端第二次请求次URL的时候,客户端会向服务端发送请求头“If-Modified-Since: Fri, 01 Aug 2014 11:57:57 GMT ”,如果服务端的内容没有变化,则自动返回304响应头,只有响应头,内容为空,就节省了网络带宽。

同样,Spring提供的对LastModified机制的支持,只需要实现LastModified接口,如下所示:
public class DemoLastModifiedController extends AbstractController implements LastModified {

private long lastModified;
@Override
public long getLastModified(HttpServletRequest request) {
if (lastModified == 0L) {
lastModified = System.currentTimeMillis();
}
return lastModified;
}
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.getWriter().write(“aa”);
return null;
}
}
Spring判断过期,通过判断请求的“If-Modified-Since”是否大于等于当前的getLastModified方法中时间戳,主要的实现代码为:

6.HandlerInterceptor的处理。
Servlet API定义的Servlet过滤器可以在servlet处理每个web请求的前后分别进行前置处理和后置处理。Spring中的拦截器需要实现HandlerInterceptor接口,实现三个方法:preHandle,postHandle和afterCompletion。preHandle和postHandle分别是在处理程序处理请求之前和之后被调用的。第二个方法还可以访问返回的ModelAndView对象。最后一个是在所有请求处理完成之后呗调用的。
下面是一个HandlerInterceptor的简单实现:

7.逻辑处理。
真正的逻辑处理其实是通过适配器中转调用Handler并返回视图的,对应的代码如下:
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
同样,这里来分析一下SimpleControllerHandlerAdapter中的实现:

进入到handleRequest方法:

真正的执行逻辑是在handleRequestInternal方法中的。
8.异常处理。
如果下面判断到出现了异常,会在调用processDispatchResult方法的时候进行识别,

processHandlerException中的代码为:

也就是把异常交给HandlerExceptionResolver的resolveException方法进行处理。
9.根据视图跳转页面。
在上面processDispatchResult的方法中,render方法就是处理页面跳转的逻辑。

上面的主要逻辑梳理如下:
9.1.解析视图名称。上面提到了DispatcherServlet会根据ModelAndView选择合适的视图来进行渲染,这一功能就是在resolveViewName中完成的。

来看一下AbstractCachingViewResolver类中的resolveViewName的处理逻辑:

如果不存在缓存的情况下就直接创建视图,如果存在就直接从缓存中提取。
createView的代码为,代码在UrlBasedViewResolver类中:

如果当前解析器不支持当前解析器如viewName为空等情况。
分别处理前缀为redirect:和forward:的情况。
最后调用父类的createView方法:

主要是考虑了几个方面的处理:
基于效率的考虑,提供了缓存的支持。
提供了redirect:和forward:前缀的支持。
添加了前缀以及后缀,并向View中加入了必须的属性设置。
9.2.页面跳转。
继续分析上面的render方法,通过viewName解析到对应的View之后,就可以进一步处理跳转逻辑了。
此处调用的代码为:
view.render(mv.getModelInternal(), request, response);
具体的实现代码为:

createMergedOutputModel方法:

renderMergedOutputModel方法:

上面的exposeModelAsRequestAttributes方法就是把model中的属性设置到request中。
至此,我们把Spring MVC的逻辑都分析完了。简单总结一下就是:
Spring MVC在容器启动的时候就把所有的请求和映射关系存放到内存中,客户端请求的时候根据request中的内容寻找对应的Handler处理请求。所以说如果存在相同的URL注解的话,Spring MVC启动的时候会报错,内容为
Ambiguous mapping found. Cannot map ‘userController’ bean method
也就是发现了模糊映射的错误。

介绍完了Spring MVC,接下来介绍Spring的远程服务。

1 0
原创粉丝点击