zoukankan      html  css  js  c++  java
  • SpringMVC 学习笔记

    一、Hello World

    新建 “Dynamic Web Project” 工程。

    1.导包

    导入如下 jar 包:

    并将这些 jar 包加入到类路径下。另外,对于 Eclipse 还需要将这些 jar 包发布到 web 应用的部署目录,方法是在项目上右键->properties->Deployment Assembly,点击右侧窗口上的“Add...”按钮,在接下来弹出窗口中选择“Java Build Path Entries”点击下一步,最后在弹窗中选中这些 jar 包添加即可,如下图所示。

    2.配置 web 应用的 web.xml,在改配置文件中加入如下内容:

    <servlet>
    <servlet-name>springDispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- 
    也可以不配置这个参数,而使用 SpringMVC 默认的 配置文件。
    默认的配置文件在 WEB-INF目录下,且文件名为[当前servlet的名字]-servlet.xml,
    如 springDispatcherServlet-servlet.xml
    -->
    <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    </servlet>
    ​
    ​
    <servlet-mapping>
    <servlet-name>springDispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
    </servlet-mapping>

    3.配置 SpringMVC 的配置文件。SpringMVC配置文件本质上是一个Spring的配置文件。在配置文件中添加如下内容:

    <context:component-scan base-package="com.ldj.springmvc.handlers"/>
    <!-- 
    配置视图解析器: 如何把 handler 方法返回值解析为实际的物理视图。
    解析规则是:[prefix]+handler方法返回值(String)+[suffix],通过这个值去找实际的视图文件 
    -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/" />
    <property name="suffix" value=".jsp"/>
    </bean>

    4.编写 handler 类及方法

    package com.ldj.springmvc.handlers;
    ​​
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    public class HelloWorld {
    ​
    @RequestMapping("/helloworld")
    public String hello(){
    System.out.println("hello()...");
    return "success";
    }
    }

    5.编写视图

    按照第3步中的配置,在 WEB-INF 下建立 views 文件夹,并根据第4步中 hello() 的返回值,在 views 目录下建立 success.jsp 文件(内容随意),这个文件即是 handler 方法 hello() 期望返回的视图。

    至此 SpringMVC 版本的 Hello World 程序构建完毕。

    二、使用 @RequestMapping 映射请求

    SpringMVC 使用 @RequestMapping 为控制器指定可以处理哪些 URL 请求。当 DispatcherServlet 截获请求后,通过控制器上 @RequestMapping 提供的映射信息确定请求所对应的处理方法。

    在控制器的类定义以及方法定义处都可标注 @RequestMapping,区别如下:

    (1)类定义处:提供初步的请求映射信息,相对于WEB应用的根目录;

    (2)方法处:提供进一步的细分映射信息,相对于类定义处的 URL,若类定义处没有标注@RequestMapping,则方法处标记的 URL 相对于WEB应用的根目录。

    2.1 映射请求参数、请求方法或请求头

    @RequestMapping:

    • value 映射请求URL;
    • method 映射请求方法(get、post等);
    • params 映射请求参数;
    • headers 映射请求头。

    @RequestMapping 这四个属性之间是“与”的关系,例如如果有“@RequestMapping(value="abc/xyz.jsp",method="post")”,则其意义是映射 URL 为 abc/xyz.jsp 且请求方法为 post 的请求。另外,params 和 headers 支持简单的表达式(等于或者不等于)

    • param1:表示请求必须包含名为 param1 的请求参数.
    • !param1:表示请求不能包含名为 param1 的请求参数.
    • param1!=value1:表示请求包含名为 param1 的请求参数,且其值不能为 value1.
    • {"param1=value1", "param2"}:表示请求必须包含名为 param1 和 param2 的两个请求参数,且 param1 参数的值必须为 value1.

    2.2 映射 URL 中的通配符

    @RequestMapping 支持 Ant 风格的 URL。所谓 Ant 风格的资源地址仅支持3种通配符:?(匹配文件名中的一个字符)、*(匹配文件名中的任意个字符)、**(匹配多层路径)。例如:
    /user/*/createUser,匹配 /user/aaa/createUser、/user/bbb/createUser 等;
    /user/**/createUser,匹配 /user/createUser、/user/aaaa/bb/createUser 等;
    /user/createUser??,匹配 /user/createUseraa、/user/createUserbb 等。


    2.3 @PathVariable 映射 URL 绑定的占位符

    带占位符的 URL 是 Spring3.0 新增的功能,该功能在 SpringMVC 向 REST 目标挺进发展过程中具有里程碑的意义。URL 中的 {xxx} 占位符可以通过 @PathVariable("xxx") 绑定到操作方法的入参中。

    2.4 REST

    简单的来说,REST 风格的 URL 就是使用 HTTP 协议中四个表示操作方式的动词 GET/POST/DELETE/PUT 来对应四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源。具体实现如下所述。

    (1)在 web.xml 中配置 HiddenHttpMethodFilter (org.springframework.web.filter.HiddenHttpMethodFilter);

    (2)需要发送 POST 请求(即使用表单);

    (3)需要在发送 POST 请求时携带一个 name="_method" 的隐藏域,值为 DELETE 或 PUT(表单只支持 GET/POST 方法);

    (4)在 SpringMVC 的目标方法中使用 @PathVariable 注解得到相关参数值(例如 id)。

    三、映射请求参数

    Spring MVC 通过分析处理方法的签名,将 HTTP 请求信息绑定到处理方法的相应人参中。绑定的方式是通过对方法或方法的参数添加相关注解来实现的。常用的注解有:@PathVariable(2.3小节)、@RequestParam、@RequestHeader、@CookieValue等。

    3.1 @RequestParam

    @RequestParam映射请求参数:

    属性  说明 数据类型 默认值
    value  请求参数的参数名 String  ""
    required  请求参数是否必须 Boolean  true
    defaultValue   请求参数的默认值 String  " ue000ue001ue002 "


    3.2 @PathVariable

    @RequestParam映射 URI 中的占位符

    属性 说明 数据类型 默认值
    value  路径中占位符的名称 String  ""
    required  路径中占位符是否必须 Boolean  true




    3.3 @RequestHeader

    @RequestHeader映射请求头

    属性 说明 数据类型 默认值
    value  请求头的名称 String  ""
    required  请求头是否必须 Boolean  true
    defaultValue   请求头的默认值 String   " ue000ue001ue002 "



    3.4 @CookieValue

    @CookieValue 映射 Cookie

    属性 说明 数据类型 默认值
    value  Cookie 名  String  ""
    required  该 Cookie 是否必须存在 Boolean  true
    defaultValue   该 Cookie 的默认值 String 

    " ue000ue001ue002 "

           

     
     

    3.5 使用 POJO 对象绑定请求参数值

    SpringMVC 会按请求参数名和 POJO 属性名进行自动匹配,自动为该对象填充属性值,支持级联属性。如:address.city、address.province 等。

    3.6 使用 Servlet API 作为入参

    SpringMVC 的 Handler 方法可以接受以下 ServletAPI 类型的参数:

    • HttpServletRequest
    • HttpServletResponse
    • HttpSession
    • java.security.Principal
    • Locale
    • InputStream
    • OutputStream
    • Reader
    • Writer


    四、处理模型数据

    SpringMVC 处理模型数据即把请求结果所需的信息传递到目标页面。有四种途径:ModelAndView、Map 及 Model、@SessionAttributes、@ModelAttribute。

    4.1 ModelAndView

    控制器处理方法的返回值如果为 ModelAndView, 则其既包含视图信息,也包含模型数据信息。SpringMVC 会把 ModelAndView 的 model 中数据放入到 request 域对象中。我们可以在处理方法中向 ModelAndView 对象添加所需的数据。

    4.2 Map 及 Model

    SpringMVC 在内部使用了一个 org.springframework.ui.Model 接口存储模型数据,在调用方法前 SpringMVC 会创建一个该接口的隐含的模型对象作为模型数据的存储容器。如果控制器处理方法的入参为 Map 或 Model 类型,Spring MVC 会将隐含模型的引用传递给这些入参。在方法体内,可以通过这个入参对象访问到模型中的所有数据,也可以向模型中添加新的信息。

    4.3 @SessionAttributes

    若希望在多个请求之间共用某个模型属性数据,则可以在控制器类上标注一个 @SessionAttributes,SpringMVC 将把模型中对应的属性暂存到 HttpSession 中。@SessionAttributes 除了可以通过属性名(通过该注解的 value 属性)指定需要放到会话中的属性外,还可以通过模型属性的对象类型(通过该注解的 types 属性)指定哪些模型属性需要放到会话中,且这两种方式是并行的,举例如下。

    •  @SessionAttributes(types=User.class) 会将隐含模型中所有类型为 User.class 的属性添加到会话中.
    •  @SessionAttributes(value={“user1”, “user2”}) 会将隐含模型中名为 user1、user2 的属性添加到会话中.
    •  @SessionAttributes(types={User.class, Dept.class}) 会将隐含模型中所有类型为 User.class、Dept.class 的属性添加到会话中.
    •  @SessionAttributes(value={“user1”, “user2”},types={Dept.class}) 会将隐含模型中名为 user1、user2 或者所有类型为 Dept 的属性添加到会话中.

    注意:该注解只能修饰类,而不能修饰放方法。

    4.4 @ModelAttribute

    需求引入:

    如上图,从页面表单获取到对象的更新字段后,写入数据时,没被更新的字段(或者业务逻辑上不应该被更新的字段等)被置空了。因为每次都是重新创建一个对象,而不是从数据库中获取。

    在方法定义上使用 @ModelAttribute 注解:SpringMVC 在调用目标处理方法前,会先逐个调用在方法级上标注了 @ModelAttribute 的方法。在方法的入参前使用 @ModelAttribute 注解:可以从隐含对象的模型数据中获取对象,再将请求参数绑定到对象中,再传入入参;将方法入参对象添加到模型中。

     

     
    源码分析

    运行流程

    1. 执行 @ModelAttribute 注解修饰的方法:从数据库中取出对象,把对象放入到了 Map 中,键为 user;

    2. SpringMVC 从 Map 中取出 User 对象,并把表单的请求参数赋给该 User 对象的对应属性;

    3. SpringMVC 把上述对象传入目标方法的参数。

    注意:在 @ModelAttribute 修饰的方法中,放入到 Map 时的键需要和目标方法入参类型的第一个字母小写的字符串一致。


    SpringMVC 确定目标方法 POJO 类型入参的过程

    1. 确定一个 key:

    (1)若目标方法的 POJO 类型的参数没有使用 @ModelAttribute 作为修饰,则 key 为 POJO 类名第一个字母的小写;

    (2)若使用了  @ModelAttribute 来修饰,则 key 为 @ModelAttribute 注解的 value 属性值。

    2. 在 implicitModel 中查找 key 对应的对象,若存在,则作为入参传入

    (1)若在 @ModelAttribute 标记的方法中在 Map 中保存过,且 key 和 步骤1确定的 key 一致,则会获取到。 

    3. 若 implicitModel 中不存在 key 对应的对象,则检查当前的 Handler 是否使用 @SessionAttributes 注解修饰。若使用了该注解,且 @SessionAttributes 注解的 value 属性值中包含了 key,则会从 HttpSession 中来获取 key 所对应的 value 值,若存在则直接传入到目标方法的入参中. 若不存在则将抛出异常.。

    4. 若 Handler 没有标识 @SessionAttributes 注解或 @SessionAttributes 注解的 value 值中不包含 key,则会通过反射来创建 POJO 类型的参数,传入为目标方法的参数。

    5. SpringMVC 会把 key 和 POJO 类型的对象保存到 implicitModel 中,进而会保存到 request 中。 


    源代码分析的流程

    1. 调用 @ModelAttribute 注解修饰的方法。实际上把 @ModelAttribute 方法中 Map 中的数据放在了 implicitModel 中。

    2. 解析请求处理器的目标参数,实际上该目标参数来自于 WebDataBinder 对象的 target 属性。

    (1)创建 WebDataBinder 对象

         ①. 确定 objectName 属性:若传入的 attrName 属性值为 "",则 objectName 为类名第一个字母小写;注意: 若目标方法的 POJO 参数使用了 @ModelAttribute 来修饰,则 attrName 值即为 @ModelAttribute 的 value 属性值 。

         ②. 确定 target 属性:

    • 在 implicitModel 中查找 attrName 对应的属性值,若存在,则可以成功返回;
    • 若不存在,则验证当前 Handler 是否使用了 @SessionAttributes 进行修饰;若使用了,则尝试从 Session 中获取 attrName 所对应的属性值;若 session 中没有对应的属性值,则抛出 HttpSessionRequiredException 异常。
    • 若 Handler 没有使用 @SessionAttributes 进行修饰,或 @SessionAttributes 中没有使用 value 值指定的 key 和 attrName 相匹配,则通过反射创建 POJO 对象。

    (2)SpringMVC 把表单的请求参数赋给了 WebDataBinder 的 target 对应的属性。 

    (3)*SpringMVC 会把 WebDataBinder 的 attrName 和 target 给到 implicitModel,进而传到 request 域对象中。 

    (4)把 WebDataBinder 的 target 作为参数传递给目标方法的入参。


    五、视图和视图解析器

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

    5.1 视图

    视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户。为了实现视图模型和具体实现技术的解耦,Spring 在 org.springframework.web.servlet 包中定义了一个高度抽象的 View 接口。

    视图对象由视图解析器负责实例化。由于视图是无状态的,所以不会有线程安全的问题。常用的视图实现类如下表所示。

    大类  视图类型 说明
    URL资源视图 InternalResourceView 将 JSP 或其它资源封装成一个视图,是 InternalResourceViewResolver 默认使用的视图实现类
    JstlView 如果 JSP 文件中使用了 JSTL 国际化标签的功能,则需要使用该视图类
    文档视图 AbstractExcelView Excel 文档视图的抽象类。该视图类基于 POI 构造 Excel 文档
    AbstractPdfView PDF 文档视图的抽象类。该视图类基于 iText 构造 PDF 文档
    报表视图 ConfigurableJasperReportsView  几个使用 JasperReports 报表技术的视图
    JasperReportsCsvView
    JasperReportsMultiFormatView
    JasperReportsHtmlView
    JasperReportsPdfView
    JasperReportsXlsView
    JSON 视图 MappingJacksonJsonView 将模型数据通过 Jackson 开源框架的 ObjectMapper 以 JSON 方式输出

    5.2 视图解析器

    SpringMVC 为逻辑视图名的解析提供了不同的策略,可在 Spring WEB 上下文中配置一种或多种解析策略,并指定他们之间的先后顺序。每个视图解析器都实现了 Ordered 接口并开放出一个 order 属性,可以通过 order 属性指定解析器的优先顺序,order  越小优先级越高。SpringMVC 会按视图解析器顺序的优先顺序对逻辑视图名进行解析,直到解析成功并返回视图对象,否则将抛出 ServletException 异常。

    每一种映射策略对应一个具体的视图解析器实现类。视图解析器的作用比较单一:将逻辑视图解析为一个具体的视图对象。所有的视图解析器都必须实现 ViewResolver 接口:

    常用的视图解析器实现类如下表所示。

    大类 视图解析器类型 说明
    解析为 Bean 的名字 BeanNameViewResolver 将逻辑视图名解析为一个 Bean,Bean 的 id 等于逻辑视图名
    解析为 URL 文件 InternalResourceViewResolver  将视图名解析为一个 URL 文件,一般使用该解析器将视图名映射为一个保存在 WEB-INF 目录下的程序文件(如 JSP)
    JasperReportsViewResolver  JasperReports 是一个基于 Java 的开源报表工具,该解析器将视图名解析为报表文件对应的 URL
    模板文件视图解析器 FreeMarkerViewResolver  解析为基于 FreeMarker 模板技术的模板文件 
    VelocityViewResolver 解析为基于 Velocity 模板技术的模板文件
    VelocityLayoutViewResolver

    5.3  <mvc:view-controller> 标签

    在 Spring 的配置文件中可以添加  <mvc:view-controller> 标签(其中 mvc 是引用)来实现直接转发到相应的页面,而不经过 Handler。用法如下:

    <!-- 
    path:相对于 WEB 应用根路径,指明哪些 URL 可以不经过 Handler 而直接返回视图
    view-name:直接返回的视图名
    -->
    <mvc:view-controller path="/success" view-name="success"/>

    配置了 <mvc:view-controller> 标签,还需要配置 <mvc:annotation-driven></mvc:annotation-driven> (在实际开发中通常都需配置该项目),否则会导致经由 Handler 方法返回的视图出现 404 错误。

    5.4 自定义视图

    自定义视图需要实现 org.springframework.web.servlet.View 接口,并在 Spring 配置文件中配置 BeanNameViewResolver 作为视图解析器即可。举例如下。

    (1)创建实现 View 接口的类 HelloView

     1 package com.ldj.springmvc.views;
     2 ​​
     3 import java.util.Date;
     4 import java.util.Map;
     5  6 import javax.servlet.http.HttpServletRequest;
     7 import javax.servlet.http.HttpServletResponse;
     8  9 import org.springframework.stereotype.Component;
    10 import org.springframework.web.servlet.View;
    11 12 13 @Component
    14 public class HelloView implements View {
    15 16 17 @Override
    18 public String getContentType() {
    19 return "text/html";
    20 }
    21 22 23 @Override
    24 public void render(Map<String, ?> arg0, HttpServletRequest request,
    25 HttpServletResponse response) throws Exception {
    26 response.getWriter().print("hello view, time: " + new Date());
    27 }
    28 }

    注意第13行的 @Component 注解,是为了使 HelloView 被 Spring 自动管理。

    (2)在 Spring 配置文件中启用 BeanNameViewResolver

    <!-- 
    配置视图解析器: 如何把 handler 方法返回值解析为实际的物理视图。
    解析规则是:[prefix]+handler方法返回值(String)+[suffix],通过这个值去找实际的视图文件 
    -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/" />
    <property name="suffix" value=".jsp"/>
    </bean>
    ​
    ​
    <!-- 
    配置视图 BeanNameViewResolver 解析器: 使用 Bean 的名字来解析视图 
    通过 order 属性来定义视图解析器的优先级, order 值越小优先级越高
    -->
    <bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
    <property name="order" value="100"></property>
    </bean>

    需要注意的是,配置的视图解析器的 order 属性,其值决定了该解析器的优先级,order 越小该解析器越优先被调用。通常越普通的解析器越靠后使用,InternalResourceViewResolver 最普通,其 order 属性默认值为 Integer.MAX_VALUE。

    (3)在 Handler 中使用

    @RequestMapping("/testCustomeView")
    public String testCustomeView(){
      System.out.println("testCustomeView");
      return "helloView";
    }

    5.5 关于重定向

    一般情况下,控制器方法返回字符串类型的值会被当成逻辑视图名处理。如果返回的字符串中带 forward: 或 redirect: 前缀,SpringMVC 会对他们进行特殊处理:将 forward: 和 redirect: 当成指示符,其后的字符串作为 URL 来处理。例如:redirect:success.jsp:会完成一个到 success.jsp 的重定向的操作;forward:success.jsp:会完成一个到 success.jsp 的转发操作。

    @RequestMapping("/testRedirect")
    public String testRedirect(){
      System.out.println("testRedirect");
      // 将会重定向到当前应用根目录下的 index.jsp
      // return "redirect:/index.jsp";
      // 将会转发到当前应用根目录下 WEB-INF/views/success.jsp
      return "forward:/WEB-INF/views/success.jsp";
    }


    六、Spring 表单标签

    通过 SpringMVC 的表单标签可以实现将模型数据中的属性和 HTML 表单元素相绑定,以实现表单数据更便捷编辑和表单值的回显。一般情况下,通过 GET 请求获取表单页面,而通过 POST 请求提交表单页面,因此获取表单页面和提交表单页面的 URL 是相同的。只要满足该最佳条件的约定,<form:form> 标签就无需通过 action 属性指定表单提交的 URL。

    可以通过 modelAttribute 属性指定绑定的模型属性,若没有指定该属性,则默认从 request 域对象中读取 command 的表单 bean,如果该属性值也不存在,则会发生错误。这是因为 Spring 认为表单总是应该被回显的,它总是试图找到合适的 Bean 来填充表单对应的项。如下表单中:

    <fm:form action="/springmvc-2/emp" method="post">
    Last Name:<fm:input path="lastName" /><br/>
    E-mail:<fm:input path="email"/><br/>
    <%
    Map<String,String> genders = new HashMap<>();
    genders.put("1", "Male");
    genders.put("0", "Female");
    request.setAttribute("genders", genders);
    %>
    Gender:<fm:radiobuttons path="gender" items="${genders }"/><br/>
    Department:<fm:select path="department.id" items="${requestScope.departments }" 
    itemLabel="departmentName" itemValue="id"></fm:select><br/>
    <input type="submit" value="Submit"/>
    </fm:form>

    第一行的 <fm:form> 没有指定 modelAttribute 属性,会导致到包含该表单的页面的请求发生如下的错误:java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'command' available as request attribute。解决方法是,在转发到该页面的 Handler 方法中添加一个空的对象,如下代码第五行所示:

    1 @RequestMapping(value="/emp",method=RequestMethod.GET)
    2 public String input(Map<String,Object> map){
    3   map.put("departments", deptDao.getDepartments());
    4   // 提供给表单页面进行回显,这里设置了 Bean 的名字为 employee,所以需要为表单设置 modelAttribute 属性为 "employee"字符串
    5   map.put("employee", new Employee()); 
    6   return "input";
    7 }

    SpringMVC 提供了多个表单组件标签,如 <form:input/>、<form:select/> 等,用以绑定表单字段的属性值,它们的共有属性如下:

    • path:表单字段,对应 html 元素的 name 属性,支持级联属性;
    • htmlEscape:是否对表单值的 HTML 特殊字符进行转换,默认值为 true;
    • cssClass:表单组件对应的 CSS 样式类名;
    • cssErrorClass:表单组件的数据存在错误时,采取的 CSS 样式

    常见的 SpringMVC 标签如下表所示:

    标签名 对应html标签 说明
    form:input text  
    form:password password  
    form:hidden hidden  
    form:textarea textarea   
    form:radiobutton    单选框组件标签,当表单 bean 对应的属性值和 value 值相等时,单选框被选中
    form:radiobuttons    单选框组标签,用于构造多个单选框,其重要属性如下:
    - items:可以是一个 List、String[] 或 Map
    - itemValue:指定 radio 的 value 值。可以是集合中 bean 的一个属性值
    - itemLabel:指定 radio 的 label 值
    - delimiter:多个单选框可以通过 delimiter 指定分隔符
    form:checkbox    复选框组件,用于构造单个复选框
    form:checkboxs   用于构造多个复选框,使用方式同 form:radiobuttons 标签
    form:select    用于构造下拉框组件,使用方式同 form:radiobuttons 标签
    form:option   下拉框选项组件标签,使用方式同 form:radiobuttons 标签
    form:errors    显示表单组件或数据校验所对应的错误:
    - <form:errors path= “ *” /> :显示表单所有的错误
    - <form:errors path= “ user*” /> :显示所有以 user 为前缀的属性对应的错误
    - <form:errors path= “ username” /> :显示特定表单对象属性的错误

          
     
















    七、处理静态资源

    优雅的 REST 风格的资源URL 不希望带 .html 或 .do 等后缀。若将 DispatcherServlet 请求映射配置为 /,则 Spring MVC 将捕获 WEB 容器的所有请求,包括静态资源的请求, SpringMVC 会将它们当成一个普通请求处理,因找不到对应处理器将导致错误。

    可以在 SpringMVC 的配置文件中配置 <mvc:default-servlet-handler/> 的方式解决静态资源的问题。<mvc:default-servlet-handler/> 将在 SpringMVC 上下文中定义一个 DefaultServletHttpRequestHandler,它会对进入 DispatcherServlet 的请求进行筛查,如果发现是没有经过映射的请求,就将该请求交由 WEB 应用服务器默认的 Servlet 处理,如果不是静态资源的请求,才由 DispatcherServlet 继续处理。

    一般 WEB 应用服务器默认的 Servlet 的名称都是 default。若所使用的 WEB 服务器的默认 Servlet 名称不是 default,则需要通过 default-servlet-name 属性显式指定。


    八、数据转换 & 数据格式化 & 数据校验

    表单页面接受的用户输入数据都是字符串类型的,然后到 Handler 方法时,SpringMVC 却可以自动将其转换成 POJO 对象,这里就涉及到了数据的转换、格式化以及验证的过程。

    8.1 数据绑定流程

    (1)Spring MVC 主框架将 ServletRequest 对象及目标方法的入参实例传递给 WebDataBinderFactory 实例,以创建 DataBinder 实例对象。

    (2)DataBinder 调用装配在 Spring MVC 上下文中的 ConversionService 组件进行数据类型转换、数据格式化工作。将 Servlet 中的请求信息填充到入参对象中。

    (3)调用 Validator 组件对已经绑定了请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果 BindingData 对象。

    (4)SpringMVC 抽取 BindingResult 中的入参对象和校验错误对象,将它们赋给处理方法的响应入参。

    Spring MVC 通过反射机制对目标处理方法进行解析,将请求消息绑定到处理方法的入参中。数据绑定的核心部件是 DataBinder,运行机制如下:

    Spring MVC 上下文中内建了很多转换器,可完成大多数 Java 类型的转换工作。

    ConversionService converters =  
    java.lang.Boolean -> java.lang.String : org.springframework.core.convert.support.ObjectToStringConverter@f874ca
    java.lang.Character -> java.lang.Number : CharacterToNumberFactory@f004c9
    java.lang.Character -> java.lang.String : ObjectToStringConverter@68a961
    java.lang.Enum -> java.lang.String : EnumToStringConverter@12f060a
    java.lang.Number -> java.lang.Character : NumberToCharacterConverter@1482ac5
    java.lang.Number -> java.lang.Number : NumberToNumberConverterFactory@126c6f
    java.lang.Number -> java.lang.String : ObjectToStringConverter@14888e8
    java.lang.String -> java.lang.Boolean : StringToBooleanConverter@1ca6626
    java.lang.String -> java.lang.Character : StringToCharacterConverter@1143800
    java.lang.String -> java.lang.Enum : StringToEnumConverterFactory@1bba86e
    java.lang.String -> java.lang.Number : StringToNumberConverterFactory@18d2c12
    java.lang.String -> java.util.Locale : StringToLocaleConverter@3598e1
    java.lang.String -> java.util.Properties : StringToPropertiesConverter@c90828
    java.lang.String -> java.util.UUID : StringToUUIDConverter@a42f23
    java.util.Locale -> java.lang.String : ObjectToStringConverter@c7e20a
    java.util.Properties -> java.lang.String : PropertiesToStringConverter@367a7f
    java.util.UUID -> java.lang.String : ObjectToStringConverter@112b07f 
    ……

    8.2 自定义类型转换器

    ConversionService 是 Spring 类型转换体系的核心接口。可以利用 ConversionServiceFactoryBean 在 Spring 的 IOC 容器中定义一个 ConversionService。Spring 将自动识别出 IOC 容器中的 ConversionService,并在 Bean 属性配置及 SpringMVC 处理方法入参绑定等场合使用它进行数据的转换。可通过 ConversionServiceFactoryBean 的 converters 属性注册自定义的类型转换器。

    Spring 定义了 3 种类型的转换器接口,实现任意一个转换器接口都可以作为自定义转换器注册到ConversionServiceFactroyBean 中:

    • Converter<S,T>:将 S 类型对象转为 T 类型对象;
    • ConverterFactory:将相同系列多个 “同质” Converter 封装在一起。如果希望将一种类型的对象转换为另一种类型及其子类的对象(例如将 String 转换为 Number 及 Number 子类 Integer、Long、Double 等对象)可使用该转换器工厂类;
    • GenericConverter:会根据源类对象及目标类对象所在的宿主类中的上下文信息进行类型转换。

    具体步骤如下:

    (1)定义一个转换器

    package com.ldj.springmvc.converters;
    ​
    ​import org.springframework.core.convert.converter.Converter;
    import com.ldj.springmvc.crud.entities.Department;
    import com.ldj.springmvc.crud.entities.Employee;
    ​
    public class EmployeeConverter implements Converter<String, Employee> {
    ​  @Override
      public Employee convert(String source) {
        if(source != null){
          //期望的字符串格式是:GG-gg@atguigu.com-0-105
          String[] vals = source.split("-");
          if(vals != null && vals.length == 4){
            String lastName = vals[0];
            String email = vals[1];
            Integer gender = Integer.parseInt(vals[2]);
            Department department = new Department();
            department.setId(Integer.parseInt(vals[3]));
            Employee emp = new Employee(null, lastName, email, gender, department);
            return emp;
          }
        }
        return null;
      }
    }

    (2)在 Spring 配置文件中配置

    首先添加 org.springframework.context.support.ConversionServiceFactoryBean 的 bean,为其 set 类型的 converts 属性赋值为 EmployeeConverter。其次,设置 mvc:annotation-driven 的 conversion-service 属性,使其引用到上文中定义的 conversionService,这样 SpringMVC 框架会将自定义的 ConversionService 注册到 SpringMVC 上下文中。

    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
      <property name="converters">
        <set>
          <bean class="com.ldj.springmvc.converters.EmployeeConverter"/>
        </set>
      </property>
    </bean>
    ​​
    <mvc:annotation-driven conversion-service="conversionService" />

    (3)使用

    <form action="testConversionService" method="post">
      <input type="text" name="employee"/>
      <input type="submit" value="submit"/>
    </form>

    在文本框中输入预定格式(在这里是形如 gg-xyz@123.com-1-104 格式的字符串)的字符串,在调用 Handler 方法之前,SpringMVC 框架就已经调用了我们自定义的转换器完成了数据格式的转换。

    @RequestMapping("/testConversionService")
    public String testConversionService(Employee employee){ 
      employeeDao.save(employee); 
      return "redirect:/emps";
    }

    8.3 关于 mvc:annotation-driven

    <mvc:annotation-driven /> 会自动注册 RequestMappingHandlerMapping、RequestMappingHandlerAdapter 与 ExceptionHandlerExceptionResolver 三个bean。还将提供以下支持:

    ①. 支持使用 ConversionService 实例对表单参数进行类型转换。

    ②. 支持使用 @NumberFormat annotation、@DateTimeFormat 注解完成数据类型的格式化。

    ③. 支持使用 @Valid 注解对 JavaBean 实例进行 JSR 303 验证。

    ④. 支持使用 @RequestBody 和 @ResponseBody 注解。


    @InitBinder

    由 @InitBinder 标识的方法,可以对 WebDataBinder 对象进行初始化。WebDataBinder 是 DataBinder 的子类,用于完成由表单字段到 JavaBean 属性的绑定。@InitBinder方法不能有返回值,它必须声明为void参数通常是 WebDataBinder。

    @InitBinder
    public void InitBinder(WebDataBinder binder){
    // 调用下面的方法后,框架将不再自动的对 lastName 字段赋值
    binder.setDisallowedFields("lastName");
    }


    8.4 数据格式化

    对属性对象的输入/输出进行格式化,从其本质上讲依然属于 “类型转换” 的范畴。Spring 在格式化模块中定义了一个实现 ConversionService 接口的 FormattingConversionService 实现类,该实现类扩展了 GenericConversionService,因此它既具有类型转换的功能,又具有格式化的功能。

    FormattingConversionService 拥有一个 FormattingConversionServiceFactroyBean 工厂类,后者用于在 Spring 上下文中构造前者。FormattingConversionServiceFactroyBean 内部已经注册了:

    ①. NumberFormatAnnotationFormatterFactroy:支持对数字类型的属性使用 @NumberFormat 注解;

    ②. JodaDateTimeFormatAnnotationFormatterFactroy:支持对日期类型的属性使用 @DateTimeFormat 注解。

    装配了 FormattingConversionServiceFactroyBean 后,就可以在 SpringMVC 入参绑定及模型数据输出时使用注解驱动了。注意,先需要在 SpringMVC 的配置文件中配置该类的 bean 才可以在字段上使用日期、数值格式化注解。

    <mvc:annotation-driven/> 默认创建的 ConversionService 实例即为 FormattingConversionServiceFactroyBean。

    8.4.1 日期格式化

    @DateTimeFormat 注解可对 java.util.Date、java.util.Calendar、java.long.Long 时间类型进行标注,重要属性如下:

    属性名 属性类型 取值范围 备注
    pattern   String    指定解析或格式化数据的模式,如:yyyy-MM-dd hh:mm:ss
    iso   DateTimeFormat.ISO ISO.NONE(不使用),默认值、
    ISO.DATE(yyyy-MM-dd) 、
    ISO.TIME(hh:mm:ss.SSSZ)、
    ISO.DATE_TIME(yyyy-MM-dd hh:mm:ss.SSSZ)
     
    style   String  由两位字符组成,第一位表示日期的格式,第二位表示时间的格式:
    S:短日 期/时间格式、
    M:中日期/时间格式、
    L:长日期/时间格式、
    F:完整 日期/时间格式、
    -:忽略日期或时间格式
    指定日期时间的格式

                   


     

    8.4.2 数值格式化

    @NumberFormat 可对类似数字类型的属性进行标注,它拥有两个互斥的属性:

    • style:类型为 NumberFormat.Style。用于指定样式类型,包括三种:Style.NUMBER(正常数字类型)、Style.CURRENCY(货币类型)、 Style.PERCENT(百分数类型);
    • pattern:类型为 String,自定义样式,如patter="#,###"(使用 # 代替数字,必须严格按照这里指定的位数和格式来输入)。

    8.5 数据校验

    JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 中。JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对 Bean 进行验证。

    注解 功能说明
    @Null 被注释的元素必须为 null
    @NotNull  被注释的元素必须不为 null 
    @AssertTrue 被注释的元素必须为 true
    @AssertFalse 被注释的元素必须为 false
    @Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
    @Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
    @DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
    @DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
    @Size(max, min) 被注释的元素的大小必须在指定的范围内
    @Digits(integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
    @Past 被注释的元素必须是一个过去的日期  
    @Future 被注释的元素必须是一个将来的日期
    @Pattern(value) 被注释的元素必须符合指定的正则表达式

             
         
           
       
     
     
     
     
     
     
     
       
         
     


    Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解:

    注解 功能说明
    @Email 被注释的元素必须是电子邮箱地址
    @Length 被注释的字符串的大小必须在指定的范围内
    @NotEmpty 被注释的字符串必须非空
    @Range  被注释的元素必须在合适的范围内

         
             
             
            
       

    8.5.1 SpringMVC 数据校验

    Spring 4.0 拥有自己独立的数据校验框架,同时支持 JSR 303 标准的校验框架。

    (1)Spring 本身并没有提供 JSR303 的实现,所以必须将 JSR303 的实现者(例如 Hibernate Validator) 的 jar 包加入到类路径下。

    (2)在 SpringMVC 配置文件中添加 <mvc:annotation-driven />。Spring 的 LocalValidatorFactroyBean 既实现了 Spring 的 Validator 接口,也实现了 JSR 303 的 Validator 接口。只要在 Spring 容器中定义一个 LocalValidatorFactoryBean,即可将其注入到需要数据校验的 Bean 中,而 <mvc:annotation-driven/> 会默认装配好一个 LocalValidatorFactoryBean 。

    (3)需要在 Bean 的属性上添加对应的注解。该 Bean 即是 SpringMVC 在进行数据绑定时,将要传递给 Handler 方法的入参。

    (4)在目标方法 Bean 类型的参数前面添加 @Valid 注解。

    通过以上步骤,即可让 SpringMVC 在完成数据绑定后执行数据校验的工作。

    8.5.2 在目标方法中获取校验结果

    SpringMVC 是通过对处理方法签名的规约来保存校验结果的:前一个表单/命令对象的校验结果保存到随后的入参中,这个保存校验结果的入参必须是 BindingResult 或 Errors 类型,这两个类都位于 org.springframework.validation 包中。常用方法:

    • FieldError getFieldError(String field)
    • List<FieldError> getFieldErrors()
    • Object getFieldValue(String field)
    • Int getErrorCount()
    @RequestMapping(value="/emp",method=RequestMethod.POST)
    public String save(@Valid Employee emp, BindingResult errors, Map<String, Object> map){
    
    if(errors.hasFieldErrors()){
    System.out.println("出错了!!!");
    for(FieldError fe : errors.getFieldErrors()){
    System.out.println(fe.getDefaultMessage());
    }
    //若验证出错, 则转向定制的页面
    map.put("departments", deptDao.getDepartments());
    return "input";
    }
    
    empDao.save(emp);
    return "redirect:emps";
    }

    注意:需校验的 Bean 对象和其绑定结果对象或错误对象时成对出现的,它们之间不允许声明其他的入参。例如,上例中的 save 方法参数中 emp 和 errors 必须紧挨着,map 参数可以放在它们的前面或后面,但不能放在它们中间。

    8.5.3 在页面上显示错误

    SpringMVC 除了会将表单/命令对象的校验结果保存到对应的 BindingResult 或 Errors 对象中外,还会将所有校验结果保存到 “隐含模型”,即使处理方法的签名中没有对应于表单/命令对象的结果入参,校验结果也会得到保存。隐含模型中的所有数据最终将通过 HttpServletRequest 的属性列表暴露给 JSP 视图对象,因此在 JSP 中可以获取错误信息。在 JSP 页面上可通过 <form:errors path="userName"> 显示错误消息。其中 path 为添加过验证的 Bean 的字段名。

     如上所示,对应的 JavaBean 的 lastName 添加了 @NotEmpty 验证:

    8.5.4 提示消息的国际化

    当使用 Spring MVC 标签显示错误消息时, Spring MVC 会查看 WEB 上下文是否装配了对应的国际化消息,如果没有,则显示默认的错误消息,否则使用国际化消息。

    (1)数据验证出错时的消息国际化

    定制国际化消息时,消息的键按如下规则生成:[校验注解类名].[被校验的 JavaBean 在请求域中的实例名(通常是类名的首字母小写)].[被校验的 JavaBean 的属性名]。消息的值即为所需的定制的消息。如:

    NotEmpty.employee.lastName=lastName不能为空.
    Email.employee.email=Email地址不合法.
    Past.employee.birth=Birth不能是现在或将来的日期.

    (2)数据转换出错时的消息国际化
    与数据校验消息定制类似,转换出错的消息的键按如下规则生成:[错误类型].[被校验的 JavaBean 在请求域中的实例名(通常是类名的首字母小写)].[被校验的 JavaBean 的属性名]。其中,错误类型包含以下几种:

    •    required:必要的参数不存在。如 @RequiredParam(“param1”)标注了一个入参,但是该参数不存在
    •    typeMismatch:在数据绑定时,发生数据类型不匹配的问题。
    •    methodInvocation:Spring MVC 在调用处理方法时发生了错误。

    如:typeMismatch.employee.birth=Birth必须是一个日期。


    九、处理 JSON:使用 HttpMessageConverter

    9.1 引例:通过 SpringMVC 响应 AJAX 请求,即返回 json 个数的数据

    (1)导入 Jackson 相关的 jar 包(注意版本对应,对于 Spring 4.x 需要 jackson-databind-2.5.0):jackson-annotations-2.5.5、jackson-databind-2.5.0、jackson-core-2.5.5;
    (2)编写目标方法,使其返回 JSON 对应的 Java 的对象或集合;
    (3)在目标方法上添加 @ResponseBody 注解。

    @ResponseBody
    @RequestMapping("/testJson")
    public Collection<Employee> testJson(){
      return employeeDao.getAll();
    }


    9.2 HttpMessageConverter<T>

    HttpMessageConverter<T> 是 Spring3.0 新添加的一个接口,负责将请求信息转换为一个对象(类型为 T),将对象(类型为 T)输出为响应信息。

    HttpMessageConverter<T> 接口定义的方法:

    • Boolean canRead(Class<?> clazz,MediaType mediaType): 指定转换器可以读取的对象类型,即转换器是否可将请求信息转换为 clazz 类型的对象,同时指定支持 MIME 类型(text/html,applaiction/json等)
    • Boolean canWrite(Class<?> clazz,MediaType mediaType):指定转换器是否可将 clazz 类型的对象写到响应流中,响应流支持的媒体类型在MediaType 中定义。
    • List<MediaType> getSupportMediaTypes():该转换器支持的媒体类型。
    • T read(Class<? extends T> clazz,HttpInputMessage inputMessage):将请求信息流转换为 T 类型的对象。
    • void write(T t,MediaType contnetType,HttpOutputMessgae outputMessage):将T类型的对象写到响应流中,同时指定相应的媒体类型为 contentType。

    HttpMessageConverter<T> 的实现类

    实现类 功能说明
    StringHttpMessageConverter 将请求信息转换为字符串
    FormHttpMessageConverter      将表单数据读取到 MultiValueMap 中
    XmlAwareFormHttpMessageConverter  扩展于 FormHttpMessageConverter,如果部分表单属性是 xml 数据,可用该转换器进行读取
    ResourceHttpMessageConverter  读写 org.springframework.core.io.Resource 对象
    BufferedImageHttpMessageConverter  读写 BufferedImage 对象
    ByteArrayHttpMessageConverter  读写二进制数据 
    SourceHttpMessageConverter  读写 javax.xml.transform.Source 类型的数据  
    MarshallingHttpMessageConverter  通过 Spring 的 org.springframework.xml.Marshaller 和 Unmarshaller 读写 xml 数据
    Jaxb2RootElemengHttpMessageConverter  通过 JAXB2 读写 xml 消息,将请求消息转换到标注 XmlRootElement 和 XxmlType 直接的类中
    MappingJackson2HttpMessageConverter  通过 Jackson 的 ObjectMapper 读写 Json 数据
    RssChannelHttpMessageConverter  能够读写 RSS 种子消息 
    AtomFeedHttpMessageConverter  和 RssChannelHttpMessageConverter 能够读写 RSS 种子消息

         
           
        

        

       
      



       


    SpringMVC 默认装配 RequestMappingHandlerAdapter ,而 RequestMappingHandlerAdapter 默认装配如下 HttpMessageConverter:

    上图中 MappingJackson2HttpMessageConverter 是因为加入 jackson jar 包后框架自动添加的。

    9.3 使用 HttpMessageConverter<T>

    使用 HttpMessageConverter<T> 将请求信息转化并绑定到处理方法的入参中或将响应结果转为对应类型的响应信息,Spring 提供了两种途径:

    ①. 使用 @RequestBody / @ResponseBody 对处理方法进行标注;

    ②. 使用 HttpEntity<T> / ResponseEntity<T> 作为处理方法的入参或返回值。

    当控制器处理方法使用到 @RequestBody/@ResponseBody 或 HttpEntity<T>/ResponseEntity<T> 时,Spring 首先根据请求头或响应头的 Accept 属性选择匹配的 HttpMessageConverter,进而根据参数类型或返回类型的过滤得到匹配的 HttpMessageConverter,若找不到可用的 HttpMessageConverter 将报错。

    @RequestMapping("/testResponseEntity")
    public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException{
        byte [] body = null;
        ServletContext servletContext = session.getServletContext();
        InputStream in = servletContext.getResourceAsStream("/files/abc.txt");
        body = new byte[in.available()];
        in.read(body);
        
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Disposition", "attachment;filename=abc.txt");
        
        HttpStatus statusCode = HttpStatus.OK;
        
        ResponseEntity<byte[]> response = new ResponseEntity<byte[]>(body, headers, statusCode);
        return response;
    }
    ​
    ​
    @ResponseBody
    @RequestMapping("/testHttpMessageConverter")
    public String testHttpMessageConverter(@RequestBody String body){
        System.out.println(body);
        return "helloworld! " + new Date();
    }

    如上面的例子,Spring 根据 testResponseEntity 目标方法的返回值类型将自动选用 ByteArrayHttpMessageConverter ,而针对 testHttpMessageConverter 将选用 StringHttpMessageConverter 。这两个请求对应的 JSP 代码如下:

    <form action="testHttpMessageConverter" method="POST" enctype="multipart/form-data">
        File: <input type="file" name="file"/>
        Desc: <input type="text" name="desc"/>
        <input type="submit" value="Submit"/>
    </form>
    <br/><br/>
    <a href="testResponseEntity">Test ResponseEntity</a>
    <br/>

    testHttpMessageConverter 请求使用了 form 表单,因为对于 GET 请求是没有 body 的,如果强行使用 GET 方式会出现:org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver handleHttpMessageNotReadable 错误。 

    注意:@RequestBody 和 @ResponseBody 不需要成对出现


    十、国际化

    关于国际化的三个经典需求:

    ①. 在页面上如何根据浏览器语言设置的情况对文本、时间、数值进行本地化处理;

    ②. 如何在 Bean 中获取国际化资源文件 Locale 对应的消息;

    ③. 可以通过超链接切换 Locale,而不再依赖于浏览器的语言设置情况。

    对于需求①,使用 JSTL 的 fmt 标签,结合国际化资源文件(需在 Spring 配置文件中配置 org.springframework.context.support.ResourceBundleMessageSource 的 Bean)即可解决;对于需求②,可以在 Handler 中添加 ResourceBundleMessageSource 注入对象即可通过它的相关方法获取。以下将针对需求③展开。

    默认情况下,SpringMVC 根据 Accept-Language 参数判断客户端的本地化类型。当接受到请求时,SpringMVC 会在上下文中查找一个本地化解析器(LocalResolver),找到后使用它获取请求所对应的本地化类型信息。SpringMVC 还允许装配一个动态更改本地化类型的拦截器,这样通过指定一个请求参数就可以控制单个请求的本地化类型。

    针对上文中的需求③,SpringMVC 默认使用的 AcceptHeaderLocaleResolver 并不能满足需求,我们需要:

    (1)配置 SessionLocaleResolver 和 LocaleChangeInterceptor 拦截器

    <bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"></bean>
    <mvc:interceptors>
      <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"></bean>
    </mvc:interceptors>

    (2)为页面上的超链接添加所需的 locale 参数

    <a href="i18n?locale=zh_CH">i18n Page 中文</a>
    <br/><br/>
    <a href="i18n?locale=en_US">i18n Page 英文</a>


    十一、文件上传

    SpringMVC 为文件上传提供了直接的支持,这种支持是通过即插即用的 MultipartResolver 实现的。Spring 用 Jakarta Commons FileUpload 技术实现了一个 MultipartResolver 实现类:CommonsMultipartResovler。为了让 CommonsMultipartResovler 正确工作,必须先将 Jakarta Commons FileUpload 及 Jakarta Commons io 的类包添加到类路径下;SpringMVC 上下文中默认没有装配 MultipartResovler,需显式配置。

    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
      <!-- 必须和用户 JSP 的 pageEncoding 属性 一致,以便正确解析表单的内容 -->
      <property name="defaultEncoding" value="UTF-8" />
      <property name="maxUploadSize" value="1024000" />
    </bean>

    使用的实例,JSP 页面代码:

    <form action="testMultipartResovler" method="POST" enctype="multipart/form-data">
      Desc: <input type="text" name="desc"/>
      File: <input type="file" name="file"/>
      <input type="submit" value="Submit"/>
    </form>

    Handler 方法代码:

    // 通过 MultipartFile 类型的入参可以获取待上传文件的对象
    @RequestMapping("/testMultipartResovler")
    public String testMultipartResovler(@RequestParam("desc") String desc, @RequestParam("file") MultipartFile file) throws IOException{
      System.out.println("desc:"+desc);
      System.out.println("OriginalFilename:"+file.getOriginalFilename());
      // 注意:这里使用输入流的 available() 方法更准确,因为在调用 MultipartFile.getSize() 时文件可能并没有真的上传,所以可能获取到的长度是 0
      System.out.println("Size:"+file.getInputStream().available());
      System.out.println("InputStream:"+file.getInputStream());
      return "success";
    }


    十二、使用拦截器

    Spring MVC也可以使用拦截器对请求进行拦截处理,用户可以自定义拦截器来实现特定的功能,自定义的拦截器必须实现 HandlerInterceptor 接口,该接口有如下三个方法:

    ①. preHandle():这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求 request 进行处理。如果决定该拦截器对请求进行拦截处理后还要调用其他的拦截器或业务处理器,则返回true;否则返回false。

    ②. postHandle():这个方法在业务处理器处理完请求后,但在 DispatcherServlet 向客户端返回响应前(渲染页面前)被调用,在该方法中对用户请求request进行处理。

    ③. afterCompletion():这个方法在 DispatcherServlet 完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。

    编写好自定义拦截器类后,需要在 SpringMVC 配置文件中对其进行配置。

    <mvc:interceptors> 
      <!-- 配置自定义的拦截器 -->
      <bean class="com.ldj.springmvc.interceptors.FirstInterceptor"></bean>
    
      <!-- 配置拦截器(不)起作用的路径,不起作用需用到 <mvc:exclude-mapping> 标签 -->
      <mvc:interceptor>
        <mvc:mapping path="/emps"/>
        <bean class="com.ldj.springmvc.interceptors.SecondInterceptor"></bean>
      </mvc:interceptor>
    </mvc:interceptors>

    多个拦截器执行顺序

    十三、异常处理

    SpringMVC 通过 HandlerExceptionResolver 处理程序的异常,包括 Handler 映射、数据绑定以及目标方法执行时发生的异常。SpringMVC 提供的 HandlerExceptionResolver 的实现类:

    SpringMVC 默认装配的 HandlerExceptionResolver:

    • 没有使用 <mvc:annotation-driven/> 配置

    •  使用了 <mvc:annotation-driven/> 配置

     

    13.1 ExceptionHandlerExceptionResolver

    主要处理 Handler 中用 @ExceptionHandler 注解定义的方法。该注解的 value 属性值是一个包含若干个异常类型的数组。

    (1)@ExceptionHandler 标注方法的入参中可以加入 Exception 类型的参数,该参数即对应发生的异常对象。

    (2)@ExceptionHandler 标注方法的入参中不能传入 Map,若希望把异常信息传导页面上,需要使用 ModelAndView 作为该方法的返回值。

    (3)若 Handler 中存在多个 @ExceptionHandler 标注方法,则当异常发生时总会调用异常类型匹配程度最高的方法。

    (4)@ControllerAdvice: Handler 方法发生异常时,若在该 Handler 中找不到 @ExceptionHandler 标记方法来处理该异常,则将去 @ControllerAdvice 标记的类中查找 @ExceptionHandler 标记的方法来处理异常。

    @RequestMapping("/testExceptionHandlerExceptionResolver")
    public String testExceptionHandlerExceptionResolver(@RequestParam("i") Integer i){
        System.out.println(10 / i);
        return "success";
    }​
    ​
    @ExceptionHandler({ArithmeticException.class})
    public ModelAndView handleArithmeticException(Exception ex){
        System.out.println("出错了:"+ex);
        ModelAndView mv = new ModelAndView("error");
        mv.addObject("exception", ex);
        return mv;
    }


    13.2 ResponseStatusExceptionResolver

    在异常及异常父类中找到 @ResponseStatus 注解,然后使用这个注解的属性进行处理。通常适用于自定义的异常类。

    @ResponseStatus(value=HttpStatus.FORBIDDEN, reason="用户名和密码不匹配!")
    public class UserNameNotMatchPasswordException extends RuntimeException {
    ​​    private static final long serialVersionUID = 1L;
    }

    例如上面定义了 UserNameNotMatchPasswordException 异常类,且用 @ResponseStatus 对其修饰。在 Handler 方法中使用如下代码:

    @RequestMapping("/testResponseStatusExceptionResolver")
    public String testResponseStatusExceptionResolver(@RequestParam("i") Integer i){
        if(i == 13){
            throw new UserNameNotMatchPasswordException();
        }
        System.out.println("testResponseStatusExceptionResolver...");
        return "success";
    }

    客户端请求效果如下图所示:

    总结起来,在 Handler 方法中抛出了带 @ResponseStatus 注解修饰的异常,而 ExceptionHandlerExceptionResolver 又不处理它的话,则会交由 ResponseStatusExceptionResolver 进行解析并最终返回 Http 状态码(如果有的话)reason 消息给客户端。

    13.3 SimpleMappingExceptionResolver

    如果希望对所有异常进行统一处理,可以使用 SimpleMappingExceptionResolver,它将异常类名映射为视图名。使用它时,需要在 SpringMVC 配置文件中进行配置。

    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
      <!-- 指定将异常对象放到请求域时所使用的名称,可在 JSP 页面通过 EL 表达式引用 -->
      <property name="exceptionAttribute" value="ex" />
      <property name="exceptionMappings">
        <props>
          <!-- prop 的 key 指定了将要处理的异常类型,prop 的值指定了视图名 -->
          <prop key="java.lang.ArrayIndexOutOfBoundsException">error</prop>
        </props>
      </property>
    </bean>

    如果要在 error 视图所关联的 JSP 页面使用异常对象,可通过 "${requestScope.ex}" 来引用,这里的 ex 即 exceptionAttribute 属性的值。Handler 方法如下:

    @RequestMapping("/testSimpleMappingExceptionResolver")
    public String testSimpleMappingExceptionResolver(@RequestParam("i") Integer i){
      String[] vals = new String[10];
      System.out.println(vals[i]);
      return "success";
    }

    客户端请求效果如下图所示:

    13.4 DefaultHandlerExceptionResolver

    对 SpringMVC 一些特殊的异常进行处理,比如:NoSuchRequestHandlingMethodException、HttpRequestMethodNotSupportedException、HttpMediaTypeNotSupportedException、HttpMediaTypeNotAcceptableException 等。 


    十四、其它问题

    14.1 SpringMVC 运行流程

     

    14.2 在 Spring 的环境下使用 SpringMVC

    SpringMVC 运行在 Spring 之上,可以在 SpringMVC 的配置文件中配置整个项目所需的 IOC 容器,达到利用 Spring 容器功能(如数据源、事务、 整合其他框架)的目的,此时就涉及到在 Spring 中整合 SpringMVC 的问题。这个问题的本质就是: Spring、SpringMVC 的配置文件如何放。

    需要明确的是,如果要同时使用 Spring 和 SpringMVC 则需要在 web.xml 中同时配置 Spring 的 ContextLoaderListener 和 SpringMVC 的 DispatcherServlet。

    <!-- 配置启动 Spring IOC 容器的 Listener -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:beans.xml</param-value>
    </context-param>
    
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    ​
    ​<!-- 配置 SpringMVC 的 DispatcherServlet -->
    <servlet>
        <servlet-name>springDispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>springDispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    关于配置文件如何放?

    (1)分开放,如上例的 web.xml 配置中,Spring IOC 的配置放在类路径下的 beans.xml 文件中,而 SpringMVC 的配置放在类路径下的 springmvc.xml 文件中。类似于数据源、事务、整合其他框架、Service 和 Dao 等都是放在 Spring 的配置文件中;而视图解析相关的配置放在 SpringMVC 的配置文件中。

    (2)放一起,都放在 SpringMVC 的配置文件中,也可以分多个 Spring 的配置文件,然后使用 import 节点导入其他的配置文件。

    推荐使用第一种方式。

    若 Spring 的 IOC 容器和 SpringMVC 的 IOC 容器扫描的包有重合的部分,就会导致有的 bean 会被创建 2 次。解决方法有两种。

    (1)使 Spring 的 IOC 容器扫描的包和 SpringMVC 的 IOC 容器扫描的包没有重合的部分(不推荐)。

    (2)使用 <context:component-scan> 标签的 exclude-filter 和 include-filter 子节点来规定只能扫描的注解:

    <!-- 在 SpringMVC 配置文件 springmvc.xml -->
    <context:component-scan base-package="com.ldj.springmvc" use-default-filters="false">
      <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
      <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
    </context:component-scan>

    注意,必须设置 use-default-filters="false" 才能生效。对应的,需要在 Spring IOC 配置文件中设置排除的包:

    <!-- 在 Spring IOC 配置文件 bean.xml -->
    <context:component-scan base-package="com.ldj.springmvc">
      <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
      <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
    </context:component-scan>

    SpringMVC 的 IOC 容器中的 bean 可以引用 Spring IOC 容器中的 bean,反之则不行,Spring IOC 容器中的 bean 不能引用 SpringMVC IOC 容器中的 bean。这是因为 SpringMVC 依赖于 Spring,即 Spring 的作用域更广而 SpringMVC 的更特化一些,所以 Spring IOC 容器中的 bean 不能引用 SpringMVC IOC 容器中的 bean。

    14.3 SpringMVC 对比 Struts2

    ①. SpringMVC 的入口是 Servlet,而 Struts2 是 Filter。

    ②. SpringMVC 会稍微比 Struts2 快些。Spring MVC 是基于方法设计,而 Sturts2 是基于类,每次发一次请求都会实例化一个 Action。

    ③. SpringMVC 使用更加简洁,开发效率Spring MVC确实比 struts2 高:支持 JSR303、处理 ajax 的请求更方便。

    ④. Struts2 的 OGNL 表达式使页面的开发效率相比 SpringMVC 更高些。


    相关源码:

    第一至六章,springmvc-1:https://git.oschina.net/laideju/springmvc-1

    第七至十三章,springmvc-2:https://git.oschina.net/laideju/springmvc-2

    第十四章,springmvc-3:无

  • 相关阅读:
    Scrum Meeting 11.11
    Scrum Meeting 11.10
    Scrum Meeting 11.09
    Scrum Meeting 11.08
    Scrum Meeting 11.07
    Scrum Meeting 11.06
    Scrum Meeting 11.05
    Scrum Meeting 11.04
    团队博客-应用功能说明书
    Scrum Meeting 11.03
  • 原文地址:https://www.cnblogs.com/itfky/p/13717532.html
Copyright © 2011-2022 走看看