web后端-Spring MVC

缺失部分随着碰到问题后补充

Servlet基础详解

Servlet基础详解

Spring MVC体系概述

Spring MVC框架围绕DispatcherServlet这个核心展开,DispatcherServlet是 SpringMVC的总导演、总策划,它负责截获请求并将其分派给相应的处理器处理。Spring MVC框架包括注解驱动控制器、请求及响应的信息处理、视图解析、本地化解析、上传文件解析、异常处理及表单标签绑定等内容。

体系结构

配置DispatcherServlet

DispatcherServlet是Spring MVC的“灵魂”和“心脏”,它负责接收HTTP请求并协调Spring MVC的各个组件完成请求处理的工作。和任何Servlet一样,用户必须在web.xml中配置好DispatcherServlet。我们在第②章中已经配置了一个简单的DispatcherServlet,这里进一步分析其具体的配置。

要了解 Spring MVC框架的工作机理,必须回答以下3个问题。

  1. DispatcherServlet框架如何截获特定的HTTP请求并交由Spring MVC框架处理?
  2. 位于Web层的Spring容器(WebApplicationContext)如何与位于业务层的Spring容器(ApplicationContext)建立关联,并将它们装配到DispatcherServlet 中?
  3. 如何初始化Spring MVC 的各个组件,并将它们装配到DispatcherServlet中?

配置 DispatcherServlet,截获特定的URL请求

大家知道,我们可以在 web.xml中配置一个Servlet,并通过<servlet-mapping>指定其处理的URL。这是传统的 DispatcherServlet配置方式。而Spring 4.0已全面支持Servlet3.0,因此也可以采用编程式的配置方式。这里先采用传统的web.xml的方式进行讲解,然后介绍基于Servlet 3.0的新方式。假设我们希望Spring MVC的 DispatcherServlet能截获并处理所有以.html结束的URL请求,那么可以在web.xml中按如下方式进行配置,如代码清单17-1所示。

  • 在①处,通过contextConfigLocation参数指定业务层Spring容器的配置文件(多个配置文件使用逗号分隔)。ContextLoaderListener是一个ServletContextListener,它通过contextConfigLocation参数所指定的Spring配置文件启动“业务层”的Spring容器。
  • 在②处配置了名为smart的 DispatcherServlet,它默认自动加载/WEB-INF/smart-servlet.xml (<servlet-Name>-servlet.xml)的Spring配置文件,启动Web层的Spring容器。
  • 在③处,通过<servlet-mapping>指定DispatcherServlet处理所有以.html为后缀的HTTP 请求,即所有带.html后缀的HTTP请求都会被 DispatcherServlet截获并处理。

我们知道,多个 Spring容器之间可设置为父子级的关系,以实现良好的解耦。在这里,“Web层”Spring容器将作为“业务层”Spring 容器的子容器,即“Web层”容器可以引用“业务层”容器的Bean,而“业务层”容器却访问不到“Web层”容器的Bean。
需要提醒的是,一个web.xml可以配置多个DispatcherServlet,通过其<servlet-mapping>配置,让每个DispatcherServlet处理不同的请求。
DispatcherServlet 遵循“契约优于配置”的原则,在大多数情况下,用户无须进行额外的配置,只需按契约行事即可。

如果确实要对DispatcherServlet 的默认规则进行调整,则DispatcherServlet是“敞开胸怀”的。下面是常用的一些配置参数,可通过<servlet>的<init-param>指定。

  • namespace: DispatcherServlet 对应的命名空间,默认为<servlet-name>-servlet,用于构造Spring 配置文件的路径。在显式指定该属性后,配置文件对应的路径为WEB-INF/<namespace>.xml,而非 WEB-INF/<servlet-name>-servlet.xml。如果将namespace设置为sample,则对应的Spring配置文件为WEB-INF/sample.xml。
  • contextConfigLocation:如果 DispatcherServlet上下文对应的Spring 配置文件有多个,则可以使用该属性按照 Spring资源路径的方式指定。如classpath:sample1.xml,classpath:sample2.xml ”,DispatcherServlet将使用类路径下的sample1.xml和 sample2.xml 这两个配置文件初始化 WebApplicationContext。
  • publishContext:布尔类型的属性,默认值为ture。DispatcherServlet根据该属性决定是否将WebApplicationContext发布到ServletContext的属性列表中,以便调用者可借由ServletContext 找到 WebApplicationContext实例,对应的属性名为DispatcherServlet#getServletContextAttributeName()方法的返回值
  • publishEvents:布尔类型的属性。当DispatcherServlet处理完一个请求后,是否需要向容器发布一个 ServletRequestHandledEvent事件,默认值为 true。如果容器中没有任何事件监听器,则可以将该属性设置为 false,以便提高运行性能。

下面的代码显式指定Web层的Spring 配置文件。

之前提到Spring 4.0已全面支持Servlet 3.0,因此,在Servlet 3.0环境中,也可以使用编程的方式来配置Servlet容器。下面的代码可达到和代码清单17-1同样的效果。

 

 接下来看看Servlet 3.0的实现原理。在Servlet 3.0环境中,容器会在类路径中查找实现javax.servlet.ServletContainerInitializer的类,如果发现已有实现类,就会调用它来配置Servlet容器。在Spring中,org.springframework.web.SpringServletContainerInitializer类实现了该接口,同时这个类又会查找实现 org.springframework.web.WebApplicationInitializer接口的类,并将配置任务交给这些实现类去完成。另外,Spring提供了一个便利的抽象类AbstractAnnotationConfigDispatcherServletInitializer来实现这个接口,使得它在注册DispatcherServlet时只需简单地指定它的Servlet 映射即可。在上述示例中,当应用部署到Servlet 3.0容器中时,容器启动时会自动发现它,并使用它来配置Servlet上下文。

探究DispatcherServlet 的内部逻辑

一个简单的实例

注解驱动的控制器

使用@RequestMapping映射请求

请求处理方法签名

使用矩阵变量绑定参数

请求处理方法签名详细说明

使用HttpMessageConverter<T>

为啥要用这个?这有什么用?

HttpMessageConverter<T>是Spring的一个重要接口,它负责将请求信息转换为一个对象(类型为T),将对象(类型为T)输出为响应信息。

这个在以前的开发中没有碰到例子,目前只有一点感触。

请求信息包括请求头,请求体,以及别的内容,可以说功能非常强大。比如我想获取请求头中的系统信息之类的信息,就可以通过这个方法获取。

怎么用?

DispatcherServlet 默认已经安装了RequestMappingHandlerAdapter作为HandlerAdapter的组件实现类,HttpMessageConverter即由 RequestMappingHandlerAdapter使用,将请求信息转换为对象,或将对象转换为响应信息。
HttpMessageConverter<T>接口定义了以下几个方法。

  • Boolean canRead(Class<?> clazz, MediaType mediaType):指定转换器可以读取的对象类型,即转换器可将请求信息转换为clazz类型的对象;同时指定支持的MIME媒体类型(如text/html、application/json等),MIME媒体类型在RFC2616中定义(MIME类型说明可参见 http://www.w3school.com.cn/media/media_mimeref.asp)。
  • Boolean canWrite(Class<?> clazz,MediaType mediaType):指定转换器可以将clazz类型的对象写到响应流中,响应流支持的媒体类型在mediaType中定义。List<MediaType> getSupportedMediaTypes():该转换器支持的媒体类型。
  • T read(Class<? extends T> clazz, HttpInputMessage inputMessage):将请求信息流转换为T类型的对象。
  • void write(T t, MediaType contentType, HttpOutputMessage outputMessage):将T类型的对象写到响应流中,同时指定响应的媒体类型为contentType。

1. HttpMessageConverter<T>的实现类

Spring 为 HttpMessageConverter<T>提供了众多的实现类,它们组成了一个功能强大、用途广泛的HttpMessageConverter<T>家族,具体说明如表17-2所示。

理论小知识:HttpMessageConverter<T>的实现类 - 百度文库

RequestMappingHandlerAdapter 默认已经装配了以下HttpMessageConverter:

  • StringHttpMessageConverter。
  • ByteArrayHttpMessageConverter。
  • SourceHttpMessageConverter。
  • AllEncompassingFormHttpMessageConverter。

如果需要装配其他类型的HttpMessageConverter,则可在Spring的 Web容器上下文中自行定义一个RequestMappingHandlerAdapter,如代码清单17-15所示。

如果在Spring Web容器中显式定义了一个RequestMappingHandlerAdapter,则 SpringMVC将使用它覆盖默认的 RequestMappingHandlerAdapter。

2.使用HttpMessageConverter<T>

如何使用HttpMessageConverter<T>将请求信息转换并绑定到处理方法的入参中呢? Spring MVC提供了两种途径。

  • 使用@RequestBodyl@ResponseBody对处理方法进行标注。
  • 使用HttpEntity<T>/ResponseEntity<T>作为处理方法的入参或返回值。

下面分别通过实例进行说明。首先来看使用@RequestBody/@ResponseBody的例子,如代码清单17-16所示。

  • 在代码清单17-15中,已经为 RequestMappingHandlerAdapter注册了若干个HttpMessageConverter。handle41()方法的requestBody入参标注了一个@RequestBody注解,如①处所示,Spring MVC将根据requestBody的类型查找匹配的HttpMessageConverter。由于StringHttpMessageConverter的泛型类型对应String,所以StringHttpMessageConverter将被Spring MVC选中,用它将请求体信息进行转换并将结果绑定到requestBody 入参上。
  • handle42()方法拥有一个@ResponseBody注解,如②处所示。由于方法返回值类型为byte[],所以Spring MVC根据类型匹配的查找规则将使用ByteArrayHttpMessageConverter对返回值进行处理,即将图片数据流输出到客户端。

下面编写一个测试用例,通过RestTemplate对 handle41()及 handle42()这两个方法进行测试,如代码清单17-17所示。

RestTemplate是 Spring 的模板类,在客户端程序中可使用该类调用Web服务器端的服务,它支持REST风格的URL。此外,它像RequestMappingHandlerAdapter一样拥有一张HttpMessageConverter的注册表,RestTemplate默认已经注册了以下HttpMessageConverter:

  • ByteArrayHttpMessageConverter。
  • StringHttpMessageConverter。
  • ResourceHttpMessageConverter。SourceHttpMessageConverter。
  • AllEncompassingFormHttpMessageConverter。

所以,在默认情况下,RestTemplate就可以利用这些HttpMessageConverter对响应数据进行相应的转换处理。可通过 RestTemplate的 setMessageConverters(List<HtpMessage-Converter<?>> messageConverters)方法手工注册 HttpMessageConverter
和@RequestBody/@ResponseBody 类似,HttpEntity<?>不但可以访问请求和响应报文体的数据,还可以访问请求和响应报文头的数据。Spring MVC根据HttpEntity的泛型类型查找对应的HttpMessageConverter。
使用HttpEntity<?>对代码清单17-16中的两个方法进行改造,完成相似的功能,如代码清单17-18所示。

  • 在①处使用 HttpEntity<String>指定入参的类型,Spring MVC 分析出泛型类型为String,使用StringHttpMessageConverter将请求体内容绑定到httpEntity 中,返回的 String类型的值为逻辑视图名。
  • ②处的处理方法返回值类型为ResponseEntity<byte[>,Spring MVC分析出泛型类型为byte[],使用 ByteArrayHttpMessageConverter输出图片数据流。

通过以上两个实例,可以得出以下几条结论。

  • 当控制器处理方法使用@RequestBody/@ResponseBody或 HttpEntity<T>/ResponseEntity<T>时,Spring MVC才使用注册的 HttpMessageConverter对请求/响应消息进行处理。
  • 当控制器处理方法使用@RequestBodyl@ResponseBody或 HttpEntity</ResponseEntity<T>时,Spring首先根据请求头或响应头的Accept属性选择匹配的 HttpMessageConverter,然后根据参数类型或泛型类型的过滤得到匹配的HttpMessageConverter,如果找不到可用的HttpMessageConverter则报错。
  • @RequestBody和@ResponseBody不需要成对出现。如果方法入参使用了@RequestBody,则 Spring MVC 选择匹配的 HttpMessageConverter将请求消息转换并绑定到该入参中。如果处理方法标注了@ResponseBody,则 Spring MVC选择匹配的HttpMessageConverter将方法返回值转换并输出响应消息。
  • HttpEntity<T>/ResponseEntity<T>的功能和@RequestBody/@ResponseBody相似。

3.处理XML和JSON

Spring MVC提供了几个处理XML和JSON格式的请求/响应消息的 HttpMessageConverter。

  • MarshallingHttpMessageConverter:处理XML格式的请求或响应消息。
  • Jaxb2RootElementHttpMessageConverter:同上,底层使用JAXB。
  • MappingJackson2HttpMessageConverter:处理JSON格式的请求或响应消息。

 

因此,只要在Spring Web容器中为 RequestMappingHandlerAdapter装配好相应的处理XML和JSON格式的请求/响应消息的HttpMessageConverter,并在交互中通过请求的Accept 指定 MIME类型,Spring MVC就可使服务器端的处理方法和客户端透明地通过XML 或JSON格式的消息进行通信,开发者几乎无须关心通信层数据格式的问题,可以将精力集中到业务层的处理上。单就这一点而言,其他 MVC框架和Spring MVC相比,就如诸葛亮给关云长的评语一样:“犹未及美髯公之绝伦超群也。”

首先为RequestMappingHandlerAdapter装配可处理XML 和JSON格式的请求/响应消息的HttpMessage Converter,如代码清单17-19所示。

然后在控制器中编写相应的方法,如代码清单17-20所示。

对于服务器端的处理方法而言,除使用@RequestBody/@ResponseBody或 HttpEntity<T>/ResponseEntity<T>进行方法签名外,不需要进行任何额外的处理,借由Spring MVC中装配的 HttpMessageConverter,它便拥有了处理XML及JSON格式的消息的能力。
在接收到一个HTTP请求时,handle51()如何知道请求消息的格式?在处理完成后,又根据什么确定响应消息的格式?答案很简单:通过请求消息头的 Content-Type及Accept属性确定。下面使用RestTemplate编写调用handle51()方法的客户端程序,如代码清单17-21所示。

服务器端启动Web服务,运行testhandle51 WithXml()测试方法,使用网络监控工具(如TcpTrace)拦截请求响应报文,如图17-6所示。

通过以上HTTP请求/响应报文,我们清楚地知道客户端的User对象被流化为一段对应的XML报文(阴影部分),同时通过报文头属性 Accept和 Content-Type指定接收的MIME类型和本请求的报文内容均为application/xml。
请求报文被服务器端的UserController#handle51()方法正确处理,它根据请求的报文头属性 Accept决定将服务器端的User对象流化为XML 并返回HTTP响应报文(阴影部分),同时指定响应报文的 Content-Type属性为application/xml。
如果希望通过JSON方式进行通信,则仅需对客户端代码进行轻微的调整即可,服务器端代码无须作任何更改,如代码清单17-22所示。

将请求报文头的Content-Type 及 Accept属性更改为application/json即可。再次执行testhandle51()方法,观察HTTP请求/响应报文,如图17-7所示。

可见,请求报文头的Content-Type 及 Accept属性更改为application/json,User对象的数据以JSON格式进行传递。

使用@RestController和AsyncRestTemplate

处理模型数据

处理方法的数据绑定

视图和视图解析器

请求处理方法执行完成后,最终返回一个ModelAndView对象。对于那些返回String、View 或 ModelMap等类型的处理方法,Spring MVC也会在内部将它们装配成-个ModelAndView对象,该对象包含了视图逻辑名和模型对象的信息。
Spring MVC借助视图解析器(ViewResolver)得到最终的视图对象(View),这可能是我们常见的JSP视图,也可能是一个基于FreeMarker、Velocity模板技术的视图,还可能是PDF、Excel、XML、JSON等各种形式的视图。
对于最终究竟采取何种视图对象对模型数据进行渲染,处理器并不关心,处理器的工作重点聚焦在生产模型数据的工作上,从而实现MVC的充分解耦。

认识视图

视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户。视图对象可以是常见的JSP,还可以是Excel或 PDF等形式不一的媒体形式。为了实现视图模型和具体实现技术的解耦,Spring 在 org.springframework.web.servlet包中定义了一个高度抽象的 View接口,该接口中定义了两个方法。

  • String getContentType():视图对应的MIME类型,如 text/html、image/jpeg 等。
  • void render(Map model,HttpServletRequest request,HttpServletResponse response):将模型数据以某种MIME类型渲染出来。

视图对象是一个Bean,通常情况下,视图对象由视图解析器负责实例化。由于视图Bean是无状态的,所以它们不会有线程安全的问题。
不同类型的视图实现技术对应不同的 View实现类,这些视图实现类都位于org.springframework.web.servlet.view包中,通过表17-5来说明。

 认识视图解析器

Spring MVC为逻辑视图名的解析提供了不同的策略,可以在Spring Web上下文中配置一种或多种解析策略,并指定它们之间的先后顺序。每种解析策略对应一个具体的视图解析器实现类。视图解析器的工作比较单一,即将逻辑视图名解析为一个具体的视图对象。所有视图解析器都实现了ViewResolver接口,该接口仅有一个方法。

View resolveviewName (String viewName, Locale locale)

resolveViewName()方法的签名清楚地向我们传达了视图解析器工作的内涵:根据逻辑视图名和本地化对象得到一个视图对象。Spring拥有众多的视图解析器实现类,通过表17-6进行概括性说明。

 用户可以选择-种视图解析器或混用多种视图解析器,每个视图解析器都实现了Ordered接口并开放出一个orderNo属性,可以通过该属性指定解析器的优先顺序,值越小优先级越高。有些视图解析器默认为最高优先级(如ContentNegotiatingViewResolver),而有些视图解析器默认为最低优先级(如InternalResourceViewResolver、 XsltViewResolver等),具体请参考API文档。

SpringMVC会按照视图解析器的优先级顺序对逻辑视图名进行解析,直到解析成功并返回视图对象,否则将抛出ServletException 异常。

JSP和JSTL

1.使用InternalResourceViewResolver

JSP是最常见的视图技术,在17.1.3节中就使用了InternalResourceViewResolver 作为视图解析器,其配置如下:

<bean class="org.springf ramework . web. servlet. view. InternalResourceViewResolver"p:prefix=" /WEB- INF/views/" p:suffix=".jsp"/>

代码清单17-4中的createUser(处理方法返回名为user/createSuccess的逻辑视图名,InternalResourceViewResolver负责对此进行解析,将得到如图17-15所示的解析结果。

 InternalResourceViewResolver默认使用InternalResourceView作为视图实现类。如果JSP文件使用了JSTL的国际化功能,确切地说,当JSP页面使用JSTL的<fmt:message/>标签时,用户需要使用JstlView替换默认的视图实现类,如下:

<bean class="org . springf ramework. web . servlet.view. InternalResourceViewResolver"
p:viewClass= "org. springframework . web. servlet. view . JstlView"
p:prefix=" /WEB- INF/views/" p:suffix=".jsp"/>

下面通过一一个例子演示使用JSTL的<fmt:message/>标签让 JSP页面实现本地化输出。首先在UserController中添加一个方法。

 

 

 userList.jsp所使用的国际化资源在content资源文件中定义,更改smart-servlet.xml中ResourceBundleMessageSource的配置,添加此国际化资源。

 这样,userList.jsp 就可以根据客户端的不同显示相应的本地化页面。除了可以使用JSTL的标签外,Spring也提供了一个轻量级的标签库,可以在JSP文件中使用这些标签。

2.使用spring表单标签

3.关于复选框、单选框及下拉框和表单对象属性的映射问题

模板视图


Excel


PDF


输出XML


输出JSON


使用XmlViewResolver


使用ResourceBundleViewResolver


混合使用多种视图技术

本地化解析

文件上传

Spring MVC为文件上传提供了直接支持,这种支持是通过即插即用的MutipartResolver实现的。Spring使用Jakarta Commons FileUpload技术实现了一个MultipartResolver实现类: CommonsMultipartResolver。

在Spring MVC.上下文中默认没有装配MultipartResolver,因此默认情况下不能处理文件的.上传工作。如果想使用Spring的文件上传功能,则需要先在上下文中配置MultipartResolver.

配置MultipartResolver

下面使用CommonsMultipartResolver配置一个MultipartResolver 解析器。

defaultEncoding必须和用户JSP的pageEncoding属性一致, 以便正确读取表单的内容。uploadTempDir是文件上传过程中所使用的临时目录,文件上传完成后,临时目录中的临时文件会被自动清除。

为了让CommonsMultipartResolver正常工作,必须先将Jakarta Commons FileUpload及Jakarta Commons io的类包添加到类路径下。

编写控制器和文件上传表单页面

在UserController中添加一个用于处理用户头像上传的方法,如代码清单17-51所示。

Spring MVC会将上传文件绑定到MultipartFile对象中。MultipartFile 提供了获取上传文件内容、文件名等方法,通过其transferTo(方法还可将文件存储到硬件中,具体说明如下。

  • byte[] getBytes(): 获取文件数据。
  • String getContentType(:获取文件MIME类型,如image/pjpeg、 text/plain 等。
  • InputStream getlnputStream():获取文件流。
  • String getName():获取表单中文件组件的名字。
  • String getOriginalFilename(:获取上传文件的原名。
  • long getSize():获取文件的字节大小,单位为Byte。
  • boolean isEmpty():是否有上传的文件。
  • void transfer To(File dest):可以使用该文件将上传文件保存到一个目标文件中。

负责上传文件的表单和一般表单有一些区别,表单的编码类型必须是multipart/form-data类型。

WebSocket支持

杂项

静态资源处理

优雅REST风格的资源URL不希望带.html或.do等后缀,以下是几个优雅的URL:

  • /blog/tom: 用户tom的blog资源
  • /forum/java: java 论坛版块资源
  • /order/4321: 订单号为4321的订单资源

由于早期的Spring MVC不能很好地处理静态资源,所以在web.xml 中配置DispatcherServlet的请求映射时,往往采用*.do、 *.xhtml 等方式。这就决定了请求URL必须是一个带后缀的URL,而无法采用真正REST风格的URL。

如果将DispatcherServlet 请求映射配置为“/”, 则Spring MVC将捕获Web容器所有的请求,包括静态资源的请求,Spring MVC会将它们当成一个普通请求处理,因找不到对应的处理器而导致错误。

如何让Spring框架能够捕获所有URL的请求,同时又将静态资源的请求转由Web容器处理,是可将DispatcherServlet的请求映射配置为“/”的前提。由于REST是Spring ;的重要功能之一,所以Spring团队很看重静态资源处理这项任务,给出了堪称经典的两种解决方案。

在学习这两个方案之前,先调整web.xml中DispatcherServlet 的配置,使其可以捕获所有的请求。

通过<url- pattern>/</url-patterm>的配置,所有URL请求都将被Spring MVC的DispatcherServlet截获。

1.采用<mvc:default-servlet-handler/>

在smart- servlet.xml中配置<mvc:default servlet-handler/>后,会在Spring MVC.上下文中定义一个org.springframework.web.servlet.resource.DefaultSerltHttpRequestHandler,它将充当一个检查员的角色,对进入DispatcherServlet的URL进行筛查。如果发现是静态资源的请求,就将该请求转由Web应用服务器默认的Servlet处理;如果不是静态资源的请求,则由DispatcherServlet继续处理。

一般Web应用服务器(包括Tomcat、 Jetty、 Glassfish、 JBoss、 Resin、 WebLogic和WebSphere) 默认的Servlet名称都是default,因此,DefaultServletHttpRequestHandler可以找到它。如果用户所使用的Web应用服务器的默认Servlet名称不是default,则需要通过default-servlet-name属性显式指定。

<mvc:default-servlet-handler default-servlet-name= "yourServer Default Servlet Name"/>

2.采用<mvc:resources/>

<mvc:default-servlet-handler/>将 静态资源的处理经由Spring MVC框架交回Web应用服务器。而<mvc:resources/>更进一 步, 由Spring MVC框架自己处理静态资源,并添加一些有用的附加功能。

首先,<mvc:resources/> 允许静态资源放置在任何地方,如WEB-INF目录下、类路径下等,甚至可以将JavaScript 等静态文件打包到JAR包中。通过location 属性指定静态资源的位置,由于location属性是Resource类型,因此可以使用诸如“classpath:”等的资源前缀指定资源位置。传统Web容器的静态资源只能放在Web容器的根路径下,<mvc:resources/>则完全打破了这个限制。

其次,<mvc:resources/>依 据当前著名的Page Speed、YSlow 等浏览器优化原则对静态资源提供优化。可以通过cacheSeconds属性指定静态资源在浏览器端的缓存时间,一般可将该时间设置为一年,以充分利用浏览器端的缓存。在输出静态资源时,会根据配置设置好响应报文头的Expires和Cache-Control值。

在接收到静态资源的获取请求时,会检查请求头的Last-Modified值。如果静态资源没有发生变化,则直接返回303响应状态码,指示客户端使用浏览器缓存的数据,而非将静态资源的内容输出到客户端,以充分节省带宽,提高程序性能。

在smart-servlet.xml中添加以下配置:

<mvc: resources mapping="/resources/ **"location="/, classpath: /META-INE /publicResources/"/>

以上配置将Web根路径“/”及类路径/META-INF/publicResources/下的目录映射为/resources路径。假设Web根路径下拥有images 和js这两个资源目录,则可以通过如图17-19所示的方式引用静态资源。

假设类路径/META-INF/publicResources/下还拥有images/bg1.gif 和js/test1.js, 则也可以在网页中通过/resources/images/bg1.gif和/resources/js/test1.js进行引用,如代码清单17-54所示。

由于<mvc:resources/>可以将多个物理路径映射为一个逻辑路径,因此,一个用逻辑路径表示的资源在多个物理路径下都存在。对于这个问题,<mvc:resources/>的处理机制是,只要在一个物理路径下找到匹配的资源后就返回,查找的顺序和物理路径在location中的配置顺序一致。

聪明的读者可能会问:既然将Web根路径“/” 映射为“/resources/**”,是否可以在网页中通过“/resources/WEB-INF/web.xml”访问这个敏感的文件呢?答案是否定的。SpringMVC在处理映射的静态资源时,会查看引用路径是否包含WEB-INF或META-INF。如果包括,则直接返回null值,以保护安全文件不泄露出去。当然,如果将/WEB-INF/设置在location 属性中,则可以通过/resources/web.xml 的URL查看到web.xml。

<mvc: resources mapping="/ resources /** location=" /WEB- INF/"/>

所以使用<mvc:resources/>时需要特别注意,不要一不小心将 不期望暴露的资源泄露出去。

通过<mvc:resources/>的cacheperiod属性可以设置静态资源在客户端浏览器中的缓存有效时间。

<mvc :resources mapping=" / resources/**"location="/ ,classpath: /META- INE/ publicResources/" cache-period "31536000"/>

一般情况下, 将cache-period设置为一年,以便充分利用客户端的缓存数据。在发布新版本的应用时,即使服务器端的JavaScript、 CSS等静态资源文件已经发生了变化,但是由于客户端浏览器本身缓存管理机制的问题,客户端并不会从服务器端下载新的静态资源。一个好的解决办法是:网页中引用静态资源的路径添加应用的发布版本号,这样在发布新的部署版本时,由于版本号的变更造成网页中静态资源路径发生更改,从而使这些静态资源成为“新的资源”,客户端浏览器就会下载这个“新的资源”,而不会使用缓存中的数据。针对这个解决思路,可以通过<mvc:resources/>的静态资源逻辑路径给出一个通用的解决方案。

将发布版本号包含到<mvc:resources/>的静态资源逻辑路径中。首先创建一个ServletContextAware实现类,如代码清单17-55所示。

在ResourcePathExposer中获取应用程序的发布版本号,产生一个带版本号的静态资源路径resourceRoot,同时将其值发布到ServletContext 中,这样JSP文件就可以通过$ {resourceRoot}引用其值了。

接下来要调整smart-servlet.xml中的配置,以便使用带版本的静态资源逻辑路径。

<bean id="rpe" class =" com . smart . web . ResourcePathExposer"① init-method ="init"/>

<mvc: resources mapping="# {rpe . resourceRoot}/**②location="/" cache-period="31536000"/>

在①处配置好ResourcePathExposer,并指定其初始化方法为init(),以便在容器启动时让其初始化resourceRoot的值。由于其实现了ServletContextAware 接口,因此,Spring会在初始化该Bean时将ServletContext引用注入进来。

在②处通过Spring EL表达式引用ResourcePathExposer 的resourceRoot属性值,生成动态的静态资源逻辑路径。

最后调整网页中引用静态资源的方式,如代码清单17-56所示。

由于①处引用的resourceRoot 值和<mvc:resources/>通过# {rpe.resourceRoot}引用的值是一样的,所以可以正确访问到物理静态资源。这样,在每次发布新版本后,随着发布版本号的更改,客户端就会自动下载新的静态资源。

装配拦截器

当收到请求时,DispatcherServlet 将请求交给处理器映射( HandlerMapping),让它找出对应该请求的HandlerExecutionChain对象。在讲解HandlerMapping之前,有必要认识一下 这个HandlerExecutionChain对象。

HandlerExecutionChain顾名思义是一个执行链,它包含-一个处理该请求的处理器( Handler),同时包括若千个对该请求实施拦截的拦截器( HandlerInterceptor)。当HandlerMapping返回HandlerExecutionChain 后,DispatcherServlet 将请求交给定义在HandlerExecutionChain中的拦截器和处理器一并处理。

HandlerExecutionChain是负责处理请求并返回ModelAndView的处理执行链,其结构如图17-20 所示。请求在被Handler执行的前后,链中装配的HandlerInterceptor会实施拦截操作。

拦截器到底做了什么事情?我们通过考查拦截器的几个接口方法进行了解。

  • boolean preHandle(HttpServletRequest request, HttpServletResponse response, Objecthandler):在请求到达Handler 之前,先执行这个前置处理方法。当该方法返回false时,请求直接返回,不会传递到链中的下一个拦截器,更不会传递到处理器链末端的Handler中。只有返回true时,请求才向链中的下一个处理节点传递。
  • void postHandle(HttpServletRequest request, HtpServletResponse response, Objecthandler, ModelAndView modelAndView):在请求被HandlerAdapter 执行后,执行这个后置处理方法。
  • void afterCompletion(HttpServletRequest request, HttpServletResponse response,Object handler, Exception ex);在响应已经被渲染后,执行该方法。
<mvc: interceptors>
<mvc: interceptor>
<mapping path="/secure/*"/>
<bean class="com. smart. web. MyInterceptor" />
</mvc: interceptor>
</mvc: interceptors>

位于处理器链末端的是一个Handler, DispatcherServlet 通过HandlerAdapter适配器对Handler进行封装,并按统一的适配器接口对Handler处理方法进行调用。

可以在smart-servlet.xml中配置多个拦截器,每个拦截器都可以指定一个匹配的映射路径,以限制拦截器的作用范围。

异常处理

Spring MVC通过HandlerExceptionResolver处理程序的异常,包括处理器映射、数据绑定及处理器执行时发生的异常。HandlerExceptionResolver仅有一个接口方法。

ModelAndView resolveException (HttpServletRequest request, HttpServletResponse response,Object handler,Exception ex)

当发生异常时,Spring MVC将调用resolveException()方法,并转到ModelAndView对应的视图中,作为一个异常报告页面反馈给用户。

HandlerExceptionResolver拥有4个实现类,分别是DefaultHandlerExceptionResolver、SimpleMappingExceptionResolver、AnnotationMethodHandlerExceptionResolver 及 ResponseStatusExceptionResolver。

DefaultHandlerExceptionResolver

Spring MVC默认装配了DefaultHandlerExceptionResolver,它会将Spring MVC框架的异常转换为相应的响应状态码,具体说明如表17-9所示。

可以在 web.xml中为响应状态码配置一个对应的页面,代码如下:

AnnotationMethodHandlerExceptionResolver

Spring MVC 默认注册了AnnotationMethodHandlerExceptionResolver,它允许通过@ExceptionHandler注解指定处理特定异常的方法,如代码清单17-57所示。

①处的处理方法在调用时抛出一个RuntimeException异常,它会被处于同一处理器类中②处的 handleException()方法捕获。@ExceptionHandler可以指定多个异常,如@ExceptionHandler({AException.class,BException.class})。异常处理方法的标签非常灵活,请参见ExceptionHandler 的 Javadoc文档。

不清楚是Spring MVC的 Bug 还是有意为之,标注@ExceptionHandler的异常处理方法只能对同一处理类中的其他处理方法进行异常响应处理,笔者现在还受困于@ExceptionHandler的这个限制中。

ResponseStatusExceptionResolver和 ResponseStatusExceptionResolver 类似,允许通过@ResponseStatus注解标注一个方法,用于处理特定类型的响应状态码。

SimpleMappingExceptionResolver

如果希望对所有异常进行统一处理,则可以使用SimpleMappingExceptionResolver,它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常。

请看下面的异常映射配置片段:

在①处指定当控制器发生 DataAccessException异常时,使用dataAccessFailure视图显示。在②处指定当控制器发生TransactionException异常时,使用dataAccessFailure视图显示。

当然,用户也可以自己实现ExceptionResolver覆盖resolveException()接口方法,编写自己的异常解析器,执行一些特定的工作,如将异常信息保存到数据库中等。编写好自定义的ExceptionResolver,在smart-servlet.xml中注册成一个 Bean即可工作

RequestContextHolder的使用

posted @ 2022-06-05 22:55  EA2218764AB  阅读(571)  评论(0编辑  收藏  举报