手码两万余字,SpringMVC 包教包会(四)

简介: 手码两万余字,SpringMVC 包教包会(四)

10.2 分组校验

由于校验规则都是定义在实体类上面的,但是,在不同的数据提交环境下,校验规则可能不一样。例如,用户的 id 是自增长的,添加的时候,可以不用传递用户 id,但是修改的时候则必须传递用户 id,这种情况下,就需要使用分组校验。

分组校验,首先需要定义校验组,所谓的校验组,其实就是空接口:

public interface ValidationGroup1 {
}
public interface ValidationGroup2 {
}
复制代码

然后,在实体类中,指定每一个校验规则所属的组:

public class Student {
    @NotNull(message = "{student.id.notnull}",groups = ValidationGroup1.class)
    private Integer id;
    @NotNull(message = "{student.name.notnull}",groups = {ValidationGroup1.class, ValidationGroup2.class})
    @Size(min = 2,max = 10,message = "{student.name.length}",groups = {ValidationGroup1.class, ValidationGroup2.class})
    private String name;
    @Email(message = "{student.email.error}",groups = {ValidationGroup1.class, ValidationGroup2.class})
    private String email;
    @Max(value = 150,message = "{student.age.error}",groups = {ValidationGroup2.class})
    private Integer age;
    public String getEmail() {
        return email;
    }
    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", age=" + age +
                '}';
    }
    public void setEmail(String email) {
        this.email = email;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
复制代码

在 group 中指定每一个校验规则所属的组,一个规则可以属于一个组,也可以属于多个组。

最后,在接收参数的地方,指定校验组:

@Controller
public class StudentController {
    @RequestMapping("/addstudent")
    @ResponseBody
    public void addStudent(@Validated(ValidationGroup2.class) Student student, BindingResult result) {
        if (result != null) {
            //校验未通过,获取所有的异常信息并展示出来
            List<ObjectError> allErrors = result.getAllErrors();
            for (ObjectError allError : allErrors) {
                System.out.println(allError.getObjectName()+":"+allError.getDefaultMessage());
            }
        }
    }
}
复制代码

配置完成后,属于 ValidationGroup2 这个组的校验规则,才会生效。

10.3 校验注解

校验注解,主要有如下几种:

  • @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(regex=,flag=) 被注解的元素必须符合指定的正则表达式
  • @NotBlank(message =) 验证字符串非 null,且长度必须大于0
  • @Email 被注解的元素必须是电子邮箱地址
  • @Length(min=,max=) 被注解的字符串的大小必须在指定的范围内
  • @NotEmpty 被注解的字符串的必须非空
  • @Range(min=,max=,message=) 被注解的元素必须在合适的范围内

11.1 数据回显基本用法

数据回显就是当用户数据提交失败时,自动填充好已经输入的数据。一般来说,如果使用 Ajax 来做数据提交,基本上是没有数据回显这个需求的,但是如果是通过表单做数据提交,那么数据回显就非常有必要了。

11.1.1 简单数据类型

简单数据类型,实际上框架在这里没有提供任何形式的支持,就是我们自己手动配置。我们继续在第 10 小节的例子上演示 Demo。加入提交的 Student 数据不符合要求,那么重新回到添加 Student 页面,并且预设之前已经填好的数据。

首先我们先来改造一下 student.jsp 页面:

<form action="/addstudent" method="post">
    <table>
        <tr>
            <td>学生编号:</td>
            <td><input type="text" name="id" value="${id}"></td>
        </tr>
        <tr>
            <td>学生姓名:</td>
            <td><input type="text" name="name" value="${name}"></td>
        </tr>
        <tr>
            <td>学生邮箱:</td>
            <td><input type="text" name="email" value="${email}"></td>
        </tr>
        <tr>
            <td>学生年龄:</td>
            <td><input type="text" name="age" value="${age}"></td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" value="提交">
            </td>
        </tr>
    </table>
</form>
复制代码

在接收数据时,使用简单数据类型去接收:

@RequestMapping("/addstudent")
public String addStudent2(Integer id, String name, String email, Integer age, Model model) {
    model.addAttribute("id", id);
    model.addAttribute("name", name);
    model.addAttribute("email", email);
    model.addAttribute("age", age);
    return "student";
}
复制代码

这种方式,相当于框架没有做任何工作,就是我们手动做数据回显的。此时访问页面,服务端会再次定位到该页面,而且数据已经预填好。

11.1.2 实体类

上面这种简单数据类型的回显,实际上非常麻烦,因为需要开发者在服务端一个一个手动设置。如果使用对象的话,就没有这么麻烦了,因为 SpringMVC 在页面跳转时,会自动将对象填充进返回的数据中。

此时,首先修改一下 student.jsp 页面:

<form action="/addstudent" method="post">
    <table>
        <tr>
            <td>学生编号:</td>
            <td><input type="text" name="id" value="${student.id}"></td>
        </tr>
        <tr>
            <td>学生姓名:</td>
            <td><input type="text" name="name" value="${student.name}"></td>
        </tr>
        <tr>
            <td>学生邮箱:</td>
            <td><input type="text" name="email" value="${student.email}"></td>
        </tr>
        <tr>
            <td>学生年龄:</td>
            <td><input type="text" name="age" value="${student.age}"></td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" value="提交">
            </td>
        </tr>
    </table>
</form>
复制代码

注意,在预填数据中,多了一个 student. 前缀。这 student 就是服务端接收数据的变量名,服务端的变量名和这里的 student 要保持一直。服务端定义如下

@RequestMapping("/addstudent")
public String addStudent(@Validated(ValidationGroup2.class) Student student, BindingResult result) {
    if (result != null) {
        //校验未通过,获取所有的异常信息并展示出来
        List<ObjectError> allErrors = result.getAllErrors();
        for (ObjectError allError : allErrors) {
            System.out.println(allError.getObjectName()+":"+allError.getDefaultMessage());
        }
        return "student";
    }
    return "hello";
}
复制代码

注意,服务端什么都不用做,就说要返回的页面就行了,student 这个变量会被自动填充到返回的 Model 中。变量名就是填充时候的 key。如果想自定义这个 key,可以在参数中写出来 Model,然后手动加入 Student 对象,就像简单数据类型回显那样。

另一种定义回显变量别名的方式,就是使用 @ModelAttribute 注解。

11.2 @ModelAttribute

@ModelAttribute 这个注解,主要有两方面的功能:

  1. 在数据回显时,给变量定义别名
  2. 定义全局数据

11.2.1 定义别名

在数据回显时,给变量定义别名,非常容易,直接加这个注解即可:

@RequestMapping("/addstudent")
public String addStudent(@ModelAttribute("s") @Validated(ValidationGroup2.class) Student student, BindingResult result) {
    if (result != null) {
        //校验未通过,获取所有的异常信息并展示出来
        List<ObjectError> allErrors = result.getAllErrors();
        for (ObjectError allError : allErrors) {
            System.out.println(allError.getObjectName()+":"+allError.getDefaultMessage());
        }
        return "student";
    }
    return "hello";
}
复制代码

这样定义完成后,在前端再次访问回显的变量时,变量名称就不是 student 了,而是 s:

<form action="/addstudent" method="post">
    <table>
        <tr>
            <td>学生编号:</td>
            <td><input type="text" name="id" value="${s.id}"></td>
        </tr>
        <tr>
            <td>学生姓名:</td>
            <td><input type="text" name="name" value="${s.name}"></td>
        </tr>
        <tr>
            <td>学生邮箱:</td>
            <td><input type="text" name="email" value="${s.email}"></td>
        </tr>
        <tr>
            <td>学生年龄:</td>
            <td><input type="text" name="age" value="${s.age}"></td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" value="提交">
            </td>
        </tr>
    </table>
</form>
复制代码


11.2.2 定义全局数据

假设有一个 Controller 中有很多方法,每个方法都会返回数据给前端,但是每个方法返回给前端的数据又不太一样,虽然不太一样,但是没有方法的返回值又有一些公共的部分。可以将这些公共的部分提取出来单独封装成一个方法,用 @ModelAttribute 注解来标记。

例如在一个 Controller 中 ,添加如下代码:

@ModelAttribute("info")
public Map<String,Object> info() {
    Map<String, Object> map = new HashMap<>();
    map.put("username", "javaboy");
    map.put("address", "www.javaboy.org");
    return map;
}
复制代码

当用户访问当前 Controller 中的任意一个方法,在返回数据时,都会将添加了 @ModelAttribute 注解的方法的返回值,一起返回给前端。@ModelAttribute 注解中的 info 表示返回数据的 key。

12.1 返回 JSON

目前主流的 JSON 处理工具主要有三种:

  • jackson
  • gson
  • fastjson

在 SpringMVC 中,对 jackson 和 gson 都提供了相应的支持,就是如果使用这两个作为 JSON 转换器,只需要添加对应的依赖就可以了,返回的对象和返回的集合、Map 等都会自动转为 JSON,但是,如果使用 fastjson,除了添加相应的依赖之外,还需要自己手动配置 HttpMessageConverter 转换器。其实前两个也是使用 HttpMessageConverter 转换器,但是是 SpringMVC 自动提供的,SpringMVC 没有给 fastjson 提供相应的转换器。

12.1.1 jackson

jackson 是一个使用比较多,时间也比较长的 JSON 处理工具,在 SpringMVC 中使用 jackson ,只需要添加 jackson 的依赖即可:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.10.1</version>
</dependency>
复制代码

依赖添加成功后,凡是在接口中直接返回的对象,集合等等,都会自动转为 JSON。如下:

public class Book {
    private Integer id;
    private String name;
    private String author;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAuthor() {
        return author;
    }
    public void setAuthor(String author) {
        this.author = author;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
}
@RequestMapping("/book")
@ResponseBody
public Book getBookById() {
    Book book = new Book();
    book.setId(1);
    book.setName("三国演义");
    book.setAuthor("罗贯中");
    return book;
}
复制代码

这里返回一个对象,但是在前端接收到的则是一个 JSON 字符串,这个对象会通过 HttpMessageConverter 自动转为 JSON 字符串。

如果想返回一个 JSON 数组,写法如下:

@RequestMapping("/books")
@ResponseBody
public List<Book> getAllBooks() {
    List<Book> list = new ArrayList<Book>();
    for (int i = 0; i < 10; i++) {
        Book book = new Book();
        book.setId(i);
        book.setName("三国演义:" + i);
        book.setAuthor("罗贯中:" + i);
        list.add(book);
    }
    return list;
}
复制代码

添加了 jackson ,就能够自动返回 JSON,这个依赖于一个名为 HttpMessageConverter 的类,这本身是一个接口,从名字上就可以看出,它的作用是 Http 消息转换器,既然是消息转换器,它提供了两方面的功能:

  1. 将返回的对象转为 JSON
  2. 将前端提交上来的 JSON 转为对象

但是,HttpMessageConverter 只是一个接口,由各个 JSON 工具提供相应的实现,在 jackson 中,实现的名字叫做 MappingJackson2HttpMessageConverter,而这个东西的初始化,则由 SpringMVC 来完成。除非自己有一些自定义配置的需求,否则一般来说不需要自己提供 MappingJackson2HttpMessageConverter。

举一个简单的应用场景,例如每一本书,都有一个出版日期,修改 Book 类如下:

public class Book {
    private Integer id;
    private String name;
    private String author;
    private Date publish;
    public Date getPublish() {
        return publish;
    }
    public void setPublish(Date publish) {
        this.publish = publish;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAuthor() {
        return author;
    }
    public void setAuthor(String author) {
        this.author = author;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
}
复制代码

然后在构造 Book 时添加日期属性:

@RequestMapping("/book")
@ResponseBody
public Book getBookById() {
    Book book = new Book();
    book.setId(1);
    book.setName("三国演义");
    book.setAuthor("罗贯中");
    book.setPublish(new Date());
    return book;
}
复制代码

访问 /book 接口,返回的 json 格式如下:


如果我们想自己定制返回日期的格式,简单的办法,可以通过添加注解来实现:

public class Book {
    private Integer id;
    private String name;
    private String author;
    @JsonFormat(pattern = "yyyy-MM-dd",timezone = "Asia/Shanghai")
    private Date publish;
复制代码

注意这里一定要设置时区。

这样,就可以定制返回的日期格式了。

但是,这种方式有一个弊端,这个注解可以加在属性上,也可以加在类上,也就说,最大可以作用到一个类中的所有日期属性上。如果项目中有很多实体类都需要做日期格式化,使用这种方式就比较麻烦了,这个时候,我们可以自己提供一个 jackson 的 HttpMesageConverter 实例,在这个实例中,自己去配置相关属性,这里的配置将是一个全局配置。

在 SpringMVC 配置文件中,添加如下配置:


<mvc:annotation-driven>
    <mvc:message-converters>
        <ref bean="httpMessageConverter"/>
    </mvc:message-converters>
</mvc:annotation-driven>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" id="httpMessageConverter">
    <property name="objectMapper">
        <bean class="com.fasterxml.jackson.databind.ObjectMapper">
            <property name="dateFormat">
                <bean class="java.text.SimpleDateFormat">
                    <constructor-arg name="pattern" value="yyyy-MM-dd HH:mm:ss"/>
                </bean>
            </property>
            <property name="timeZone" value="Asia/Shanghai"/>
        </bean>
    </property>
</bean>
复制代码

添加完成后,去掉 Book 实体类中日期格式化的注解,再进行测试,结果如下:

12.1.2 gson

gson 是 Google 推出的一个 JSON 解析器,主要在 Android 开发中使用较多,不过,Web 开发中也是支持这个的,而且 SpringMVC 还针对 Gson 提供了相关的自动化配置,以致我们在项目中只要添加 gson 依赖,就可以直接使用 gson 来做 JSON 解析了。

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.6</version>
</dependency>
复制代码

如果项目中,同时存在 jackson 和 gson 的话,那么默认使用的是 jackson,为社么呢?在 org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter 类的构造方法中,加载顺序就是先加载 jackson 的 HttpMessageConverter,后加载 gson 的 HttpMessageConverter。

加完依赖之后,就可以直接返回 JSON 字符串了。使用 Gson 时,如果想做自定义配置,则需要自定义 HttpMessageConverter。

<mvc:annotation-driven>
    <mvc:message-converters>
        <ref bean="httpMessageConverter"/>
    </mvc:message-converters>
</mvc:annotation-driven>
<bean class="org.springframework.http.converter.json.GsonHttpMessageConverter" id="httpMessageConverter">
    <property name="gson">
        <bean class="com.google.gson.Gson" factory-bean="gsonBuilder" factory-method="create"/>
    </property>
</bean>
<bean class="com.google.gson.GsonBuilder" id="gsonBuilder">
    <property name="dateFormat" value="yyyy-MM-dd"/>
</bean>
复制代码

12.1.3 fastjson

fastjson 号称最快的 JSON 解析器,但是也是这三个中 BUG 最多的一个。在 SpringMVC 并没针对 fastjson 提供相应的 HttpMessageConverter,所以,fastjson 在使用时,一定要自己手动配置 HttpMessageConverter(前面两个如果没有特殊需要,直接添加依赖就可以了)。

使用 fastjson,我们首先添加 fastjson 依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.60</version>
</dependency>
复制代码


然后在 SpringMVC 的配置文件中配置 HttpMessageConverter:

<mvc:annotation-driven>
    <mvc:message-converters>
        <ref bean="httpMessageConverter"/>
    </mvc:message-converters>
</mvc:annotation-driven>
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter" id="httpMessageConverter">
    <property name="fastJsonConfig">
        <bean class="com.alibaba.fastjson.support.config.FastJsonConfig">
            <property name="dateFormat" value="yyyy-MM-dd"/>
        </bean>
    </property>
</bean>
复制代码

fastjson 默认中文乱码,添加如下配置解决:

<mvc:annotation-driven>
    <mvc:message-converters>
        <ref bean="httpMessageConverter"/>
    </mvc:message-converters>
</mvc:annotation-driven>
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter" id="httpMessageConverter">
    <property name="fastJsonConfig">
        <bean class="com.alibaba.fastjson.support.config.FastJsonConfig">
            <property name="dateFormat" value="yyyy-MM-dd"/>
        </bean>
    </property>
    <property name="supportedMediaTypes">
        <list>
            <value>application/json;charset=utf-8</value>
        </list>
    </property>
</bean>
复制代码

12.2 接收 JSON

浏览器传来的参数,可以是 key/value 形式的,也可以是一个 JSON 字符串。在 Jsp/Servlet 中,我们接收 key/value 形式的参数,一般是通过 getParameter 方法。如果客户端商户惨的是 JSON 数据,我们可以通过如下格式进行解析:

@RequestMapping("/addbook2")
@ResponseBody
public void addBook2(HttpServletRequest req) throws IOException {
    ObjectMapper om = new ObjectMapper();
    Book book = om.readValue(req.getInputStream(), Book.class);
    System.out.println(book);
}
复制代码

但是这种解析方式有点麻烦,在 SpringMVC 中,我们可以通过一个注解来快速的将一个 JSON 字符串转为一个对象:

@RequestMapping("/addbook3")
@ResponseBody
public void addBook3(@RequestBody Book book) {
    System.out.println(book);
}
复制代码

这样就可以直接收到前端传来的 JSON 字符串了。这也是 HttpMessageConverter 提供的第二个功能。

13. RESTful

本小节选自外部博客,原文链接:www.ruanyifeng.com/blog/2011/0…

越来越多的人开始意识到,网站即软件,而且是一种新型的软件。这种"互联网软件"采用客户端/服务器模式,建立在分布式体系上,通过互联网通信,具有高延时(high latency)、高并发等特点。网站开发,完全可以采用软件开发的模式。但是传统上,软件和网络是两个不同的领域,很少有交集;软件开发主要针对单机环境,网络则主要研究系统之间的通信。互联网的兴起,使得这两个领域开始融合,现在我们必须考虑,如何开发在互联网环境中使用的软件。

RESTful 架构,就是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。

但是,到底什么是 RESTful 架构,并不是一个容易说清楚的问题。下面,我就谈谈我理解的 RESTful 架构。、

RESTful 它不是一个具体的架构,不是一个软件,不是一个框架,而是一种规范。在移动互联网兴起之前,我们都很少提及 RESTful,主要是因为用的少,移动互联网兴起后,RESTful 得到了非常广泛的应用,因为在移动互联网兴起之后,我们再开发后端应用,就不仅仅只是开发一个网站了,还对应了多个前端(Android、iOS、HTML5 等等),这个时候,我们在设计后端接口是,就需要考虑接口的形式,格式,参数的传递等等诸多问题了。

13.1 起源

REST 这个词,是 Roy Thomas Fielding 在他 2000 年的博士论文中提出的。

Fielding 是一个非常重要的人,他是 HTTP 协议(1.0版和1.1版)的主要设计者、Apache 服务器软件的作者之一、Apache 基金会的第一任主席。所以,他的这篇论文一经发表,就引起了关注,并且立即对互联网开发产生了深远的影响。

他这样介绍论文的写作目的:

"本文研究计算机科学两大前沿----软件和网络----的交叉点。长期以来,软件研究主要关注软件设计的分类、设计方法的演化,很少客观地评估不同的设计选择对系统行为的影响。而相反地,网络研究主要关注系统之间通信行为的细节、如何改进特定通信机制的表现,常常忽视了一个事实,那就是改变应用程序的互动风格比改变互动协议,对整体表现有更大的影响。我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。"

13.2 名称

Fielding 将他对互联网软件的架构原则,定名为REST,即 Representational State Transfer 的缩写。我对这个词组的翻译是"表现层状态转化"。

如果一个架构符合 REST 原则,就称它为 RESTful 架构。

要理解 RESTful 架构,最好的方法就是去理解 Representational State Transfer 这个词组到底是什么意思,它的每一个词代表了什么涵义。如果你把这个名称搞懂了,也就不难体会 REST 是一种什么样的设计。

13.3 资源(Resources)

REST 的名称"表现层状态转化"中,省略了主语。"表现层"其实指的是"资源"(Resources)的"表现层"。

所谓"资源",就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。你可以用一个 URI (统一资源定位符)指向它,每种资源对应一个特定的 URI。要获取这个资源,访问它的 URI 就可以,因此 URI 就成了每一个资源的地址或独一无二的识别符。

所谓"上网",就是与互联网上一系列的"资源"互动,调用它的 URI。

在 RESTful 风格的应用中,每一个 URI 都代表了一个资源。

13.4 表现层(Representation)

"资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式,叫做它的"表现层"(Representation)。

比如,文本可以用 txt 格式表现,也可以用 HTML 格式、XML 格式、JSON 格式表现,甚至可以采用二进制格式;图片可以用 JPG 格式表现,也可以用 PNG 格式表现。

URI 只代表资源的实体,不代表它的形式。严格地说,有些网址最后的 ".html" 后缀名是不必要的,因为这个后缀名表示格式,属于 "表现层" 范畴,而 URI 应该只代表"资源"的位置。它的具体表现形式,应该在 HTTP 请求的头信息中用 Accept 和 Content-Type 字段指定,这两个字段才是对"表现层"的描述。

13.5 状态转化(State Transfer)

访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。

互联网通信协议 HTTP 协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。

客户端用到的手段,只能是 HTTP 协议。具体来说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:

  • GET 用来获取资源
  • POST 用来新建资源(也可以用于更新资源)
  • PUT 用来更新资源
  • DELETE 用来删除资源

13.6 综述

综合上面的解释,我们总结一下什么是 RESTful 架构:

  • 每一个 URI 代表一种资源;
  • 客户端和服务器之间,传递这种资源的某种表现层;
  • 客户端通过四个 HTTP 动词,对服务器端资源进行操作,实现"表现层状态转化"。

13.7 误区

RESTful 架构有一些典型的设计误区。

最常见的一种设计错误,就是 URI 包含动词。因为"资源"表示一种实体,所以应该是名词,URI 不应该有动词,动词应该放在 HTTP 协议中。

举例来说,某个 URI 是 /posts/show/1,其中 show 是动词,这个 URI 就设计错了,正确的写法应该是 /posts/1,然后用 GET 方法表示 show。

如果某些动作是HTTP动词表示不了的,你就应该把动作做成一种资源。比如网上汇款,从账户 1 向账户 2 汇款 500 元,错误的 URI 是:

  • POST /accounts/1/transfer/500/to/2

正确的写法是把动词 transfer 改成名词 transaction,资源不能是动词,但是可以是一种服务:

POST /transaction HTTP/1.1
Host: 127.0.0.1
from=1&to=2&amount=500.00
复制代码

另一个设计误区,就是在URI中加入版本号:

因为不同的版本,可以理解成同一种资源的不同表现形式,所以应该采用同一个 URI。版本号可以在 HTTP 请求头信息的 Accept 字段中进行区分(参见 Versioning REST Services):

Accept: vnd.example-com.foo+json; version=1.0
Accept: vnd.example-com.foo+json; version=1.1
Accept: vnd.example-com.foo+json; version=2.0
复制代码

13.8 SpringMVC 的支持

SpringMVC 对 RESTful 提供了非常全面的支持,主要有如下几个注解:

  • @RestController

这个注解是一个组合注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
  /**
   * The value may indicate a suggestion for a logical component name,
   * to be turned into a Spring bean in case of an autodetected component.
   * @return the suggested component name, if any (or empty String otherwise)
   * @since 4.0.1
   */
  @AliasFor(annotation = Controller.class)
  String value() default "";
}
复制代码

一般,直接用 @RestController 来标记 Controller,可以不使用 @Controller。

请求方法中,提供了常见的请求方法:

  • @PostMapping
  • @GetMapping
  • @PutMapping
  • @DeleteMapping

另外还有一个提取请求地址中的参数的注解 @PathVariable:

@GetMapping("/book/{id}")//http://localhost:8080/book/2
public Book getBookById(@PathVariable Integer id) {
    Book book = new Book();
    book.setId(id);
    return book;
}
复制代码

参数 2 将被传递到 id 这个变量上。

14. 静态资源访问

在 SpringMVC 中,静态资源,默认都是被拦截的,例如 html、js、css、jpg、png、txt、pdf 等等,都是无法直接访问的。因为所有请求都被拦截了,所以,针对静态资源,我们要做额外处理,处理方式很简单,直接在 SpringMVC 的配置文件中,添加如下内容:

<mvc:resources mapping="/static/html/**" location="/static/html/"/>
复制代码

mapping 表示映射规则,也是拦截规则,就是说,如果请求地址是 /static/html 这样的格式的话,那么对应的资源就去 /static/html/ 这个目录下查找。

在映射路径的定义中,最后是两个 *,这是一种 Ant 风格的路径匹配符号,一共有三个通配符:

image.png


一个比较原始的配置方式可能如下:

<mvc:resources mapping="/static/html/**" location="/static/html/"/>
<mvc:resources mapping="/static/js/**" location="/static/js/"/>
<mvc:resources mapping="/static/css/**" location="/static/css/"/>
复制代码

但是,由于 ** 可以表示多级路径,所以,以上配置,我们可以进行简化:

<mvc:resources mapping="/**" location="/"/>
复制代码

15. 拦截器

SpringMVC 中的拦截器,相当于 Jsp/Servlet 中的过滤器,只不过拦截器的功能更为强大。

拦截器的定义非常容易:

@Component
public class MyInterceptor1 implements HandlerInterceptor {
    /**
     * 这个是请求预处理的方法,只有当这个方法返回值为 true 的时候,后面的方法才会执行
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("MyInterceptor1:preHandle");
        return true;
    }
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("MyInterceptor1:postHandle");
    }
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("MyInterceptor1:afterCompletion");
    }
}
@Component
public class MyInterceptor2 implements HandlerInterceptor {
    /**
     * 这个是请求预处理的方法,只有当这个方法返回值为 true 的时候,后面的方法才会执行
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("MyInterceptor2:preHandle");
        return true;
    }
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("MyInterceptor2:postHandle");
    }
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("MyInterceptor2:afterCompletion");
    }
}
复制代码

拦截器定义好之后,需要在 SpringMVC 的配置文件中进行配置:

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <ref bean="myInterceptor1"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <ref bean="myInterceptor2"/>
    </mvc:interceptor>
</mvc:interceptors>
复制代码

如果存在多个拦截器,拦截规则如下:

  • preHandle 按拦截器定义顺序调用
  • postHandler 按拦截器定义逆序调用
  • afterCompletion 按拦截器定义逆序调用
  • postHandler 在拦截器链内所有拦截器返成功调用
  • afterCompletion 只有 preHandle 返回 true 才调用
目录
相关文章
|
6月前
|
Java Maven
SpringMVC常用Maven POM.xml依赖包片段
SpringMVC常用Maven POM.xml依赖包片段
|
11月前
|
设计模式 前端开发 Java
手码两万余字,SpringMVC 包教包会(一)
手码两万余字,SpringMVC 包教包会
46 0
|
11月前
|
前端开发 安全 Java
手码两万余字,SpringMVC 包教包会(三)
手码两万余字,SpringMVC 包教包会(三)
40 0
|
11月前
|
JSON 前端开发 Java
手码两万余字,SpringMVC 包教包会(二)
手码两万余字,SpringMVC 包教包会(二)
36 0
|
SQL JSON 前端开发
SpringMvc+Spring+MyBatis+Maven+Ajax+Json注解开发 利用Maven的依赖导入不使用架包模式 (实操十二)
SpringMvc+Spring+MyBatis+Maven+Ajax+Json注解开发 利用Maven的依赖导入不使用架包模式 (实操十二)
77 0
|
JSON 前端开发 Java
SpringMvc+Spring+MyBatis+Maven+Ajax+Json注解开发 利用Maven的依赖导入不使用架包模式 (实操十一)
SpringMvc+Spring+MyBatis+Maven+Ajax+Json注解开发 利用Maven的依赖导入不使用架包模式 (实操十一)
85 0
|
前端开发 JavaScript Java
SpringMvc+Spring+MyBatis+Maven+Ajax+Json注解开发 利用Maven的依赖导入不使用架包模式 (实操十)
SpringMvc+Spring+MyBatis+Maven+Ajax+Json注解开发 利用Maven的依赖导入不使用架包模式 (实操十)
70 0
|
Java Android开发
springMvc37-Unknow tag(c:forEach)错误解决办法,jstl.jar包以及standard.jar包下载与导入
springMvc37-Unknow tag(c:forEach)错误解决办法,jstl.jar包以及standard.jar包下载与导入
122 0
springMvc37-Unknow tag(c:forEach)错误解决办法,jstl.jar包以及standard.jar包下载与导入
|
Java Spring 数据格式
springMVC4.X导入jackson三个核心包报错
本文主要是个人在利用jackson,处理json数据的时候,在spring4.x的环境下导入Jackson的jar包,一开始不知道版本问题,所以跟着视频,在网上随便下载版本,我是下载2.1的版本,之后多次运行,都是出行classnotfund的问题,我确认jar包是没问题的。
1603 0
|
6月前
|
设计模式 前端开发 JavaScript
Spring MVC(一)【什么是Spring MVC】
Spring MVC(一)【什么是Spring MVC】