您的位置:首页 > 编程语言 > Java开发

Spring MVC学习详解

2016-01-22 12:55 405 查看
介绍一下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的远程服务。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息