springMVC官方文档知识点梳理-关键

1、异步请求处理的相关配置

Servlet容器配置html

对于那些使用web.xml配置文件的应用,请确保web.xml的版本更新到3.0:java

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance http://java.sun.com/xml/ns/javaee
                    http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">

    ...

</web-app>

异步请求必须在web.xmlDispatcherServlet下的子元素<async-supported>true</async-supported>设置为true。此外,全部可能参与异步请求处理的过滤器Filter都必须配置为支持ASYNC类型的请求分派。在Spring框架中为过滤器启用支持ASYNC类型的请求分派应是安全的,由于这些过滤器通常都继承了基类OncePerRequestFilter,后者在运行时会检查该过滤器是否须要参与到异步分派的请求处理中。web

如下是一个例子,展现了web.xml的配置:spring

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="
                http://java.sun.com/xml/ns/javaee
                http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
        version="3.0">

        <filter>
            <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
            <filter-class>org.springframework.~.OpenEntityManagerInViewFilter</filter-class>
            <async-supported>true</async-supported>
        </filter>

        <filter-mapping>
            <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
            <url-pattern>/*</url-pattern>
            <dispatcher>REQUEST</dispatcher>
            <dispatcher>ASYNC</dispatcher>
        </filter-mapping>

    </web-app>

若是应用使用的是Servlet 3规范基于Java编程的配置方式,好比经过WebApplicationInitializer,那么你也须要设置"asyncSupported"标志和ASYNC分派类型的支持,就像你在web.xml 中所配置的同样。你能够考虑直接继承AbstractDispatcherServletInitializerAbstractAnnotationConfigDispatcherServletInitializer来简化配置,它们都自动地为你设置了这些配置项,并使得注册Filter过滤器实例变得很是简单。数据库

 注意过滤器的地址。apache

2、使用HandlerInterceptor拦截请求

Spring的处理器映射机制包含了处理器拦截器。拦截器在你须要为特定类型的请求应用一些功能时可能颇有用,好比,检查用户身份等。编程

处理器映射处理过程配置的拦截器,必须实现 org.springframework.web.servlet包下的 HandlerInterceptor接口。这个接口定义了三个方法: preHandle(..),它在处理器实际执行 以前 会被执行; postHandle(..),它在处理器执行 完毕 之后被执行; afterCompletion(..),它在 整个请求处理完成 以后被执行。这三个方法为各类类型的前处理和后处理需求提供了足够的灵活性。json

preHandle(..)方法返回一个boolean值。你能够经过这个方法来决定是否继续执行处理链中的部件。当方法返回 true时,处理器链会继续执行;若方法返回 false, DispatcherServlet即认为拦截器自身已经完成了对请求的处理(好比说,已经渲染了一个合适的视图),那么其他的拦截器以及执行链中的其余处理器就不会再被执行了。后端

拦截器能够经过interceptors属性来配置,该选项在全部继承了AbstractHandlerMapping的处理器映射类HandlerMapping都提供了配置的接口。以下面代码样例所示:浏览器

<beans>
    <bean id="handlerMapping" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
        <property name="interceptors">
            <list>
                <ref bean="officeHoursInterceptor"/>
            </list>
        </property>
    </bean>

    <bean id="officeHoursInterceptor" class="samples.TimeBasedAccessInterceptor">
        <property name="openingTime" value="9"/>
        <property name="closingTime" value="18"/>
    </bean>
<beans>
package samples;

public class TimeBasedAccessInterceptor extends HandlerInterceptorAdapter {

    private int openingTime;
    private int closingTime;

    public void setOpeningTime(int openingTime) {
        this.openingTime = openingTime;
    }

    public void setClosingTime(int closingTime) {
        this.closingTime = closingTime;
    }

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
            Object handler) throws Exception {
        Calendar cal = Calendar.getInstance();
        int hour = cal.get(HOUR_OF_DAY);
        if (openingTime <= hour && hour < closingTime) {
            return true;
        }
        response.sendRedirect("http://host.com/outsideOfficeHours.html");
        return false;
    }
}

在上面的例子中,全部被此处理器处理的请求都会被TimeBasedAccessInterceptor拦截器拦截。若是当前时间在工做时间之外,那么用户就会被重定向到一个HTML文件提示用户,好比显示“你只有在工做时间才能够访问本网站”之类的信息。

使用RequestMappingHandlerMapping时,实际的处理器是一个处理器方法HandlerMethod的实例,它标识了一个将被用于处理该请求的控制器方法。

如你所见,Spring的拦截器适配器HandlerInterceptorAdapter让继承HandlerInterceptor接口变得更简单了。

上面的例子中,全部控制器方法处理的请求都会被配置的拦截器先拦截到。若是你想进一步缩小拦截的URL范围,你能够经过MVC命名空间或MVC Java编程的方式来配置,或者,声明一个MappedInterceptor类型的bean实例来处理。

须要注意的是,HandlerInterceptor的后拦截postHandle方法不必定老是适用于注解了@ResponseBodyResponseEntity的方法。这些场景中,HttpMessageConverter会在拦截器的postHandle方法被调以前就把信息写回响应中。这样拦截器就没法再改变响应了,好比要增长一个响应头之类的。若是有这种需求,请让你的应用实现ResponseBodyAdvice接口,并将其定义为一个@ControllerAdvicebean或直接在RequestMappingHandlerMapping中配置。

3、视图解析

全部web应用的MVC框架都提供了视图相关的支持。Spring提供了一些视图解析器,它们让你可以在浏览器中渲染模型,并支持你自由选用适合的视图技术而没必要与框架绑定到一块儿。Spring原生支持JSP视图技术、Velocity模板技术和XSLT视图等。

有两个接口在Spring处理视图相关事宜时相当重要,分别是视图解析器接口ViewResolver和视图接口自己View。视图解析器ViewResolver负责处理视图名与实际视图之间的映射关系。视图接口View负责准备请求,并将请求的渲染交给某种具体的视图技术实现。

21.5.1 使用ViewResolver接口解析视图

Spring MVC中全部控制器的处理器方法都必须返回一个逻辑视图的名字,不管是显式返回(好比返回一个StringView或者ModelAndView)仍是隐式返回(好比基于约定的返回)。Spring中的视图由一个视图名标识,并由视图解析器来渲染。Spring有很是多内置的视图解析器。下表列出了大部分,表后也给出了一些例子。

表21.3 视图解析器

视图解析器 描述
AbstractCachingViewResolver 一个抽象的视图解析器类,提供了缓存视图的功能。一般视图在可以被使用以前须要通过准备。继承这个基类的视图解析器便可以得到缓存视图的能力。
XmlViewResolver 视图解析器接口ViewResolver的一个实现,该类接受一个XML格式的配置文件。该XML文件必须与Spring XML的bean工厂有相同的DTD。默认的配置文件名是/WEB-INF/views.xml
ResourceBundleViewResolver 视图解析器接口ViewResolver的一个实现,采用bundle根路径所指定的ResourceBundle中的bean定义做为配置。通常bundle都定义在classpath路径下的一个配置文件中。默认的配置文件名为views.properties
UrlBasedViewResolver ViewResolver接口的一个简单实现。它直接使用URL来解析到逻辑视图名,除此以外不须要其余任何显式的映射声明。若是你的逻辑视图名与你真正的视图资源名是直接对应的,那么这种直接解析的方式就很方便,不须要你再指定额外的映射。
InternalResourceViewResolver UrlBasedViewResolver的一个好用的子类。它支持内部资源视图(具体来讲,Servlet和JSP)、以及诸如JstlViewTilesView等类的子类。You can specify the view class for all views generated by this resolver by using setViewClass(..)。更多的细节,请见UrlBasedViewResolver类的java文档。
VelocityViewResolver / FreeMarkerViewResolver UrlBasedViewResolver下的实用子类,支持Velocity视图VelocityView(Velocity模板)和FreeMarker视图FreeMarkerView以及它们对应子类。
ContentNegotiatingViewResolver 视图解析器接口ViewResolver的一个实现,它会根据所请求的文件名或请求的Accept头来解析一个视图。

咱们能够举个例子,假设这里使用的是JSP视图技术,那么咱们可使用一个基于URL的视图解析器UrlBasedViewResolver。这个视图解析器会将URL解析成一个视图名,并将请求转交给请求分发器来进行视图渲染。

<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>

若返回一个test逻辑视图名,那么该视图解析器会将请求转发到RequestDispatcher,后者会将请求交给/WEB-INF/jsp/test.jsp视图去渲染。

若是须要在应用中使用多种不一样的视图技术,你可使用ResourceBundleViewResolver

<bean id="viewResolver"
        class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
    <property name="basename" value="views"/>
    <property name="defaultParentView" value="parentView"/>
</bean>

ResourceBundleViewResolver会检索由bundle根路径下所配置的ResourceBundle,对于每一个视图而言,其视图类由[viewname].(class)属性的值指定,其视图url由[viewname].url属性的值指定。下一节将详细讲解视图技术,你能够在那里找到更多例子。你还能够看到,视图还容许有基视图,即properties文件中全部视图都“继承”的一个文件。经过继承技术,你能够为众多视图指定一个默认的视图基类。

AbstractCachingViewResolver的子类可以缓存已经解析过的视图实例。关闭缓存特性也是能够的,只须要将cache属性设置为false便可。另外,若是实在须要在运行时刷新某个视图(好比修改了Velocity模板时),你可使用removeFromCache(String viewName, Locale loc)方法。`

为啥没有HTML的页面视图。由于其它都是包含HTML元素的,并无纯粹的HTML页面,都须要注入视图相关的东西。这个连接http://docs.spring.io/spring-framework/docs/4.2.4.RELEASE/spring-framework-reference/html/view.html详细介绍了各类视图,例如jsp、xml、freemark等。那怎么解决这个问题呢?先后端分离,就能够解决。

3.2视图链

Spring支持同时使用多个视图解析器。所以,你能够配置一个解析器链,并作更多的事好比,在特定条件下覆写一个视图等。你能够经过把多个视图解析器设置到应用上下文(application context)中的方式来串联它们。若是须要指定它们的次序,那么设置order属性便可。请记住,order属性的值越大,该视图解析器在链中的位置就越靠后。

在下面的代码例子中,视图解析器链中包含了两个解析器:一个是InternalResourceViewResolver,它老是自动被放置在解析器链的最后;另外一个是XmlViewResolver,它用来指定Excel视图。InternalResourceViewResolver不支持Excel视图。

<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>

<bean id="excelViewResolver" class="org.springframework.web.servlet.view.XmlViewResolver">
    <property name="order" value="1"/>
    <property name="location" value="/WEB-INF/views.xml"/>
</bean>

<!-- in views.xml -->

<beans>
    <bean name="report" class="org.springframework.example.ReportExcelView"/>
</beans>

若是一个视图解析器不能返回一个视图,那么Spring会继续检查上下文中其余的视图解析器。此时若是存在其余的解析器,Spring会继续调用它们,直到产生一个视图返回为止。若是最后全部视图解析器都不能返回一个视图,Spring就抛出一个ServletException

视图解析器的接口清楚声明了,一个视图解析器是能够返回null值的,这表示不能找到任何合适的视图。并不是全部的视图解析器都这么作,可是也存在不得不如此的场景,即解析器确实没法检测对应的视图是否存在。好比,InternalResourceViewResolver在内部使用了RequestDispatcher,而且进入分派过程是检测一个JSP视图是否存在的惟一方法,但这个过程仅可能发生惟一一次。一样的VelocityViewResolver和部分其余的视图解析器也存在这样的状况。具体的请查阅某个特定的视图解析器的Java文档,看它是否会report不存在的视图。所以,若是不把InternalResourceViewResolver放置在解析器链的最后,将可能致使解析器链没法彻底执行,由于InternalResourceViewResolver永远都会 返回一个视图。

重定向前缀——redirect:

尽管使用RedirectView来作重定向能工做得很好,但若是控制器自身仍是须要建立一个RedirectView,那无疑控制器仍是了解重定向这么一件事情的发生。这仍是有点不尽完美,不一样范畴的耦合仍是太强。控制器其实不该该去关心响应会如何被渲染。In general it should operate only in terms of view names that have been injected into it.

一个特别的视图名前缀能完成这个解耦:redirect:。若是返回的视图名中含有redirect:前缀,那么UrlBasedViewResolver(及它的全部子类)就会接受到这个信号,意识到这里须要发生重定向。而后视图名剩下的部分会被解析成重定向URL。

这种方式与经过控制器返回一个重定向视图RedirectView所达到的效果是同样的,不过这样一来控制器就能够只专一于处理并返回逻辑视图名了。若是逻辑视图名是这样的形式:redirect:/myapp/some/resource,他们重定向路径将以Servlet上下文做为相对路径进行查找,而逻辑视图名若是是这样的形式:redirect:http://myhost.com/some/arbitrary/path,那么重定向URL使用的就是绝对路径。

注意的是,若是控制器方法注解了@ResponseStatus,那么注解设置的状态码值会覆盖RedirectView设置的响应状态码值。

重定向前缀——forward:

对于最终会被UrlBasedViewResolver或其子类解析的视图名,你可使用一个特殊的前缀:forward:。这会致使一个InternalResourceView视图对象的建立(它最终会调用RequestDispatcher.forward()方法),后者会认为视图名剩下的部分是一个URL。所以,这个前缀在使用InternalResourceViewResolverInternalResourceView时并无特别的做用(好比对于JSP来讲)。但当你主要使用的是其余的视图技术,而又想要强制把一个资源转发给Servlet/JSP引擎进行处理时,这个前缀可能就颇有用(或者,你也可能同时串联多个视图解析器)。

redirect:前缀同样,若是控制器中的视图名使用了forward:前缀,控制器自己并不会发觉任何异常,它关注的仍然只是如何处理响应的问题。

@RequestMapping(path = "/files/{path}", method = RequestMethod.POST)
public String upload(...) {
    // ...
    return "redirect:files/{path}";
}

Spring内置对多路上传的支持,专门用于处理web应用中的文件上传。你能够经过注册一个可插拔的MultipartResolver对象来启用对文件多路上传的支持。该接口在定义于org.springframework.web.multipart包下。Spring为通常的文件上传提供了MultipartResolver接口的一个实现,为Servlet 3.0多路请求的转换提供了另外一个实现。

默认状况下,Spring的多路上传支持是不开启的,由于有些开发者但愿由本身来处理多路请求。若是想启用Spring的多路上传支持,你须要在web应用的上下文中添加一个多路传输解析器。每一个进来的请求,解析器都会检查是否是一个多部分请求。若发现请求是完整的,则请求按正常流程被处理;若是发现请求是一个多路请求,则你在上下文中注册的MultipartResolver解析器会被用来处理该请求。以后,请求中的多路上传属性就与其余属性同样被正常对待了。【最后一句翻的很差,multipart翻译成多路仍是多部分还在斟酌中。望阅读者注意此处。】

21.10.2 使用MultipartResolver与Commons FileUpload传输文件

下面的代码展现了如何使用一个通用的多路上传解析器CommonsMultipartResolver

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">

    <!-- 支持的其中一个属性,支持的最大文件大小,以字节为单位 -->
    <property name="maxUploadSize" value="100000"/>

</bean>

固然,要让多路解析器正常工做,你须要在classpath路径下准备必须的jar包。若是使用的是通用的多路上传解析器CommonsMultipartResolver,你所须要的jar包是commons-fileupload.jar

当Spring的DispatcherServlet检测到一个多部分请求时,它会激活你在上下文中声明的多路解析器并把请求交给它。解析器会把当前的HttpServletRequest请求对象包装成一个支持多路文件上传的请求对象MultipartHttpServletRequest。有了MultipartHttpServletRequest对象,你不只能够获取该多路请求中的信息,还能够在你的控制器中得到该多路请求的内容自己。

4、文件上传 Servlet 3.0下的MultipartResolver

要使用基于Servlet 3.0的多路传输转换功能,你必须在web.xml中为DispatcherServlet添加一个multipart-config元素,或者经过Servlet编程的方法使用javax.servlet.MultipartConfigElement进行注册,或你本身定制了本身的Servlet类,那你必须使用javax.servlet.annotation.MultipartConfig对其进行注解。其余诸如最大文件大小或存储位置等配置选项都必须在这个Servlet级别进行注册,由于Servlet 3.0不容许在解析器MultipartResolver的层级配置这些信息。

当你经过以上任一种方式启用了Servlet 3.0多路传输转换功能,你就能够把一个StandardServletMultipartResolver解析器添加到你的Spring配置中去了:

<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver">
</bean>

21.10.4 处理表单中的文件上传

当解析器MultipartResolver完成处理时,请求便会像其余请求同样被正常流程处理。首先,建立一个接受文件上传的表单将容许用于直接上传整个表单。编码属性(enctype="multipart/form-data")能让浏览器知道如何对多路上传请求的表单进行编码(encode)。

<html>
    <head>
        <title>Upload a file please</title>
    </head>
    <body>
        <h1>Please upload a file</h1>
        <form method="post" action="/form" enctype="multipart/form-data">
            <input type="text" name="name"/>
            <input type="file" name="file"/>
            <input type="submit"/>
        </form>
    </body>
</html>

下一步是建立一个能处理文件上传的控制器。这里须要的控制器与通常注解了@Controller的控制器基本同样,除了它接受的方法参数类型是MultipartHttpServletRequest,或MultipartFile

@Controller
public class FileUploadController {

    @RequestMapping(path = "/form", method = RequestMethod.POST)
    public String handleFormUpload(@RequestParam("name") String name, @RequestParam("file") MultipartFile file) {

        if (!file.isEmpty()) {
            byte[] bytes = file.getBytes();
            // store the bytes somewhere
            return "redirect:uploadSuccess";
        }

        return "redirect:uploadFailure";
    }

}

请留意@RequestParam注解是如何将方法参数对应到表单中的定义的输入字段的。在上面的例子中,咱们拿到了byte[]文件数据,只是没对它作任何事。在实际应用中,你可能会将它保存到数据库、存储在文件系统上,或作其余的处理。特别注意这个,有的时候缺失这个,可能致使file为null。

当使用Servlet 3.0的多路传输转换时,你也可使用javax.servlet.http.Part做为方法参数:

@Controller
public class FileUploadController {

    @RequestMapping(path = "/form", method = RequestMethod.POST)
    public String handleFormUpload(@RequestParam("name") String name, @RequestParam("file") Part file) {

        InputStream inputStream = file.getInputStream();
        // store bytes from uploaded file somewhere

        return "redirect:uploadSuccess";
    }

}

21.10.5 处理客户端发起的文件上传请求

在使用了RESTful服务的场景下,非浏览器的客户端也能够直接提交多路文件请求。上一节讲述的全部例子与配置在这里也都一样适用。但与浏览器不一样的是,提交的文件和简单的表单字段,客户端发送的数据能够更加复杂,数据能够指定为某种特定的内容类型(content type)——好比,一个多路上传请求可能第一部分是个文件,而第二部分是个JSON格式的数据:

POST /someUrl
    Content-Type: multipart/mixed

    --edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
    Content-Disposition: form-data; name="meta-data"
    Content-Type: application/json; charset=UTF-8
    Content-Transfer-Encoding: 8bit

    {
        "name": "value"
    }
    --edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
    Content-Disposition: form-data; name="file-data"; filename="file.properties"
    Content-Type: text/xml
    Content-Transfer-Encoding: 8bit
    ... File Data ...

对于名称为meta-data的部分,你能够经过控制器方法上的@RequestParam("meta-data") String metadata参数来得到。但对于那部分请求体中为JSON格式数据的请求,你可能更想经过接受一个对应的强类型对象,就像@RequestBody经过HttpMessageConverter将通常请求的请求体转换成一个对象同样。

这是可能的,你可使用@RequestPart注解来实现,而非@RequestParam。该注解将使得特定多路请求的请求体被传给HttpMessageConverter,而且在转换时考虑多路请求中不一样的内容类型参数'Content-Type'

@RequestMapping(path = "/someUrl", method = RequestMethod.POST)
public String onSubmit(@RequestPart("meta-data") MetaData metadata, @RequestPart("file-data") MultipartFile file) {

    // ...

}

请注意MultipartFile方法参数是如何可以在@RequestParam@RequestPart注解下互用的,两种方法都能拿到数据。但,这里的方法参数@RequestPart("meta-data") MetaData则会由于请求中的内容类型请求头'Content-Type'被读入成为JSON数据,而后再经过MappingJackson2HttpMessageConverter被转换成特定的对象。

5、异常

异常 HTTP状态码
BindException 400 (无效请求)
ConversionNotSupportedException 500 (服务器内部错误)
HttpMediaTypeNotAcceptableException 406 (不接受)
HttpMediaTypeNotSupportedException 415 (不支持的媒体类型)
HttpMessageNotReadableException 400 (无效请求)
HttpMessageNotWritableException 500 (服务器内部错误)
HttpRequestMethodNotSupportedException 405 (不支持的方法)
MethodArgumentNotValidException 400 (无效请求)
MissingServletRequestParameterException 400 (无效请求)
MissingServletRequestPartException 400 (无效请求)
NoHandlerFoundException 404 (请求未找到)
NoSuchRequestHandlingMethodException 404 (请求未找到)
TypeMismatchException 400 (无效请求)
MissingPathVariableException 500 (服务器内部错误)
NoHandlerFoundException 404 (请求未找到)

总结:

图21.1 Spring Web MVC处理请求的(高层抽象)工做流

Spring的视图解析也是设计得异常灵活。控制器通常负责准备一个Map模型、填充数据、返回一个合适的视图名等,同时它也能够直接将数据写到响应流中。视图名的解析高度灵活,支持多种配置,包括经过文件扩展名、Accept内容头、bean、配置文件等的配置,甚至你还能够本身实现一个视图解析器ViewResolver模型(MVC中的M,model)实际上是一个Map类型的接口,完全地把数据从视图技术中抽象分离了出来。你能够与基于模板的渲染技术直接整合,如JSP、Velocity和Freemarker等,或者你还能够直接生成XML、JSON、Atom以及其余多种类型的内容。Map模型会简单地被转换成合适的格式,好比JSP的请求属性(attribute),一个Velocity模板的模型等。

相关文章
相关标签/搜索