SpringMVC - 基础笔记

HelloWorld

步骤:

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

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

web.xml

 <!-- 如果安装了Spring IDE,直接使用alt+/ ,选择# dispatcherServlet 生成配置DispatcherServlet文件 -->
 <!-- The front controller of this Spring Web application, responsible for handling all application requests -->
<servlet>
    <servlet-name>springDispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- 配置DispatcherServlet的一个初始化参数:配置SpringMVC配置文件的位置和名称 -->
    <!-- 
    实际上也可以不通过 contextConfigLocation 来配置 SpringMVC 的配置文件,而使用默认的
    默认的配置文件为: /WEB-INF/<servlet-name>-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>

<!-- Map all requests to the DispatcherServlet for handling -->
<servlet-mapping>
    <servlet-name>springDispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

springmvc.xml

<!-- 配置指定扫描的包 -->
<context:component-scan base-package="com.atguigu.springmvc.handlers"></context:component-scan>

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

index.jsp

<body>
    <a href="helloworld">Hello World</a>
</body>

HelloWorld.java

@Controller
public class HelloWorld {

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

success.jsp

<body>
    <h4>Hello World</h4>
</body>

RequestMapping

使用 @RequestMapping 映射请求

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

在控制器的类定义及方法定义处都可标注@RequestMapping
- 类定义处:提供初步的请求映射信息。相对于 WEB 应用的根目
- 录方法处:提供进一步的细分映射信息。相对于类定义处的 URL。若类定义处未标@RequestMapping,则方法处标记的 URL 相对于WEB 应用的根目录

DispatcherServlet 截获请求后,就通过控制器上@RequestMapping 提供的映射信息确定请求所对应的处理方法。

index.jsp

<a href="springmvc/testRequestMapping">Test RequestMapping</a>

SpringMVCTest.java

@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {

    @RequestMapping("/testRequestMapping")
    public  String testRequestMapping(){
        System.out.println("test RequestMapping");
        return "success";
    }
}

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

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

@RequestMapping 的 value、method、params 及 heads 分别表示请求 URL、请求方法、请求参数及请求头的映射条件,他们之间是与的关系,联合使用多个条件可让请求映射更加精确化。

  • params . 和 headers支持简单的表达式:
  • param1: 表示请求必须包含名为 param1 的请求参数
  • !param1: 表示请求不能包含名为 param1 的请求参数
  • param1 != value1: 表示请求包含名为 param1 的请求参数,但其值不能为 value1
  • {“param1=value1”, “param2”}: 请求必须包含名为 param1和param2的两个请求参数,且 param1 参数的值必须为 value1

index.jsp

<a href="springmvc/testRequestParam?username=atguigu&age=10">test requestParam</a>
<br><br>
<form action="springmvc/testMehod" method="post">
    <input type="submit" value="提交" />
</form>

SpringMVCTest.java

@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {

    @RequestMapping(value="/testRequestParam",params={"username=atguigu", "age=10"})
    public String testRequestParam(){
        System.out.println("test RequestParam");
        return "success";
    }

    @RequestMapping(value="/testMehod", method=RequestMethod.POST)
    public String testMethod(){
        System.out.println("test Method");
        return "success";
    }
}

通配符

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

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

index.jsp

<a href="springmvc/testAntPath/sdf/aaa">test Ant Path</a>

SpringMVCTest.java

@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {

    @RequestMapping("/testAntPath/*/aaa")
    public String testAntPath(){
        System.out.println("test ant Path");
        return "success";
    }

@PathVariable 映射 URL 绑定的占位符

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

通过 @PathVariable 可以将 URL 中占位符参数绑定到控制器处理方法的入参中:URL 中的 {xxx} 占位符可以通过@PathVariable(“xxx”) 绑定到操作方法的入参中。

index.jsp

<a href="springmvc/testPathVariable/5">test PathVariable</a>

SpringMVCTest.java

@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {

    @RequestMapping("/testPathVariable/{id}")
    public String testPathVariable(@PathVariable("id") Integer id){
        System.out.println("test Path Variable:" + id);
        return "success";
    }
}

REST

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

HiddenHttpMethodFilter:浏览器 form 表单只支持 GET 与 POST 请求,而DELETE、PUT 等 method 并不支持,Spring3.0 添加了一个过滤器,可以将这些请求转换为标准的 http 方法,使得支持 GET、POST、PUT 与DELETE 请求。

在web.xml文件中配置HiddenHttpMethodFilter
web.xml

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

index.jsp

<form action="springmvc/testRestPut/1" method="post">
    <input type="hidden" name="_method" value="put"/>
    <input type="submit" value="test Rest PUT" />
</form>
<br><br>

<form action="springmvc/testRestDelete/1" method="post">
    <input type="hidden" name="_method" value="delete"/>
    <input type="submit" value="test Rest Delete" />
</form>
<br><br>

<form action="springmvc/testRest" method="post">
    <input type="hidden" name="_method" value="post"/>
    <input type="submit" value="test Rest post" />
</form>
<br><br>

<a href="springmvc/testRest/1">Test Rest Get</a>

SpringMVCTest.java

@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {

    /**
     * Rest 风格的 URL. 以 CRUD 为例: 新增: /order POST 修改: /order/1 PUT update?id=1 获取:
     * /order/1 GET get?id=1 删除: /order/1 DELETE delete?id=1
     * 
     * 如何发送 PUT 请求和 DELETE 请求呢 ? 1. 需要配置 HiddenHttpMethodFilter 2. 需要发送 POST 请求
     * 3. 需要在发送 POST 请求时携带一个 name="_method" 的隐藏域, 值为 DELETE 或 PUT
     * 
     * 在 SpringMVC 的目标方法中如何得到 id 呢? 使用 @PathVariable 注解
     * 
     */

    @RequestMapping(value = "/testRestPut/{id}", method = RequestMethod.PUT)
    public String testRestPut(@PathVariable Integer id){
        System.out.println("test Rest Put:" + id);
        return "success";
    }

    @RequestMapping(value = "/testRestDelete/{id}", method = RequestMethod.DELETE)
    public String testRestDelete(@PathVariable Integer id){
        System.out.println("test Rest Delete:" + id);
        return "success";
    }

    @RequestMapping(value="/testRest",method = RequestMethod.POST)
    public String testRest(){
        System.out.println("test Rest POST");
        return "success";
    }

    @RequestMapping(value="/testRest/{id}",method=RequestMethod.GET)
    public String testRest(@PathVariable Integer id){
        System.out.println("test Rest Get:" + id);
        return "success";
    }
}

映射请求参数 & 请求参数

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

Spring MVC 对控制器处理方法签名的限制是很宽松的,几乎可以按喜欢的任何方式对方法进行签名。

必要时可以对方法及方法入参标注相应的注解(@PathVariable、@RequestParam、@RequestHeader 等)、Spring MVC 框架会将 HTTP 请求的信息绑定到相应的方法入参中,并根据方法的返回值类型做出相应的后续处理。

RequestParam

在处理方法入参处使用 @RequestParam 可以把请求参数传递给请求方法
value:参数名
required:是否必须。默认为 true, 表示请求参数中必须包含对应的参数,若不存在,将抛出异常

index.jsp

<a href="springmvc/testRequestParam?name=atguigu&age=10">test RequestParam</a>

SpringMVCTest.java

@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {

    /**
     * @RequestParam 来映射请求参数. value 值即请求参数的参数名 required 该参数是否必须. 默认为 true
     *               defaultValue 请求参数的默认值
     */
    @RequestMapping(value="/testRequestParam")
    public String testRequestParam(@RequestParam(value = "name") String name, @RequestParam(value = "age", required = false, defaultValue = "0") int age){
        System.out.println("test RequestParam name: " + name + " age: " + age);
        return "success";
    }
}

RequestHeader

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

index.jsp

<a href="springmvc/testRequestHeader">test RequestHeader</a>

SpringMVCTest.java

@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {

    /**
     * 了解: 映射请求头信息 用法同 @RequestParam
     */
    @RequestMapping(value="/testRequestHeader")
    public String testRequestHeader(@RequestHeader(value = "Accept-Language") String al){
        System.out.println("test RequestHeader Accept-Language : " + al);
        return "success";
    }
}

CookieValue

@CookieValue 可让处理方法入参绑定某个 Cookie 值

index.jsp

<a href="springmvc/testCookieValue">testCookieValue</a>

SpringMVCTest.java

@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {

    /**
     * 了解:
     * 
     * @CookieValue: 映射一个 Cookie 值. 属性同 @RequestParam
     */
    @RequestMapping("/testCookieValue")
    public String testCookieValue(@CookieValue(value="JSESSIONID") String sessionId){
        System.out.println("testCookieValue sessionId: " + sessionId);
        return "success";
    }
}

POJO

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

User.java

public class User {

    private String username;
    private String password;
    private String email;
    private int age;
    private Address address;
    ... ...
}

Address.java

public class Address {

    private String province;
    private String city;
    ... ...
}

index.jsp

<form action="springmvc/testPojo" method="post">
    username: <input type="text" name="username"/>
    <br>
    password: <input type="password" name="password"/>
    <br>
    email: <input type="text" name="email"/>
    <br>
    age: <input type="text" name="age"/>
    <br>
    city: <input type="text" name="address.city"/>
    <br>
    province: <input type="text" name="address.province"/>
    <br>
    <input type="submit" value="Submit"/>
</form>

SpringMVCTest.java

@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {

    @RequestMapping("/testPojo")
    public String testPojo(User user){
        System.out.println("testPojo user " + user);
        return "success";
    }
}

Servlet API

可以使用 Serlvet 原生的 API 作为目标方法的参数 具体支持以下类型

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

index.jsp

<a href="springmvc/testServletAPI">test ServletAPI</a>

SpringMVCTest.java

@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {

    @RequestMapping("/testServletAPI")
    public void testServletAPI(HttpServletRequest request,
            HttpServletResponse response, Writer out) throws IOException{
        System.out.println("test ServletAPI " + request + " , " + response);
        out.write("aaa");
//      return "success";
    }
}

处理模型数据

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

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

ModelAndView

控制器处理方法的返回值如果为 ModelAndView, 则其既包含视图信息,也包含模型数据信息。

添加模型数据:

  • MoelAndView addObject(String attributeName, Object attributeValue)
  • ModelAndView addAllObject(Map<String, ?> modelMap)

设置视图:

  • void setView(View view)
  • void setViewName(String viewName)

index.jsp

<a href="springmvc/testModelAndView">test ModelAndView</a>

SpringMVCTest.java

@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {

    /**
     * 目标方法的返回值可以是 ModelAndView 类型。 
     * 其中可以包含视图和模型信息
     * SpringMVC 会把 ModelAndView 的 model 中数据放入到 request 域对象中. 
     * @return
     */
    @RequestMapping("/testModelAndView")
    public ModelAndView testModelAndView(){
        String viewName = "success";
        ModelAndView modelAndView = new ModelAndView(viewName);

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

        return modelAndView;
    }
}

success.jsp

<body>
    <h4>Hello World</h4>
    time:${requestScope.time }
</body>

Map 及 Model

入参为org.springframework.ui.Model、org.springframework.ui.ModelMap 或 java.uti.Map 时,处理方法返回时,Map 中的数据会自动添加到模型中。

Spring MVC 在内部使用了一个org.springframework.ui.Model 接口存储模型数据
这里写图片描述

具体步骤

  • Spring MVC 在调用方法前会创建一个隐含的模型对象作为模型数据的存储容器。
  • 如果方法的入参为 Map 或 Model 类型,Spring MVC 会将隐含模型的引用传递给这些入参。在方法体内,开发者可以通过这个入参对象访问到模型中的所有数据,也可以向模型中添加新的属性数据

index.jsp

<a href="springmvc/testMap">test Map</a>

SpringMVCTest.java

@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {

    /**
     * 目标方法可以添加 Map 类型(实际上也可以是 Model 类型或 ModelMap 类型)的参数. 
     * @param map
     * @return
     */
    @RequestMapping("/testMap")
    public String testMap(Map<String, Object> map){
        System.out.println(map.getClass().getName());
        map.put("names", Arrays.asList("zhangsan", "lisi", "wangwu"));
        return "success";
    }
}

success.jsp

names:${requestScope.names }

SessionAttributes

若希望在多个请求之间共用某个模型属性数据,则可以在控制器类上标注一个 @SessionAttributes, Spring MVC 将在模型中对应的属性暂存到 HttpSession 中。

@SessionAttributes 除了可以通过属性名指定需要放到会话中的属性外,还可以通过模型属性的对象类型指定哪些模型属性需要放到会话中

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

index.jsp

<a href="springmvc/testSessionAttributes">test sessionAttributes</a>

SpringMVCTest.java

@SessionAttributes(value={"user"}, types={String.class})
@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {

    /**
     * @SessionAttributes 除了可以通过属性名指定需要放到会话中的属性外(实际上使用的是 value 属性值),
     * 还可以通过模型属性的对象类型指定哪些模型属性需要放到会话中(实际上使用的是 types 属性值)
     * 
     * 注意: 该注解只能放在类的上面. 而不能修饰放方法. 
     */
    @RequestMapping("/testSessionAttributes")
    public String testSessionAttributes(Map<String, Object> map){
        User user = new User("zhangsan", "132456", "sdf@qq.com", 20);
        map.put("user", user);
        map.put("school", "qinghuadaxue");
        return "success";
    }
}

success.jsp

<body>
    user:${sessionScope.user }
    <br>
    school:${sessionScope.school }
</body>

ModelAttribute

使用场景

这里写图片描述

如果有一个表单内容需要进行修改,而它的一个字段值不能修改,这时候,如果我们还按照以前的方法,这个不能修改的值就会变成空,所以就需要从数据库读取这个对象然后再进行修改。

这里写图片描述

示例

给User类加id字段,并且添加相应的构造器

index.jsp

<!--  
    模拟修改操作
    1. 原始数据为: 1, Tom, 123456,tom@atguigu.com,12
    2. 密码不能被修改.
    3. 表单回显, 模拟操作直接在表单填写对应的属性值
-->
<form action="springmvc/testModelAttribute" method="post">
    <input type="hidden" name="id" value="1"/>
    username: <input type="text" name="username" value="Tom"/>
    <br>
    emain: <input type="text" name="email" value="tom@atguigu.com"/>
    <br>
    age: <input type="text" name="age" value="12"/>
    <br>
    <input type="submit" value="Submit"/>
</form>

SpringMVCTest.java

//@SessionAttributes(value={"user"}, types={String.class})
@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {

    @ModelAttribute
    public void getUser(@RequestParam(value="id", required=false) Integer id, Map<String, Object> map){
        System.out.println("modelAttribute method");
        if(id != null){
            //模拟从数据库中获取对象
            User user = new User(1, "Tom", "123456", "tom@atguigu.com", 12);
            System.out.println("从数据库中获取一个对象:" + user);
            map.put("user", user);
        }
    }

    @RequestMapping("/testModelAttribute")
    public String testModelAttribute(User user){
        System.out.println("修改: " + user);
        return "success";
    }
}

运行原理

运行流程:
1. 执行 @ModelAttribute 注解修饰的方法: 从数据库中取出对象, 把对象放入到了 Map 中. 键为: user
2. SpringMVC 从 Map 中取出 User 对象, 并把表单的请求参数赋给该 User 对象的对应属性.
3. SpringMVC 把上述对象传入目标方法的参数.

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

源码分析

源代码分析的流程:
1. 调用 @ModelAttribute 注解修饰的方法. 实际上把 @ModelAttribute 方法中 Map 中的数据放在了implicitModel 中.
2. 解析请求处理器的目标参数, 实际上该目标参数来自于 WebDataBinder 对象的 target 属性
1). 创建 WebDataBinder 对象:
①. 确定 objectName 属性: 若传入的 attrName 属性值为 “”, 则 objectName 为类名第一个字母小写.
*注意: attrName. 若目标方法的 POJO 属性使用了 @ModelAttribute 来修饰, 则 attrName 值即为 @ModelAttribute 的 value 属性值

②. 确定 target 属性:
▷ 在 implicitModel 中查找 attrName 对应的属性值. 若存在, ok
*若不存在: 则验证当前 Handler 是否使用了 @SessionAttributes 进行修饰, 若使用了, 则尝试从 Session 中获取 attrName 所对应的属性值. 若 session 中没有对应的属性值, 则抛出了异常.
▷ 若 Handler 没有使用 @SessionAttributes 进行修饰, 或 @SessionAttributes 中没有使用 value 值指定的 key 和 attrName 相匹配, 则通过反射创建了 POJO 对象

2). SpringMVC 把表单的请求参数赋给了 WebDataBinder 的 target 对应的属性.
3). *SpringMVC 会把 WebDataBinder 的 attrName 和 target 给到 implicitModel.
近而传到 request 域对象中.
4). 把 WebDataBinder 的 target 作为参数传递给目标方法的入参.

如何确定目标方法POJO类型参数
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 中.

对于被 @ModelAttribute 标记的方法
1. 有 @ModelAttribute 标记的方法, 会在每个目标方法执行之前被 SpringMVC 调用!
2. @ModelAttribute 注解也可以来修饰目标方法 POJO 类型的入参, 其 value 属性值有如下的作用:
1). SpringMVC 会使用 value 属性值在 implicitModel 中查找对应的对象, 若存在则会直接传入到目标方法的入参中.
2). SpringMVC 会一 value 为 key, POJO 类型的对象为 value, 存入到 request 中.

@RequestMapping("/testModelAttribute")
public String testModelAttribute(@ModelAttribute("user") User user){
    System.out.println("修改: " + user);
    return SUCCESS;
}

关于 SessionAttribute 注解引发的异常
如果 Handler 没有 @ModelAttribute 修饰的方法,则检查当前的 Handler 是否使用 @SessionAttributes 注解修饰, 若使用了该注解, 且 @SessionAttributes 注解的 value 属性值中包含了 key(这里就是user), 则会从 HttpSession 中来获取 key 所对应的 value 值, 若存在则直接传入到目标方法的入参中. 若不存在则将抛出异常.

解决办法:
1.将目标方法的POJO入参改为

@RequestMapping("/testModelAttribute")
public String testModelAttribute(@ModelAttribute("abc") User user){
    System.out.println("修改: " + user);
    return SUCCESS;
}

但这样就获取不到 password 了
2.加@ModelAttribute 方法

视图解析流程分析

请求处理方法执行完成后,最终返回一个 ModelAndView 对象。对于那些返回 String,View 或 ModeMap 等类型的处理方法,Spring MVC 也会在内部将它们装配成一个 ModelAndView 对象,它包含了逻辑名和模型对象的视图

Spring MVC 借助视图解析器(ViewResolver)得到最终的视图对象(View),最终的视图可以是 JSP ,也可能是 Excel、JFreeChart 等各种表现形式的视图

对于最终究竟采取何种视图对象对模型数据进行渲染,处理器并不关心,处理器工作重点聚焦在生产模型数据的工作上,从而实现 MVC 的充分解耦

视图

视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户。

为了实现视图模型和具体实现技术的解耦,Spring 在org.springframework.web.servlet 包中定义了一个高度抽象的 View 接口
这里写图片描述

视图对象由视图解析器负责实例化。由于视图是无状态的,所以他们不会有线程安全的问题

常用的视图实现类:
这里写图片描述

视图解析器

SpringMVC 为逻辑视图名的解析提供了不同的策略,可以在 Spring WEB 上下文中配置一种或多种解析策略,并指定他们之间的先后顺序。每一种映射策略对应一个具体的视图解析器实现类。

视图解析器的作用比较单一:将逻辑视图解析为一个具体的视图对象。

所有的视图解析器都必须实现 ViewResolver 接口:
这里写图片描述

常用的视图解析器实现类:
这里写图片描述

InternalResourceViewResolver和JstlView

JSP 是最常见的视图技术,可以使用 InternalResourceViewResolver 作为视图解析器:

这里写图片描述

若项目中使用了 JSTL,则 SpringMVC 会自动把视图由 InternalResourceView 转为 JstlView

若使用 JSTL 的 fmt 标签则需要在 SpringMVC 的配置文件中配置国际化资源文件

加入JSTL的jar包:jstl.jar和standard.jar

在SpringMVC.xml文件中配置国际化资源文件
SpringMVC.xml

<!-- 配置国际化资源文件 -->
<bean class="org.springframework.context.support.ResourceBundleMessageSource" id="messageSource">
    <property name="basename" value="i18n"></property>
</bean>

index.jsp

<body>
    <a href="springmvc/testViewAndViewResolver">test ViewAndViewResolver</a>
</body>

SpringMVCTest.java

@SessionAttributes(value={"user"}, types={String.class})
@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {

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

在目标页面引入JSTL的fmt标签
success.jsp

<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<body>
    <fmt:message key="i18n.username"></fmt:message>
    <br><br>

    <fmt:message key="i18n.password"></fmt:message>
    <br><br>
</body>

mvc:view-controller

若希望直接响应通过 SpringMVC 渲染的页面,可以使用 mvc:view- controller 标签实现

在springmvc配置文件中增加内容
springmvc.xml

<!-- 配置直接转发的页面,可以直接响应转发的页面,而无需再经过 Handler 的方法 -->
<mvc:view-controller path="/success" view-name="success"/>

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

如果只配置mvc:view-controller,我们直接访问 Handler 转发的页面success可以,但是其他的访问就失败了
如果想要直接访问转发的页面和 Handler 的转发都可用,就需要配置 mvc:annotation-driven 标签

自定义视图

新建一个视图
HelloView.java

@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());
    }
}

配置视图解析器
springmvc.xml

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


<!-- 配置视图  BeanNameViewResolver 解析器: 使用视图的名字来解析视图 -->
<!-- 通过 order 属性来定义视图解析器的优先级, order 值越小优先级越高 -->
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
    <property name="order" value="100"></property>
</bean> 

index.jsp

<a href="springmvc/testView">test View</a>

SpringMVCTest.java

@SessionAttributes(value={"user"}, types={String.class})
@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {

    @RequestMapping("/testView")
    public String testView(){
        System.out.println("testView");
        return "helloView";//类名第一个字母小写
    }
}

重定向

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

如果返回的字符串中带 forward: 或 redirect: 前缀时,SpringMVC 会对他们进行特殊处理:将 forward: 和
redirect: 当成指示符,其后的字符串作为 URL 来处理

  • redirect:success.jsp:会完成一个到 success.jsp 的重定向的操作
  • forward:success.jsp:会完成一个到 success.jsp 的转发操作

index.jsp

<a href="springmvc/testRedirect">test redirect</a>

SpringMVCTest.java

@SessionAttributes(value={"user"}, types={String.class})
@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {

    @RequestMapping("/testRedirect")
    public String testRedirect(){
        System.out.println("test Redirect");
        return "redirect:/index.jsp";
    }
}

RESTRUL风格的CRUD & 表单标签 & 处理静态资源

表单标签

form标签主要用于表单回显

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

一般情况下,通过 GET 请求获取表单页面,而通过 POST 请求提交表单页面,因此获取表单页面和提交表单页面的 URL 是相同的。只要满足该最佳条件的契约,<form:form> 标签就无需通过 action 属性指定表单提交的 URL

可以通过 modelAttribute 属性指定绑定的模型属性,若没有指定该属性,则默认从 request 域对象中读取 command 的表单 bean,如果该属性值也不存在,则会发生错误。

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

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

form:input、form:password、form:hidden、form:textarea :对应 HTML 表单的 text、password、hidden、textarea 标签

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

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

  • items:可以是一个 List、String[] 或 Map
  • itemValue:指定 radio 的 value 值。可以是集合中 bean 的一个属性值
  • itemLabel:指定 radio 的 label 值
  • delimiter:多个单选框可以通过 delimiter 指定分隔符

entities:
Employee.java

public class Employee {

    private Integer id;
    private String lastName;

    private String email;
    //1 male, 0 female
    private Integer gender;

    private Department department;
    ... ...
}

Department.java

public class Department {

    private Integer id;
    private String departmentName;
    ... ...
}

dao:
EmployeeDao.java

@Repository
public class EmployeeDao {

    private static Map<Integer, Employee> employees = null;

    @Autowired
    private DepartmentDao departmentDao;

    static{
        employees = new HashMap<Integer, Employee>();

        employees.put(1001, new Employee(1001, "E-AA", "aa@163.com", 1, new Department(101, "D-AA")));
        employees.put(1002, new Employee(1002, "E-BB", "bb@163.com", 1, new Department(102, "D-BB")));
        employees.put(1003, new Employee(1003, "E-CC", "cc@163.com", 0, new Department(103, "D-CC")));
        employees.put(1004, new Employee(1004, "E-DD", "dd@163.com", 0, new Department(104, "D-DD")));
        employees.put(1005, new Employee(1005, "E-EE", "ee@163.com", 1, new Department(105, "D-EE")));
    }

    private static Integer initId = 1006;

    public void save(Employee employee){
        if(employee.getId() == null){
            employee.setId(initId++);
        }

        employee.setDepartment(departmentDao.getDepartment(employee.getDepartment().getId()));
        employees.put(employee.getId(), employee);
    }

    public Collection<Employee> getAll(){
        return employees.values();
    }

    public Employee get(Integer id){
        return employees.get(id);
    }

    public void delete(Integer id){
        employees.remove(id);
    }
}

DepartmentDao.java

@Repository
public class DepartmentDao {

    private static Map<Integer, Department> departments = null;

    static{
        departments = new HashMap<Integer, Department>();

        departments.put(101, new Department(101, "D-AA"));
        departments.put(102, new Department(102, "D-BB"));
        departments.put(103, new Department(103, "D-CC"));
        departments.put(104, new Department(104, "D-DD"));
        departments.put(105, new Department(105, "D-EE"));
    }

    public Collection<Department> getDepartments(){
        return departments.values();
    }

    public Department getDepartment(Integer id){
        return departments.get(id);
    }
}

handler:
EmployeeHandler.java

@Controller
public class EmployeeHandler {

    @Autowired
    EmployeeDao employeeDao;

    @Autowired
    DepartmentDao departmentDao;

    @RequestMapping("/emps")
    public String list(Map<String, Object> map){
        map.put("employees", employeeDao.getAll());
        return "list";
    }

    @RequestMapping(value="/emp",method=RequestMethod.GET)
    public String input(Map<String, Object> map){
        map.put("departments", departmentDao.getDepartments());
        //为请求域添加bean,这里不需要回显,所以自己new一个空的就可以了
        map.put("employee", new Employee());
        return "input";
    }

    @RequestMapping(value="/emp", method=RequestMethod.POST)
    public String save(Employee employee){
        employeeDao.save(employee);
        return "redirect:/emps";
    }

    @RequestMapping(value="/emp/{id}", method=RequestMethod.DELETE)
    public String delete(@PathVariable("id") Integer id){
        employeeDao.delete(id);
        return "redirect:/emps";
    }

    @RequestMapping(value="/emp/{id}",method=RequestMethod.GET)
    public String input(@PathVariable("id") Integer id,Map<String, Object> map){
        map.put("employee", employeeDao.get(id));
        map.put("departments", departmentDao.getDepartments());
        return "input";
    }

    @RequestMapping(value="/emp", method=RequestMethod.PUT)
    public String update(Employee employee){
        employeeDao.save(employee);
        return "redirect:/emps";
    }

    @ModelAttribute
    public void getEmployee(@RequestParam(value="id",required=false) Integer id, Map<String, Object> map){
        if(id!=null){
            map.put("employee", employeeDao.get(id));
        }
    }
}

web.xml

<!-- 配置 DispatcherServlet -->
    <!-- The front controller of this Spring Web application, responsible for handling all application requests -->
    <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>

    <!-- Map all requests to the DispatcherServlet for handling -->
    <servlet-mapping>
        <servlet-name>springDispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!-- 配置 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>

springmvc.xml

    <!-- 配置指定扫描的包 -->
    <context:component-scan base-package="com.atguigu.springmvc.crud"></context:component-scan>

    <!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="applicationContextAware">
    <property name="prefix" value="/WEB-INF/views/"></property>
    <property name="suffix" value=".jsp"></property>
</bean>

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

index.jsp

<a href="emps">show all employees</a>

list.jsp

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<!-- 
jQuery的js导入无法使用,因为DispatcherServlet把js的请求路径http://localhost:8080/myspringmvc-2/script/jquery-1.9.1.min.js
也拦截了
SpringMVC 处理静态资源:
1. 为什么会有这样的问题:
优雅的 REST 风格的资源URL 不希望带 .html 或 .do 等后缀
若将 DispatcherServlet 请求映射配置为 /, 
则 Spring MVC 将捕获 WEB 容器的所有请求, 包括静态资源的请求, SpringMVC 会将他们当成一个普通请求处理, 
因找不到对应处理器将导致错误。
2. 解决: 在 SpringMVC 的配置文件中配置 <mvc:default-servlet-handler/>

添加了之后http://localhost:8080/myspringmvc-2/script/jquery-1.9.1.min.js可以获取到js文件
但是普通的http://localhost:8080/myspringmvc-2/emps不能用了
再在 SpringMVC 的配置文件中添加<mvc:annotation-driven></mvc:annotation-driven>即可
 -->
<script type="text/javascript" src="scripts/jquery-1.9.1.min.js"></script>
<script type="text/javascript">
    /* 把post请求转为delete请求 */
    $(function(){
        $(".delete").click(function(){
            var href = $(this).attr("href");
            $("form").attr("action", href).submit();
            return false;
        });
    });
</script>
</head>
<body>
    <form action="" method="post">
        <input type="hidden" name="_method" value="DELETE"/>
    </form>
    <c:if test="${empty requestScope.employees }">
        没有信息,employee为空
    </c:if>
    <c:if test="${!empty requestScope.employees }">
        <table border="1" cellpadding="10" cellspacing="0">
            <tr>
                <th>ID</th>
                <th>LastName</th>
                <th>Email</th>
                <th>Gender</th>
                <th>Department</th>
                <th>Edit</th>
                <th>Delete</th>
            </tr>
            <c:forEach items="${requestScope.employees }" var="emp">
                <tr>
                    <td>${emp.id }</td>
                    <td>${emp.lastName }</td>
                    <td>${emp.email }</td>
                    <td>${emp.gender == 0 ? 'Female' : 'Male' }</td>
                    <td>${emp.department.departmentName }</td>
                    <td><a href="emp/${emp.id }">Edit</a></td>
                    <td><a class="delete" href="emp/${emp.id }">Delete</a></td>
                </tr>
            </c:forEach>
        </table>
    </c:if>
    <br><br>
    <a href="emp">Add An Employee</a>
</body>
</html>

input.jsp

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

    <!--  
        1. WHY 使用 form 标签呢 ?
        可以更快速的开发出表单页面, 而且可以更方便的进行表单值的回显
        2. 注意:
        可以通过 modelAttribute 属性指定绑定的模型属性, 
        若没有指定该属性,则默认从 request 域对象中读取 command 的表单 bean
        如果该属性值也不存在,则会发生错误。

        也就是说springmvc默认这个表单是需要进行回显的,所以它会去请求域中寻找bean,就要在Handler的map中加入bean对象
        并且默认的名字是 command ,所以需要指定为map中bean对象的名字
    -->
    <form:form action="${pageContext.request.contextPath }/emp" method="POST" modelAttribute="employee">
        <!-- path 属性对应 html 表单标签的 name 属性值 -->
        <c:if test="${employee.id == null }">
            LastName: <form:input path="lastName"/>
        </c:if>
        <c:if test="${employee.id != null }">
            <form:hidden path="id"/>
            <input type="hidden" name="_method" value="PUT"/>
            <%-- 对于 _method 不能使用 form:hidden 标签, 因为 modelAttribute 对应的 bean 中没有 _method 这个属性 --%>
            <%-- 
            <form:hidden path="_method" value="PUT"/>
            --%>
        </c:if>
        <br><br>
        Email: <form:input path="email"/>
        <br><br>
        <%
            Map<String, String> genders = new HashMap<String, String>();

            genders.put("1", "Male");
            genders.put("0", "Female");

            request.setAttribute("genders", genders);
        %>
        Gender: <form:radiobuttons path="gender" items="${genders }"/>
        <br><br>
        <!-- path 支持级联属性,这里传递给employee的只是department的id -->
        Department: <form:select path="department.id" items="${departments }" itemLabel="departmentName" itemValue="id"></form:select>
        <br><br>
        <input type="submit" value="Submit"/>
    </form:form>
</body>
</html>

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

数据转换

数据绑定流程分析

1.Spring MVC 主框架将 ServletRequest 对象及目标方法的入参实例传递给 WebDataBinderFactory 实例,以创建 DataBinder 实例对象
2.DataBinder 调用装配在 Spring MVC 上下文中的 ConversionService 组件进行数据类型转换、数据格式化工作。将 Servlet 中的请求信息填充到入参对象中
3.调用 Validator 组件对已经绑定了请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果 BindingData 对象
4.Spring MVC 抽取 BindingResult 中的入参对象和校验错误对象,将它们赋给处理方法的响应入参

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

这里写图片描述

自定义类型转换器(了解)

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

自定义类型转换器流程:

input.jsp

<form action="testConversionServiceConverer" method="POST">
    <!-- lastname-email-gender-department.id 例如: GG-gg@atguigu.com-0-105 -->
    Employee: <input type="text" name="employee"/>
    <input type="submit" value="Submit"/>
</form>

SpringMVCTest.java

@RequestMapping("/testConversionServiceConverer")
public String testConverter(@RequestParam("employee") Employee employee){
    System.out.println("save: " + employee);
    employeeDao.save(employee);
    return "redirect:/emps";
}

EmployeeConverter.java

@Component
public class EmployeeConverter implements Converter<String, Employee> {

    @Override
    public Employee convert(String source) {
        if(source != null){
            String [] vals = source.split("-");
            //GG-gg@atguigu.com-0-105
            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 employee = new Employee(null, lastName, email, gender, department);
                System.out.println(source + "--convert--" + employee);
                return employee;
            }
        }
        return null;
    }
}

springmvc.xml

<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>  

<!-- 配置 ConversionService -->
<bean id="conversionService"
    class="org.springframework.format.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <ref bean="employeeConverter"/>
        </set>
    </property> 
</bean>

annotation-driven配置

之前我们用到annotation-driven配置的有三个地方:
1.直接响应转发的页面,不经过handler,配置完mvc:view-controller之后发现 Requestmapping 不好用了,配置 annotation-driven 后解决
2.访问静态资源,配置mvc:default-servlet-handler后 Requestmapping 又不好用了,配置 annotation-driven 解决
3.自定义类型转换器后装配自定义的类型转换器

<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。
  • @InitBinder方法的参数通常是是 WebDataBinder

EmployeeHandler.java

/**
* 不自动绑定对象中的 lastName 属性,另行处理
* 也就是说在从新增employee的时候input提交到handler然后到list后,lastName不进行赋值,实体类的lastName为空
*/
@InitBinder
public void initBinder(WebDataBinder binder){
    binder.setDisallowedFields("lastName");
}

数据格式化

其实基本上,数据格式化和数据转换是分不开的。

Spring 在格式化模块中定义了一个实现 ConversionService 接口的 FormattingConversionService 实现类,该实现类扩展了 GenericConver sionService,因此它既具有类型转换的功能,又具有格式化的功能

FormattingConversionService 拥有一个 FormattingConversionServiceFactroyBean 工厂类,后者用于在 Spring 上下文中构造前者

FormattingConversionServiceFactroyBean 内部已经注册了 :

  • NumberFormatAnnotationFormatterFactroy:支持对数字类型的属性使用 @NumberFormat 注解
  • JodaDateTimeFormatAnnotationFormatterFactroy:支持对日期类型的属性使用 @DateTimeFormat 注解

装配了 FormattingConversionServiceFactroyBean 后,就可以在 Spring MVC 入参绑定及模型数据输出时使用注解驱动了。<mvc:annotation-driven/> 默认创建的 ConversionService 实例即为 FormattingConversionServiceFactroyBean

日期格式化

@DateTimeFormat 注解可对 java.util.Date、java.util.Calendar、java.long.Long 时间类型进行标注:

  • pattern 属性:类型为字符串。指定解析/格式化字段数据的模式, 如:”yyyy-MM-dd hh:mm:ss”
  • iso 属性:类型为 DateTimeFormat.ISO。指定解析/格式化字段数据的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 属性:字符串类型。通过样式指定日期时间的格式,由两位字符组成,第一位表示日期的格式,第二位表示时间的格式:S:短日期/时间格式、M:中日期/时间格式、L:长日期/时间格式、F:完整日期/时间格式、-:忽略日期或时间格式

数值格式化

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

  • style:类型为 NumberFormat.Style。用于指定– 样式类型,包括三种:Style.NUMBER(正常数字类型)、Style.CURRENCY(货币类型)、 Style.PERCENT(百分数类型)
  • pattern:类型为 String,自定义样式, 如patter=”#,###”;

springmvc.xml

<mvc:annotation-driven></mvc:annotation-driven>

<!-- 配置 ConversionService -->
<!-- 如果想要使用之前的自定义类型转换器,就要把ConversionServiceFactoryBean 改为 FormattingConversionServiceFactoryBean -->
<bean id="" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <ref bean="employeeConverter"/>
        </set>
    </property>
</bean>

Employee.java

public class Employee {

    private Integer id;
    private String lastName;

    private String email;
    //1 male, 0 female
    private Integer gender;

    private Department department;

    @DateTimeFormat(pattern="yyyy-MM-dd")
    private Date birth;
    @NumberFormat(pattern="#,###,###.#")
    private Float salary;
    ... ...
}

EmployeeHandler.java

@Controller
public class EmployeeHandler {

    @Autowired
    EmployeeDao employeeDao;

    @Autowired
    DepartmentDao departmentDao;

    @RequestMapping(value="/emp", method=RequestMethod.POST)
    public String save(Employee employee){
        System.out.println("save:" + employee);
        employeeDao.save(employee);
        return "redirect:/emps";
    }
}

input.jsp

... ...
<br><br>
Birth: <form:input path="birth"/>
<br><br>
salary: <form:input path="salary"/>
... ...

步骤:
1.配置annotation-driven
2.在目标属性上加@DateTimeFormat或@NumberFormat

PS:如果转换失败的话怎么办?
我们可以再目标方法中加入参数BindingResult,如果类型转换错误,错误信息就保存在这里面

EmployeeHandler.java

@RequestMapping(value="/emp", method=RequestMethod.POST)
public String save(Employee employee, BindingResult result){
    System.out.println("save:" + employee);

    if(result.getErrorCount() > 0){
        System.out.println("出错了");

        for(FieldError error : result.getFieldErrors()){
            System.out.println(error.getField() + ":" + error.getDefaultMessage());
        }
    }
    employeeDao.save(employee);

    return "redirect:/emps";
}

数据校验

提出问题:
1.如何校验?能否通过注解的方式完成?
2.如果校验出现错误转向哪一个页面
3.错误消息如何进行显示,如何把错误消息进行国际化

JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 中。
JSR 303 提供的注解:

这里写图片描述

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

这里写图片描述

步骤:
①. 加入 实现 JSR 303 验证标准的 hibernate validator 验证框架的 jar 包

  • hibernate-validator-5.0.0.CR2.jar
  • hibernate-validator-annotation-processor-5.0.0.CR2.jar
  • classmate-0.8.0.jar
  • jboss-logging-3.1.1.GA.jar
  • validation-api-1.1.0.CR1.jar

②. 在 SpringMVC 配置文件中配置 LocalValidatorFactoryBean ,其实 mvc:annotation-driven 会默认装配好一个 LocalValidatorFactoryBean ,所以只需要配置一个 mvc:annotation-driven 就可以
③.需要在 bean 的属性上添加对应的校验注解
④. 在目标方法 bean 类型的前面添加 @Valid 注解

当校验出错时,SpringMVC 会把错误信息保存在目标方法的入参 BindingResult 或 Errors 类型的参数中,需要注意的是,需校验的 Bean 对象和其绑定结果对象或错误对象时成对出现的,它们之间不允许声明其他的入参,就是说Bean对象和BindingResult或Errors必须挨着

目标:
Employee的lastName不能为空,email必须是邮件格式,birth必须在当前日期之前

Employee.java

public class Employee {

    private Integer id;
    @NotEmpty
    private String lastName;
    @Email
    private String email;
    //1 male, 0 female
    private Integer gender;

    private Department department;

    @Past
    @DateTimeFormat(pattern="yyyy-MM-dd")
    private Date birth;
    @NumberFormat(pattern="#,###,###.#")
    private Float salary;
    ... ...
}

EmployeeHandler.java

@RequestMapping(value="/emp", method=RequestMethod.POST)
public String save(@Valid Employee employee, BindingResult result, Map<String, Object> map){
    System.out.println("save:" + employee);

    if(result.getErrorCount() > 0){
        System.out.println("出错了");

        for(FieldError error : result.getFieldErrors()){
            System.out.println(error.getField() + ":" + error.getDefaultMessage());
        }

        //若验证出错,则转向定制的页面
        map.put("departments", departmentDao.getDepartments());
        return "input";
    }
    employeeDao.save(employee);

    return "redirect:/emps";
}

错误消息显示

如果想要在页面上显示错误消息,可以使用 form 标签

  • <form:errors path=“*”>:可以显示全部的错误消息
  • <form:errors path=“lastName”>:可以显示指定字段的错误消息

input.jsp

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

    <!--  
        1. WHY 使用 form 标签呢 ?
        可以更快速的开发出表单页面, 而且可以更方便的进行表单值的回显
        2. 注意:
        可以通过 modelAttribute 属性指定绑定的模型属性, 
        若没有指定该属性,则默认从 request 域对象中读取 command 的表单 bean
        如果该属性值也不存在,则会发生错误。

        也就是说springmvc默认这个表单是需要进行回显的,所以它会去请求域中寻找bean,就要在Handler的map中加入bean对象
        并且默认的名字是 command ,所以需要指定为map中bean对象的名字
    -->
    <form:form action="${pageContext.request.contextPath }/emp" method="POST" modelAttribute="employee">

        <form:errors path="*"></form:errors>
        <br>


        <!-- path 属性对应 html 表单标签的 name 属性值 -->
        <c:if test="${employee.id == null }">
            LastName: <form:input path="lastName"/>
            <form:errors path="lastName"></form:errors>
        </c:if>
        <c:if test="${employee.id != null }">
            <form:hidden path="id"/>
            <input type="hidden" name="_method" value="PUT"/>
            <%-- 对于 _method 不能使用 form:hidden 标签, 因为 modelAttribute 对应的 bean 中没有 _method 这个属性 --%>
            <%-- 
            <form:hidden path="_method" value="PUT"/>
            --%>
        </c:if>

        <br><br>
        Email: <form:input path="email"/>
        <form:errors path="email"></form:errors>
        <br><br>
        <%
            Map<String, String> genders = new HashMap<String, String>();

            genders.put("1", "Male");
            genders.put("0", "Female");

            request.setAttribute("genders", genders);
        %>
        Gender: <form:radiobuttons path="gender" items="${genders }"/>
        <br><br>
        <!-- path 支持级联属性,这里传递给employee的只是department的id -->
        Department: <form:select path="department.id" items="${departments }" itemLabel="departmentName" itemValue="id"></form:select>
        <br><br>
        Birth: <form:input path="birth"/>
        <form:errors path="birth"></form:errors>
        <br><br>
        salary: <form:input path="salary"/>
        <input type="submit" value="Submit"/>
    </form:form>
</body>
</html>

国际化

步骤:
1.创建国际化资源文件
2.将国际化资源文件配置到springmvc.xml文件中

国际化资源文件中的键为以校验注解类名为前缀,结合 modleAttribute、属性名及属性类型名生成,
值为定制的消息

若数据类型转换或数据格式转换时发生错误,或该有的参数不存在,或调用处理方法时发生错误,都会在隐含模型中创建错误消息。其错误代码前缀说明如下:

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

i18n.properties

NotEmpty.employee.lastName=^^LastName\u4E0D\u80FD\u4E3A\u7A7A.
Email.employee.email=Email\u5730\u5740\u4E0D\u5408\u6CD5
Past.employee.birth=Birth\u4E0D\u80FD\u662F\u4E00\u4E2A\u5C06\u6765\u7684\u65F6\u95F4. 

typeMismatch.employee.birth=Birth\u4E0D\u662F\u4E00\u4E2A\u65E5\u671F. 

springmv.xml

<!-- 配置国际化资源文件 -->
<bean id="messageSource"
    class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basename" value="i18n"></property>
</bean>
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值