SpringMVC

Posted by 余腾 on 2019-04-01
Estimated Reading Time 39 Minutes
Words 9.5k In Total
Viewed Times

一、SpringMVC 概述

Spring 为展现层提供的基于 MVC 设计理念的优秀的Web 框架,是目前最主流的 MVC 框架之一

  • Spring MVC 3.0 后全面超越 Struts2,成为最优秀的 MVC 框架
  • Spring MVC 通过一套 MVC 注解,让 POJO 成为处理请求的控制器,而无须实现任何接口
  • 支持 REST 风格的 URL 请求
  • 采用了松散耦合可插拔组件结构,比其他 MVC 框架更具扩展性和灵活性

Helloworld 步骤

  • 1、加入 jar 包
  • 2、在 web.xml 中配置 DispatcherServlet
  • 3、加入 Spring MVC 的配置文件
  • 4、编写处理请求的处理器,并标识为处理器
  • 5、编写视图

1、jar 包:

  • commons-logging-1.1.3.jar
  • spring-aop-4.0.0.RELEASE.jar
  • spring-beans-4.0.0.RELEASE.jar
  • spring-context-4.0.0.RELEASE.jar
  • spring-core-4.0.0.RELEASE.jar
  • spring-expression-4.0.0.RELEASE.jar
  • spring-web-4.0.0.RELEASE.jar
  • spring-webmvc-4.0.0.RELEASE.jar

2、配置 web.xml DispatcherServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 配置 DispatcherServlet -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置 DispatcherServlet 的一个初始化参数: 配置 SpringMVC 配置文件的位置和名称 -->
<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>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

配置 DispatcherServlet:

  • DispatcherServlet 默认加载 /WEB-INF/<servletName-servlet>.xml (dispatcherServlet-servlet.xml)的 Spring 配置文件
  • 启动 WEB 层的 Spring 容器。可以通过 contextConfigLocation 初始化参数自定义配置文件的位置和名称

3、加入 Spring MVC 的配置文件

  • 配置自动扫描的包

    1
    2
    <!-- 配置自定扫描的包 -->
    <context:component-scan base-package="springmvc"></context:component-scan>
  • 配置视图解析器

视图名称解析器:将视图逻辑名解析为: /WEB-INF/pages/<viewName>.jsp

1
2
3
4
5
<!-- 配置视图解析器: 如何把 handler 方法返回值解析为实际的物理视图 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>

4、创建请求处理器类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Controller
public class HelloWorld {

/**
* 1. 使用 @Controller 标注为请求处理器
* 2. 使用 @RequestMapping 注解来映射请求的 URL
* 3. 返回值会通过视图解析器解析为实际的物理视图, 对于 InternalResourceViewResolver 视图解析器,
*
* 会做如下的解析:
* 通过 prefix + returnVal + 后缀 这样的方式得到实际的物理视图, 然会做转发操作
* /WEB-INF/views/success.jsp
*/
@RequestMapping("/helloworld")
public String hello(){
System.out.println("hello world");
return "success";
}
}

5、页面

  • <a href = “helloworld”>Hello World 通过 @RequestMapping(“/helloworld”) 接收
  • return 到 /WEB-INF/views/success.jsp 页面


二、使用 @RequestMapping 映射请求

Spring MVC 使用 @RequestMapping 注解为控制器指定可以处理哪些 URL 请求。

  • 在控制器的类定义及方法定义处都可标注
    • @RequestMapping
      • 类定义处: 提供初步的请求映射信息。相对于 WEB 应用的根目录。
      • 方法处: 提供进一步的细分映射信息。相对于类定义处的 URL。
        • 若类定义处未标注 @RequestMapping,则方法处标记的 URL 相对于WEB 应用的根目录
  • DispatcherServlet 截获请求后,就通过控制器上@RequestMapping 提供的映射信息确定请求所对应的处理方法。


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

@RequestMapping 除了可以使用请求 URL 映射请求外,还可以使用请求方法、请求参数及请求头

@RequestMapping

  • value=“URL”
  • method=RequestMethod.POST (请求方式)
  • params=请求参数
  • headers=请求头的映射条件

他们之间是与的关系,联合使用多个条件可让请求映射更加精确化。


params 和 headers支持简单的表达式:

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

Ant 风格资源地址支持 3 种匹配符:

  • ?: 匹配文件名中的一个字符
  • *: 匹配文件名中的任意字符
  • **: 匹配多层路径

@RequestMapping 还支持 Ant 风格的 URL:

  • /user/*/createUser ——> 匹配 /user/aaa/createUser、/user/bbb/createUser 等 URL
  • /user/**/createUser ——> 匹配 /user/createUser、/user/aaa/bbb/createUser 等 URL
  • /user/createUser?? ——> 匹配 /user/createUseraa、/user/createUserbb 等 URL


@PathVariable 映射URL绑定的占位符

带占位符的 URL 是 Spring3.0 新增的功能,该功能在SpringMVC 向 REST 目标挺进发展过程中具有里程碑的意义

通过 @PathVariable 可以将 URL 中占位符参数绑定到控制器处理方法的入参中:

  • URL 中的{xxx}占位符可以通过@PathVariable(“xxx”) 绑定到操作方法的入参中。x必须是一致的
1
2
3
4
5
6
7
8
9
/**
* @PathVariable 可以来映射 URL 中的占位符到目标方法的参数中.
* @param id
*/
@RequestMapping("/testPathVariable/{id}")
public String testPathVariable(@PathVariable("id") Integer id) {
System.out.println("testPathVariable: " + id);
return SUCCESS;
}


三、Representational State Transfer

REST:(资源)表现层状态转化。是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便。

  • 资源(Resources):网络上的一个实体,或者说是网络上的一个具体信息。可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的 URI 。
    • 要获取这个资源,访问它的URI就可以,因此 URI 即为每一个资源的独一无二的识别符。

  • 表现层(Representation):把资源具体呈现出来的形式,叫做它的表现层。
    • 比如,文本可以用 txt 格式表现,也可以用 HTML 格式、XML 格式、JSON 格式表现,甚至可以采用二进制格式。

  • 状态转化(State Transfer):每发出一个请求就代表客户端和服务器的一次交互过程。HTTP协议,是一个无状态协议,即所有的状态都保存在服务器端。
    • 因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生“状态转化”(State Transfer)。
    • 而这种转化是建立在表现层之上的,所以就是 “表现层状态转化”。

具体说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。

  • 它们分别对应四种基本操作:
    • GET 用来获取资源
    • POST 用来新建资源
    • PUT 用来更新资源
    • DELETE 用来删除资源

示例:

  • /order/1 HTTP GET :得到 id = 1 的 order
  • /order/1 HTTP DELETE:删除 id = 1的 order
  • /order/1 HTTP PUT:更新id = 1的 order
  • /order HTTP POST:新增 order

  • 浏览器 form 表单只支持 GET与 POST 请求,而DELETE、PUT 等 method 并不支持。
    • 1、HiddenHttpMethodFilter :Spring3.0 添加了一个过滤器,可以将这些请求转换为标准的 http 方法,使得支持 PUT 与 DELETE 请求。
    • 2、发送Post请求
    • 3、发送Post请求时,携带隐藏域 < input type=”hidden” name=”_method” value=”PUT | DELETE” />

在 web.xml 配置过滤器

1
2
3
4
5
6
7
8
9
10
<!-- 配置 org.springframework.web.filter.HiddenHttpMethodFilter:可以把 POST 请求转为 DELETE 或 PUT 请求 -->
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/ *</url-pattern>
</filter-mapping>

Tomcat 7.0 版本以上 DELETE PUT 无法跳转 报405错误

  • 解决办法
    • 第一:tomcat换到7.0以及以下版本
    • 第二:请求先转给一个Controller,再返回jsp页面
    • 第三:在success页面头部文件将
      • <%@ page language=”java” contentType=”text/html; charset=UTF-8” pageEncoding=”UTF-8” isErrorPage=”true”%>
      • 多加一句话:isErrorPage设置为true,默认为false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<form action="springmvc/testRest/1" method="post">
<input type="hidden" name="_method" value="PUT"/>
<input type="submit" value="TestRest PUT"/>
</form>
<br><br>

<form action="springmvc/testRest/1" method="post">
<input type="hidden" name="_method" value="DELETE"/>
<input type="submit" value="TestRest DELETE"/>
</form>
<br><br>

<form action="springmvc/testRest" method="post">
<input type="submit" value="TestRest POST"/>
</form>
<br><br>


RESTful SpringMVC CRUD

通过 SpringMVC 的表单标签 可以实现将模型数据中的属性和 HTML 表单元素相绑定,以实现表单数据更便捷编辑和表单值的回显。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<form:form action="${pageContext.request.contextPath }/emp" method="POST" modelAttribute="employee">

<c:if test="${employee.id != null }">
<form:hidden path="id"/>
<input type="hidden" name="_method" value="PUT" />
</c:if>

LastName: <form:input path="lastName" />
<br>
Email: <form:input path="email"/>
<br>
<%
Map<String, String> genders = new HashMap();
genders.put("1", "Male");
genders.put("0", "Female");
request.setAttribute("genders", genders);
%>
Gender:
<br/>
<form:radiobuttons path="gender" items="${genders }" delimiter="-----"/>
<br>
Department:
<form:select path="department.id" items="${departments }" itemLabel="departmentName" itemValue="id" />
<br>
<input type="submit" value="Submit"/>
</form:form>

  • 可以通过 modelAttribute 属性指定绑定的模型属性

    • 若没有指定该属性,则默认从 request 域对象中读取command 的表单 bean,如果该属性值也不存在,则会发生错误。
  • path:表单字段,对应 html 元素的 name 属性,支持级联属性

  • <form:select>:用于构造下拉框组件。

    • items:可以是一个 List、String[] 或 Map。
    • itemValue:指定 items中的一个值作为 value 值。可以是集合中 bean 的一个属性值,回显提交该值。
    • itemLabel:指定 items中的一个值作为 label 值。显示在页面。
    • delimiter:多个单选框可以通过 delimiter 指定分隔符。
  • <form:checkbox> 复选框组件。用于构造单个复选框

  • <form:checkboxs> 用于构造多个复选框。使用方式同

  • <form:radiobuttons> 单选框组件标签,当表单 bean 对应的属性值和 value 值相等时,单选框被选中

  • <form:radiobuttons> 单选框组标签,用于构造多个单选框

  • <form:option> 下拉框选项组件标签。使用方式同

  • <form:errors> 显示表单组件或数据校验所对应的错误

    • <form:errors path=”*”/> :显示表单所有的错误
    • <form:errors path=”user*”/> :显示所有以 user 为前缀的属性对应的错误
    • <form:errors path=”username”/> :显示特定表单对象属性的错误


处理静态资源

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

可以在SpringMVC的配置文件中配置<mvc:default-servlethandler/>的方式解决静态资源的问题

  • <mvc:default-servlet-handler/> 将在 SpringMVC 上下文中定义一个DefaultServletHttpRequestHandler
  • 它会对进入 DispatcherServlet 的请求进行筛查,如果发现是没有经过映射的请求,就将该请求交由 WEB应用服务器默认的 Servlet 处理
  • 如果不是静态资源的请求,才由DispatcherServlet 继续处理。
  • 一般 WEB 应用服务器默认的 Servlet 的名称都是 default。
  • 若所使用的WEB 服务器的默认 Servlet 名称不是 default,则需要通过 defaultservlet-name 属性显式指定。

一般都需要配置 <mvc:annotation-driven></mvc:annotation-driven>

1
2
<mvc:default-servlet-handler/>
<mvc:annotation-driven></mvc:annotation-driven>



映射请求参数 & 请求参数

Spring MVC 通过分析处理方法的签名,将 HTTP 请求信息绑定到处理方法的相应入参中。

  • Spring MVC 对控制器处理方法签名的限制是很宽松的,几乎可以按喜欢的任何方式对方法进行签名。
  • 必要时可以对方法及方法入参标注相应的注解(@PathVariable、@RequestParam、@RequestHeader 等)
  • SpringMVC 框架会将 HTTP 请求的信息绑定到相应的方法入参中,并根据方法的返回值类型做出相应的后续处理。

@RequestParam 绑定请求参数值

  • 在处理方法入参处使用 @RequestParam 可以把请求参数传递给请求方法。
  • value:参数名
  • required:是否必须有值。
    • 默认为 true, 表示请求参数中必须包含对应的参数,若不存在,将抛出异常; false 不强制有该参数。
  • defaultValue = “0” 请求参数的默认值。
1
2
3
4
5
6
7
@RequestMapping(value = "/testRequestParam")
public String testRequestParam(
@RequestParam(value = "username") String un,
@RequestParam(value = "age", required = false, defaultValue = "0") int age) {
System.out.println("testRequestParam, username: " + un + ", age: "+ age);
return SUCCESS;
}

@RequestHeader 绑定请求报头

  • 请求头包含了若干个属性,服务器可据此获知客户端的信息,通过 @RequestHeader 即可将请求头中的属性值绑定到处理方法的入参中。

@CookieValue 绑定请求Cookie值

  • @CookieValue 可让处理方法入参绑定某个 Cookie 值
    1
    2
    3
    4
    5
    @RequestMapping("/testCookieValue")
    public String testCookieValue(@CookieValue("JSESSIONID") String sessionId) {
    System.out.println("testCookieValue: sessionId: " + sessionId);
    return SUCCESS;
    }

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

SpringMVC按请求参数名和POJO属性名进行自动匹配,自动为对象填充属性值。支持级联属性。

  • 如:dept.deptId、dept.address.tel 等

使用 Servlet API 作为入参

  • 可以使用 Serlvet 原生的 API 作为目标方法的参数 具体支持以下类型。
    • HttpServletRequest
    • HttpServletResponse
    • HttpSession
    • java.security.Principal
    • Locale
    • InputStream
    • OutputStream
    • Reader
    • Writer

四、处理模型数据

Spring MVC 提供了以下几种途径输出模型数据:

  • ModelAndView: 处理方法返回值类型为ModelAndView时, 方法体通过该对象添加模型数据。
  • Map 及 Model: 入参为以下类型时,处理方法返回时,Map中的数据会自动添加到模型中。
    • org.springframework.ui.Model、
    • org.springframework.ui.ModelMap
    • java.uti.Map
  • @SessionAttributes: 将模型中的某个属性暂存到HttpSession 中,以便多个请求之间可以共享这个属性。
  • @ModelAttribute: 方法入参标注该注解后, 入参的对象就会放到数据模型中。


ModelAndView

  • 1、目标方法的返回值可以是 ModelAndView 类型。
  • 2、其中包含视图和模型信息。new ModelAndView(viewName); viewName是视图的名字
    • SpringMVC 会把 ModelAndView 的 model 中数据放入到 request 域对象中。
1
2
3
4
5
6
7
8
9
10
11
12
import org.springframework.web.servlet.ModelAndView;
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
String viewName = SUCCESS;
ModelAndView modelAndView = new ModelAndView(viewName);

//添加模型数据到 ModelAndView 中.
modelAndView.addObject("time", new Date());
return modelAndView;
}

time: ${requestScope.time } 可以在 request 域中取出模型信息。


Map 及 Model

Spring MVC 在内部使用了一个org.springframework.ui.Model 接口存储模型数据。

  • 具体步骤
    • Spring MVC 在调用方法前会创建一个隐含的模型对象作为模型数据的存储容器。
    • 如果方法入参为 Map 或 Model 类型,Spring MVC 会将隐含模型引用传递给这些入参。

目标方法可以添加 Map 类型(实际上也可以是 Model 类型或 ModelMap 类型)的参数。

1
2
3
4
5
6
7
8
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map){
System.out.println(map.getClass().getName()); // BindingAwareModelMap
map.put("names", Arrays.asList("Tom", "Jerry", "Mike"));
return SUCCESS;
}

names: ${requestScope.names } 可以在 request 域中取出模型信息。

@SessionAttributes

  • 若希望在多个请求之间共用某个模型属性数据,则可以在控制器类上标注一个 @SessionAttributes

    • 注意:该注解只能放在类的上面。而不能修饰放方法。
  • Spring MVC将在模型中对应的属性暂存到 HttpSession 中。

  • @SessionAttributes

    • 除了可以通过属性名指定需要放到会话中的属性外(实际上使用的是 value 属性值)
    • 还可以通过模型属性的对象类型指定哪些模型属性需要放到会话中(实际上使用的是 types 属性值)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

@SessionAttributes(value={"user"})
class{

@RequestMapping("/testSessionAttributes")
public String testSessionAttributes(Map<String, Object> map){
User user = new User("Tom", "123456", 15);
map.put("user", user);
map.put("dept", "dept");
return SUCCESS;
}

request user: ${requestScope.user } 可以在 request 域中取出模型信息。
session user: ${sessionScope.user } 可以在 session 域中取出模型信息。

  • @SessionAttributes(types=User.class) 将隐含模型中所有类型为User.class属性添加到会话中
  • @SessionAttributes(value={“user”, “dept”})
  • @SessionAttributes(types={User.class, Dept.class})
  • @SessionAttributes(value={“user”, “dept”},types={Dept.class})

@ModelAttribute

  • 在方法定义上使用 @ModelAttribute 注解:

    • Spring MVC在调用目标处理方法之前,会先逐个调用在方法上标注了@ModelAttribute的方法
  • 在方法的入参前使用 @ModelAttribute 注解:

    • 可以从隐含对象中获取隐含的模型数据中获取对象,再将请求参数绑定到对象中,再传入入参。将方法入参对象添加到模型中。
    • 注解也可以来修饰目标方法 POJO 类型的入参, 其 value 属性值有如下的作用:
      • SpringMVC 会使用 value 属性值在 implicitModel 中查找对应的对象, 若存在则会直接传入到目标方法的入参中。
      • SpringMVC 会以 value 为 key, POJO 类型的对象为 value, 存入到 request 中。

运行流程:

  • 1、执行 @ModelAttribute 注解修饰的方法: 从数据库中取出对象, 把对象放入到了 Map 中(implicitModel)。键为:user
  • 2、SpringMVC 从 Map 中取出 User 对象, 并把表单的请求参数赋给该 User 对象的对应属性。
  • 3、SpringMVC 把上述对象传入目标方法的参数。
    • 注意: 在 @ModelAttribute 修饰的方法中, 放入到 Map 时的键需要和目标方法入参类型的第一个参数小写的字符串一致!
    • 若目标方法的 POJO 属性使用了 @ModelAttribute 来修饰, 则 attrName 值即为 @ModelAttribute 的 value 属性值

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

  • 1、确定一个 key:
    • 若目标方法的 POJO 类型的参数没有使用 @ModelAttribute 作为修饰, 则 key 为 POJO 类名第一个字母的小写
    • 若使用了 @ModelAttribute 来修饰, 则 key 为 @ModelAttribute 注解的 value 属性值。
  • 2、在 implicitModel 中查找 key 对应的对象, 若存在, 则作为入参传入
    • 若在 @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 域中。

五、视图和视图解析器

Spring MVC如何解析视图,请求处理方法执行完成后,最终返回一个 ModelAndView对象。

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

InternalResourceViewResolver

  • 若项目中使用了 JSTL,则 SpringMVC 会自动把视图由 InternalResourceView 转为 JstlView。
  • 若使用 JSTL 的 fmt 标签则需要在 SpringMVC 的配置文件中配置国际化资源文件。

JSTL 标签库

jar包

  • jstl.jar
  • standard.jar

i18n.properties

  • i18n.properties
  • i18n_zh_CN.properties
  • i18n_en_US.properties
    这些代码以校验注解类名为前缀,结合modleAttribute、属性名及属性类型名生成多个对应的消息代码。
  • 添加标签 <%@ taglib prefix=”fmt” uri=”http://java.sun.com/jsp/jstl/fmt” % >
1
2
3
4
5
6
7
<!-- 配置国际化资源文件 -->
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n"></property>
</bean>

<fmt:message key="i18n.username"></fmt:message>

  • 若希望直接响应通过 SpringMVC 渲染的页面,可以使用 <mvc:viewcontroller/> 标签实现
    • (单独配这个会导致其他链接不可用 报404)
    • 在实际开发中通常都需配置 <mvc:annotation-driven> 空标签 配合使用
  • 配置直接转发的页面
  • 可以直接相应转发的页面, 而无需再经过 Handler 的方法。
    1
    2
    3
    4
    <mvc:view-controller path="/success" view-name="success"/>

    <!-- 在实际开发中通常都需配置 mvc:annotation-driven 标签 -->
    <mvc:annotation-driven></mvc:annotation-driven>

自定义视图

  • 配置视图 BeanNameViewResolver 解析器:使用视图的名字来解析视图
  • 通过 order 属性来定义视图解析器的优先级, order 值越小优先级越高 (默认最大值MAX) 常用的放后面
1
2
3
4
5
6
7
<a href="springmvc/testView">Test View</a>

@RequestMapping("/testView")
public String testView(){
System.out.println("testView");
return "helloView";
}
1
2
3
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
<property name="order" value="100"></property>
</bean>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.Date;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.View;

@Component
public class HelloView implements View{
@Override
public String getContentType() {
return "text/html";
}
@Override
public void render(Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
response.getWriter().print("hello view, time: " + new Date());
}
}

Excel 视图

若希望使用 Excel 展示数据列表,仅需要扩展SpringMVC 提供的 AbstractExcelView 或AbstractJExcel View 即可。

  • 实现 buildExcelDocument()方法,在方法中使用模型数据对象构建 Excel 文档就可以了。
  • AbstractExcelView 基于 POI API,而AbstractJExcelView 是基于 JExcelAPI 的。
  • 视图对象需要配置 IOC 容器中的一个 Bean,使用BeanNameViewResolver 作为视图解析器即可
  • 若希望直接在浏览器中直接下载 Excel 文档,则可以设置响应头 Content-Disposition 的值为attachment;filename=xxx.xls


关于重定向

一般情况下,控制器方法返回字符串类型的值会被当成逻辑视图名处理。

如果返回的字符串中带 forward:或 redirect:前缀时,SpringMVC 会对他们进行特殊处理。

  • 将 forward: 和 redirect: 当成指示符,其后的字符串作为 URL 来处理 。
  • “redirect:success.jsp” 会完成一个到 success.jsp 的重定向的操作。
  • “forward:success.jsp” 会完成一个到 success.jsp 的转发操作。


关于 <mvc:annotation-driven/>

  • 配置直接转发的页面标签,直接转发页面,无需经过Handler。

    1
    2
    <mvc:view-controller path="/success" view-name="success"/>
    <mvc:annotation-driven></mvc:annotation-driven>
  • 处理静态资源标签,需要 <mvc:annotation-driven/> 配合。

    1
    2
    <mvc:default-servlet-handler/>
    <mvc:annotation-driven></mvc:annotation-driven>

  • <mvc:annotation-driven /> 会自动注册三个bean
    • RequestMappingHandlerMapping
    • RequestMappingHandlerAdapter
    • ExceptionHandlerExceptionResolver

  • 还将提供以下支持:
    • 支持使用 ConversionService 实例对表单参数进行类型转换
    • 支持使用 @NumberFormat annotation、@DateTimeFormat注解完成数据类型的格式化
    • 支持使用 @Valid 注解对 JavaBean 实例进行 JSR 303 验证
    • 支持使用 @RequestBody 和 @ResponseBody 注解

数据格式化

FormattingConversionServiceFactroyBean :因此它既具有类型转换的功能,又具有格式化的功能。

  • FormattingConversionServiceFactroyBean 内部已经注册了:
    • NumberFormatAnnotationFormatterFactroy:支持对数字类型的属性 使用 @NumberFormat 注解
    • JodaDateTimeFormatAnnotationFormatterFactroy:支持对日期类型的属性使用 @DateTimeFormat 注解
    • 装配了 FormattingConversionServiceFactroyBean 后,就可以在 Spring MVC 入参绑定及模型数据输出时使用注解驱动了。
    • <mvc:annotation-driven/> 默认创建的ConversionService 实例即为FormattingConversionServiceFactroyBean
1
2
3
4
5
6
7
8
9
10
11
12
13
@NotEmpty
private String lastName;

@Email
private String email;

@Past
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date birth;

@NumberFormat(pattern="#,###,###.#")
@NumberFormat(style=Sytle.NUMBER(正常数字类型)、Sytle.CURRENCY(货币类型)、Sytle.PERCENT(百分数类型))
private Float salary;


数据校验

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) 被注释的元素必须是一个数字 其值必须小于等于指定的最大值
@Digits(integer,fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Size(max,min) 被注释的元素的大小必须在指定的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(regexp=””) 被注释的元素必须符合指定的正则表达式
  • Hibernate Validator 扩展注解
    • Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解。
      • 1、加入 Hibernate Validator 验证框架的 jar 包
      • 2、配置文件中添加 <mvc:annotation-driven></mvc:annotation-driven>
      • 3、需要在 bean 类型的前面添加 @valid 注解
注解 功能说明
@Email 被注释的元素必须是电子邮箱地址
@Length 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range 被注释的元素必须在合适的范围内


Spring MVC 数据校验

  • Spring 4.0 拥有自己独立的数据校验框架,同时支持 JSR303 标准的校验框架。
  • Spring 在进行数据绑定时,可同时调用校验框架完成数据校验工作。
  • 在 Spring MVC 中,可直接通过注解驱动的方式进行数据校验。
  • Spring 的 LocalValidatorFactroyBean 既实现了 Spring 的Validator 接口,也实现了 JSR 303 的 Validator 接口。
  • 只要在 Spring 容器中定义了一个LocalValidatorFactoryBean,即可将其注入到需要数据校验的 Bean 中。
  • Spring 本身并没有提供 JSR303 的实现,所以必须将JSR303 的实现者的 jar 包放到类路径下。

<mvc:annotation-driven/> 会默认装配好一个 LocalValidatorFactoryBean。

  • 通过在处理方法的入参上标注 @valid 注解即可让 Spring MVC 在完成数据绑定后执行数据校验的工作。
    • public String save(@Valid Employee employee){}
  • 在已经标注了 JSR303 注解的表单命令对象前标注一个@Valid
    • Spring MVC 框架在将请求参数绑定到该入参对象后,就会调用校验框架根据注解声明的校验规则实施校验。
  • Spring MVC 是通过对处理方法签名的规约来保存校验结果的:
    • 前一个表单命令对象的校验结果保存到随后的入参中,这个保存校验结果的入参必须是 BindingResult 或Errors 类型。
    • 这两个类都位于 org.springframework.validation 包中。

需校验的 Bean 对象和其绑定结果对象或错误对象是成对出现的,它们之间不允许声明其他的入参

  • Errors 接口提供了获取错误信息的方法
  • FieldError getFieldError(String field)
  • List getFieldErrors()
  • Object getFieldValue(String field)
  • Int getErrorCount()
    • BindingResult 扩展了 Errors 接口
1
2
3
//校验的 Bean 对象 和 其绑定结果对象或错误对象 成对出现的 紧挨着
public String handler (@Valid User user, BindingResult userBindingResult,ModelMap mm,
@Valid Dept dept, Errors deptErrors){}


在页面上显示错误

Spring MVC 除了会将表单/命令对象的校验结果保存到对应的 BindingResult 或 Errors 对象中外,还将所有校验结果保存到“隐含模型”

  • 即使处理方法的签名中没有对应表单/命令对象的结果入参,校验结果也会保存在隐含对象中。
  • 隐含模型中的所有数据最终将通过 HttpServletRequest 的属性列表暴露给 JSP 视图对象,因此在 JSP 中可以获取错误信息。
  • 在 JSP 页面上可通过 <form:errors path=”*”>显示全部错误消息。
    • 也可以写在每个 input 字段的后面 显示对应的错误消息。



六、处理 JSON

使用

  • 1、加入 jar包

    • jackson-annotations-2.1.5.jar
    • jackson-core-2.1.5.jar
    • jackson-databind-2.1.5.jar
  • 2、编写目标方法,使其返回 JSON 对应的对象或集合

  • 3、在方法上添加 @ResponseBody 注解

原理

HttpMessageConverter

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

  • 使用 @RequestBody / @ResponseBody 对处理方法进行标注。

  • 使用 HttpEntity / ResponseEntity 作为处理方法的入参或返回值。

  • 当控制器处理方法使用到

    • @RequestBody/@ResponseBody 或HttpEntity/ResponseEntity
    • Spring 首先根据请求头或响应头的Accept 属性选择匹配的 HttpMessageConverter,
    • 进而根据参数类型或泛型类型的过滤得到匹配的 HttpMessageConverter, 若找不到可用的HttpMessageConverter 将报错

@RequestBody 和 @ResponseBody 不需要成对出现



国际化

默认情况下,SpringMVC 根据 Accept-Language 参数判断客户端的本地化类型。

  • 当接受到请求时,SpringMVC 会在上下文中查找一个本地化解析器(LocalResolver)找到后使用它获取请求所对应的本地化类型信息
  • SpringMVC 还允许装配一个动态更改本地化类型的拦截器,这样通过指定一个请求参数就可以控制单个请求的本地化类型。
1
2
3
4
5
6
7
<!-- 配置 SessionLocalResolver -->
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"></bean>

<mvc:interceptors>
<!-- 配置 LocaleChanceInterceptor -->
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"></bean>
</mvc:interceptors>



文件上传

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

  • Spring MVC 上下文中默认没有装配 MultipartResovler,默认情况下不能处理文件的上传工作。
  • 如果想使用 Spring的文件上传功能,需现在上下文中配置 MultipartResolver。

1、jar 包:

  • commons-fileupload-1.2.1.jar
  • commons-io-2.0.jar
1
2
3
4
5
6
<!-- 配置 MultipartResolver -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"></property>
<property name="maxUploadSize" value="1024000"></property>
</bean>
1
2
3
4
5
6
7
8
@RequestMapping("/testFileUpload")
public String testFileUpload(@RequestParam("desc") String desc,
@RequestParam("file") MultipartFile file) throws IOException{
System.out.println("desc: " + desc);
System.out.println("OriginalFilename: " + file.getOriginalFilename());
System.out.println("InputStream: " + file.getInputStream());
return "success";
}
1
2
3
4
5
<form action="testFileUpload" method="POST" enctype="multipart/form-data">
File: <input type="file" name="file"/>
Desc: <input type="text" name="desc"/>
<input type="submit" value="Submit"/>
</form>


拦截器

Spring MVC也可以使用拦截器对请求进行拦截处理。

  • 可以自定义拦截器来实现特定的功能,自定义的拦截器必须实现HandlerInterceptor接口。
  • preHandle(): 这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求 request 进行处理。
    • 如果程序员决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去进行处理,则返回true。
    • 如果程序员决定不需要再调用其他的组件去处理请求,则返回false。
  • postHandle(): 这个方法在业务处理器处理完请求后,但是DispatcherServlet 向客户端返回响应前被调用,在该方法中对用户请求request进行处理。
  • afterCompletion(): 这个方法在 DispatcherServlet 完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class FirstInterceptor implements HandlerInterceptor{
/**
* 该方法在目标方法之前被调用.
* 若返回值为 true, 则继续调用后续的拦截器和目标方法.
* 若返回值为 false, 则不会再调用后续的拦截器和目标方法.
* 可以考虑做权限. 日志, 事务等.
*/
@Override
public boolean preHandle(HttpServletRequest request,HttpServletResponse response,
Object handler) throws Exception {
System.out.println("[FirstInterceptor] preHandle");
return true;
}
/**
* 调用目标方法之后, 但渲染视图之前.
* 可以对请求域中的属性或视图做出修改.
*/
@Override
public void postHandle(HttpServletRequest request,HttpServletResponse response,
Object handler,ModelAndView modelAndView) throws Exception {
System.out.println("[FirstInterceptor] postHandle");
}
/**
* 渲染视图之后被调用. 释放资源
*/
@Override
public void afterCompletion(HttpServletRequest request,HttpServletResponse response,
Object handler, Exception ex)throws Exception {
System.out.println("[FirstInterceptor] afterCompletion");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
<mvc:interceptors>
<!-- 配置自定义的拦截器 -->
<bean class="springmvc.interceptors.FirstInterceptor"></bean>

<mvc:interceptor>
<!-- 配置拦截器作用的路径 -->
<mvc:mapping path="/emps"/>
<!-- 配置拦截器不作用的路径 -->
<mvc:exclude-mapping path="/emps"/>
<bean class="springmvc.interceptors.SecondInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>


拦截器执行顺序

preHandle 正序
postHandle 倒序
afterCompletion 倒序


拦截器 preHandle return false 拦截器执行顺序



异常处理

Spring MVC 通过 HandlerExceptionResolver 处理程序的异常,包括 Handler 映射、数据绑定以及目标方法执行时发生的异常。

  • 在 @ExceptionHandler 方法的入参中可以加入 Exception 类型的参数, 该参数即对应发生的异常对象。
  • @ExceptionHandler:方法的入参中不能传入 Map。若希望把异常信息传导页面上, 需要使用 ModelAndView 作为返回值。
  • @ExceptionHandler:方法标记的异常有优先级的问题。
    • 先精确匹配,如果没有,此时会根据异常的最近继承关系找到继承深度最浅的那个。
  • @ControllerAdvice: 如果在当前 Handler 中找不到 @ExceptionHandler 方法来出来当前方法出现的异常,
    • 将去 @ControllerAdvice 标记的类中查找 @ExceptionHandler 标记的方法来处理异常。
1
2
3
4
5
6
7
@ExceptionHandler({ArithmeticException.class})
public ModelAndView handleArithmeticException(Exception ex){
System.out.println("出异常了: " + ex);
ModelAndView mv = new ModelAndView("error");
mv.addObject("exception", ex);
return mv;
}
  • @ResponseStatus 注解修饰
    • 放在异常类上面 @ResponseStatus(value=HttpStatus.FORBIDDEN, reason=”用户名和密码不匹配!”)
    • 放在方法上面 @ResponseStatus(reason=”测试”,value=HttpStatus.NOT_FOUND)
      • 方法正常执行,页面跳转到异常页面。并显示自定义 属性

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

  • SimpleMappingExceptionResolver
  • 如果希望对所有异常进行统一处理,可以使用SimpleMappingExceptionResolver,它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常
1
2
3
4
5
6
7
8
9
<!-- 配置使用 SimpleMappingExceptionResolver 来映射异常 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionAttribute" value="ex"></property>//改异常的属性名,默认为 exception
<property name="exceptionMappings">
<props>
<prop key="java.lang.ArrayIndexOutOfBoundsException">error</prop>
</props>
</property>
</bean>

SpringMVC 运行流程

  • 用户向服务端发送请求,这个请求会先到前端控制器 DispatcherServlet(也叫中央控制器)
  • DispatcherServlet 接收到请求后会调用HandlerMapping处理器映射器,得知该请求该由哪个 Controller来处理(并未调用Controller,只是得知);
  • DispatcherServlet 调用 HandlerAdapter 处理器适配器,告诉处理器适配器应该要去执行哪个 Controller;
  • HandlerAdapter 处理器适配器去执行 Controller 并得到 ModelAndView,并层层返回给DispatcherServlet;
  • DispatcherServlet 将 ModelAndView 交给 ViewReslover 视图解析器解析返回真正的视图;
  • DispatcherServlet 将模型数据填充到视图中;
  • DispatcherServlet 将结果响应给用户;

Spring 的环境下使用SpringMVC

Spring 的 IOC 容器不应该扫描 SpringMVC 中的 bean, 对应的SpringMVC 的 IOC 容器不应该扫描 Spring 中的 bean

  • 通常情况下, 类似于数据源, 事务, 整合其他框架都是放在 Spring 的配置文件中(而不是放在 SpringMVC 的配置文件中)。
    • 实际上放入 Spring 配置文件对应的 IOC 容器中的还有 Service 和 Dao。
    • 都放在 SpringMVC 的配置文件中。也可以分多个 Spring 的配置文件, 然后使用 import 节点导入其他的配置文件

若 Spring 的 IOC 容器和 SpringMVC 的 IOC 容器扫描的包有重合的部分, 会导致 bean 被创建 2 次

  • 解决:
    • 使 Spring 的 IOC 容器扫描的包和 SpringMVC 的 IOC 容器扫描的包没有重合的部分。
    • 使用 exclude-filter 和 include-filter 子节点来规定只能扫描的注解

SpringMVC 的 IOC 容器中的 bean 可以来引用 Spring IOC 容器中的 bean
反之则不行,Spring IOC 容器中的 bean 却不能来引用 SpringMVC IOC 容器中的 bean


applicationContext.xml

1
2
3
4
<context:component-scan base-package="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.xml

1
2
3
4
<context:component-scan base-package="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>

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!-- 配置启动 Spring IOC 容器的 Listener -->
<!-- needed for ContextLoaderListener -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationcontext.xml</param-value>
</context-param>

<!-- Bootstraps the root web application context before servlet initialization -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<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>

SpringMVC 对比 Struts2

  • Spring MVC 的入口是 Servlet, 而 Struts2 是 Filter
  • Spring MVC 会稍微比 Struts2 快些。Spring MVC 是基于方法设计, 而 Sturts2 是基于类, 每次发一次请求都会实例一个 Action。
  • Spring MVC 使用更加简洁, 开发效率Spring MVC确实比 struts2 高: 支持 JSR303, 处理 ajax 的请求更方便。
  • Struts2 的 OGNL 表达式使页面的开发效率相比Spring MVC 更高些。

感谢阅读


If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them. Thank you !