Spring WebMvc基础-RESTful与mvc中的常用注解

从 0 开始深入学习 Spring 专栏目录总览

上一章咱把部门列表的加载,以及按部门名称的模糊查询都搞定了,最后的小结与练习中咱留下了两个功能,这里咱简单回顾一下。

1. 上章练习回顾

1.1 部门的删除

部门的删除主要想让小伙伴们巩固的是请求参数的收集。编写的 Controller 方法也很简单:

    @RequestMapping("/department/delete")
    public String delete(String id) {
        departmentService.deleteById(id);
        return "dept/deptList";
    }

1.2 部门的修改

部门信息的修改主要巩固的点是基于模型的请求参数绑定,编写的 Controller 代码也不复杂:

    @RequestMapping("/department/edit")
    public String edit(HttpServletRequest request, String id) {
        request.setAttribute("dept", departmentService.findById(id));
        return "dept/deptInfo";
    }
    
    @RequestMapping("/department/save")
    public void save(Department department) {
        System.out.println(department);
    }

1.3 练习的小问题

可能有不少小伙伴在练习时发现了一些问题:当删除 / 修改完成后,直接跳转回部门列表页,因为没有查数据库,将数据放到 request ,会导致跳转过来的表格是没有数据的:

Spring WebMvc基础-RESTful与mvc中的常用注解

而且还有一点,浏览器中地址栏的路径也不对劲,按道理来讲这里应该有一个重定向的动作才对!修改完成后重定向到 /department/list ,而不是直接跳转到 deptList.jsp !

还有,如果部门信息编辑的时候,如果输入中文的话,Controller 拿到的数据是乱码的,这很明显也有问题:

Department{id='84fef077c8dd97be89b2dff7de601073', name='??????'}

最后还有一个小细节,现在写的几个 Controller 的方法,它们都有一个共同的前缀,但每次都要写,很费劲:

@RequestMapping("/department/list")
@RequestMapping("/department/delete")
@RequestMapping("/department/edit")
@RequestMapping("/department/save")

好,下面我们针对这里面的问题,先讲解它们的解决办法,再继续新的内容。

2. Controller方法的返回值【掌握】

我们先捋一下,现在我们学过的 Controller 方法的返回值有哪些了?

    @RequestMapping("/department/list")
    public String list(HttpServletRequest request, String name) {
        request.setAttribute("deptList", departmentService.findDepartmentsByName(name));
        return "dept/deptList";
    }

    @RequestMapping("/department/list2")
    public ModelAndView list2(ModelAndView mav) {
        mav.addObject("deptList", departmentService.findDepartments(null));
        mav.setViewName("dept/deptList");
        return mav;
    }

好像就这两种吧:一个是直接返回视图(jsp)页面的路径,一个是返回 ModelAndView ,它里面也有视图的路径。

除此之外,还有几种返回的内容,一一来看。

2.1 void

其实 Controller 方法的返回值是可以为 void 的,也就是不返回。其实这个 void 一点也不意外,因为 HttpServlet 定义的 doGet 也好,doPost 也好,这些方法的返回值不也是 void 嘛,所以小伙伴不要觉得这个 void 很奇怪哈。

不过一般情况下我们不会常用它,在 SpringWebMvc 的早期,没有注解驱动的时候,可以用返回值 void 来实现 json 数据的响应。比方说这样:

    @RequestMapping("/department/get")
    public void get(HttpServletResponse response, String id) throws IOException {
        Department department = departmentService.findById(id);
        response.setCharacterEncoding("utf-8");
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().println("department序列化后的json");
    }

不过在 SpringWebMvc 发展到 3.0 之后,这种写法便慢慢被淘汰了,新的写法是配合一个 @ResponseBody 注解来的,咱下一章会讲到,这里先简单提一下。

2.2 json

刚才说了,配合 @ResponseBody 注解可以实现 json 数据的输出,所以在 Controller 方法的返回值其实可以是任意类型,只需要在方法上标注一个 @ResponseBody 注解即可。这个注解咱下一章就会讲到,小伙伴们稍安勿躁。

2.3 请求转发与重定向

上面咱提到的一个重定向的问题,其实在返回值为 String 的时候,是可以实现转发和重定向的,它们的写法都很简单。

  • 请求转发:forward:/department/list ,等同于 request.getRequestDispatcher("/department/list").forward(request, response);
  • 重定向:redirect:/department/list ,等同于 response.sendRedirect("/department/list");

这里又要夸一下 IDEA 了,对于请求转发和重定向的路径,IDEA 都能给出提示,真的是非常智能了:

Spring WebMvc基础-RESTful与mvc中的常用注解

3. 请求中文乱码问题【掌握】

紧接着,我们来解决表单提交的中文乱码问题。

3.1 POST请求乱码

Department{id='84fef077c8dd97be89b2dff7de601073', name='??????'}

这种情况的出现,原因是浏览器提交表单的时候,字符编码集与后端 Tomcat 不一致。通常情况下,浏览器并不会发送带 content-type 请求头的字符编码标识符,而是会把字符编码的决定留在读取 HTTP 请求的时候。如果此时浏览器没有指明字符编码集, Web 容器(Tomcat)用来创建请求读和解析 POST 表单数据的的时候,默认编码就是 "ISO-8859-1" 。

解决乱码的问题非常简单,只需要在 web.xml 中添加一个过滤器:

    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

这个过滤器是 SpringWebMvc 自带的,我们只需要配好注册,并声明字符编码集为 UTF-8 ,就可以了。

如果小伙伴使用 Servlet 3.0 规范搭建的工程,则需要再写一个原生的 WebApplicationInitializer 的实现类,在其中注册 CharacterEncodingFilter :

public class FilterWebApplicationInitializer implements WebApplicationInitializer {
    
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        FilterRegistration.Dynamic filter = servletContext
                .addFilter("characterEncodingFilter", CharacterEncodingFilter.class);
        filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
        filter.setInitParameter("encoding", "UTF-8");
    }
}

这样编写完成之后,效果是一样的。

3.2 GET请求乱码

可能有些特殊情况下,GET 请求中文也会乱码,这种情况通常是 Web 容器,也就是 Tomcat 有问题。这个时候需要小伙伴检查你的 Tomcat 中 server.xml 配置的下面部分是否正确:

    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" 
               useBodyEncodingForURI="true"/>

最后一行的 useBodyEncodingForURI="true" 如果没有,需要加上。

如果加上还不行的话,有可能是你发 ajax 请求的时候出现的乱码,则上面的 useBodyEncodingForURI="true" 改为 URIEncoding="UTF-8" ,即可解决问题。

4. @RequestMapping注解的使用【掌握】

接下来,我们来重点学习一下 @RequestMapping 这个注解,它的作用我们都已经体会到了,不过这个注解还大有讲究呢,咱下面来学习它的使用。

4.1 请求路径的拼接

先解决刚才在上面提到的那个小问题,对于 DepartmentController 来讲,它的所有请求都应该以 /department 开头。为此,@RequestMapping 可以直接标注在 Controller 类上:

@Controller
@RequestMapping("/department")
public class DepartmentController {

这样里面定义的所有方法,就都不用加 /department 前缀了:

    @RequestMapping("/list")
    public String list(HttpServletRequest request, String name) {
        request.setAttribute("deptList", departmentService.findDepartmentsByName(name));
        return "dept/deptList";
    }

4.2 请求方式的限定

早在 JavaWeb 阶段,我们已经学过 HTTP 的 8 种请求方式是吧,它们分别是 GET POST PUT DELETE HEAD CONNECT OPTIONS TRACE 。当然,我们目前最熟悉的是 GET 和 POST 吧。通过 @RequestMapping ,是可以给这个请求方式做限定的。它的使用方式也很简单:

    @RequestMapping(value = "/list", method = RequestMethod.GET)
    public String list(HttpServletRequest request, String name) {
        request.setAttribute("deptList", departmentService.findDepartmentsByName(name));
        return "dept/deptList";
    }

如此标注之后,用 POST 请求就无法再访问了。到底是不是真的无法访问呢?我们可以借助 postman 来检验一下。

在 postman 中输入 http://localhost:8080/spring-webmvc/department/list ,并选择 POST 方式请求,服务器会给我们响应 405 错误:

Spring WebMvc基础-RESTful与mvc中的常用注解

Spring WebMvc基础-RESTful与mvc中的常用注解

我们都知道当一个路径的请求方式只允许为 GET 时,其余的所有方式请求过来都是会报 405 错误,所以 @RequestMapping 的请求方式限制时已经起了作用的。

4.3 注解的扩展

在 SpringFramework 4.2 之后,@RequestMapping 注解多了几个扩展的注解:

@RequestMapping(value = "/list", method = RequestMethod.GET)
@GetMapping("/list")
@PostMapping("/list")
@PutMapping("/list")
@DeleteMapping("/list")

这样看是不是就方便多了呀,不用每次都写那么一大串了。

其实它的底层封装简单的很,咱看一眼源码就知道了:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {
    
	@AliasFor(annotation = RequestMapping.class)
	String name() default "";

	@AliasFor(annotation = RequestMapping.class)
	String[] value() default {};

嚯,除了在注解上多了一个已经限定死的 method = RequestMethod.GET 之外,其余的都是 @AliasFor 啊,那这肯定能用。。。

当然,也正是这样一些小小的优化,给我们开发者带来的便捷是很大的。

5. RESTful编码风格【熟悉】

既然上面都提到了这些 @RequestMapping 注解的扩展注解,那下面我们就可以来讲讲 RESTful 了。

5.1 RESTful概述

在好几年前,Java 开发圈子里的一位大佬阮一峰发表过一篇文章 《理解RESTful架构》 ,在这篇文章中他解释了 RESTful 的一些相关的概念,小册在这里也引用这篇文章的一些内容来讲解。

RESTful ,全名叫 Representational State Transfer ,表现层状态转化 。这个名听起来很抽象,没关系,咱用比较容易理解的话来解释。

HTTP 协议中不是有 8 种请求方式嘛,里面包含 GET POST PUT DELETE ,RESTful 的编码风格将这四种方式赋予真正的意义:

  • GET :获取资源 / 数据
  • POST :新建资源 / 数据
  • PUT :更新资源 / 数据
  • DELETE :删除资源 / 数据

注意,RESTful 只是一种编码的风格,不是标准、不是协议、不是规范,仅仅是风格而已。只是说,如果用这种编码风格的话,设计的 API 请求路径看上去更简洁、有层次性。

OK RESTful 的概述就说这么一点吧,不用了解太多,主要掌握怎么编码即可。其余的内容,小伙伴们现在可以不用知道太多,如果想深入探索的话,就去好好读一读阮一峰大佬的那篇文章吧。

5.2 uri的RESTful

对于目前来讲,我们说的 RESTful 风格的编码,一般都是在接口 API 层面的 RESTful ,也就是基于 uri 的 RESTful 。这种风格的编码,小册给各位列一个对比:

请求路径定义 普通接口请求路径 RESTful请求路径
根据id查部门 【GET】/department/findById?id=1 【GET】/department/1
新增部门 【POST】/department/save 【POST】/department
修改部门 【POST】/department/update 【PUT】/department/1
删除部门 【POST】/department/deleteById?id=1 【DELETE】/department/1

能看出来这样路径的对比吧,我们平时写的这些普通的接口请求路径,都是通过路径名称来区分动作的;而 RESTful 的请求路径是通过 HTTP 请求方式来区分的。另外,传参的方式也不一样,我们写的普通接口,传参 id 是放在 url 后面拼接参数,而 RESTful 请求的传参是直接写在 uri 上的,所以这样看上去才比较简洁。

要真说对比起来,没有谁好谁坏,用哪种形式,全靠个人喜好,或者团队风格统一。

5.3 基于RESTful风格的编码编写

下面我们来单独写一个 RestfulDepartmentController ,在这里面我们练习一下如何编写基于 RESTful 风格的接口。

多的咱就不说了,直接上代码吧:

@GetMapping("/{id}")
public Department findById(@PathVariable("id") String id) {
    return departmentService.findById(id);
}

@PostMapping("/")
public void save(Department department) {
    departmentService.save(department);
}

@PutMapping("/{id}")
public void update(Department department, @PathVariable("id") String id) {
    // update ......
}

@DeleteMapping("/{id}")
public void delete(@PathVariable("id") String id) {
    departmentService.deleteById(id);
}

可以发现,这里面的接口声明,就是仿照着上面表格的内容写的,看上去还都算熟悉。唯一一个陌生的注解是这个 @PathVariable ,它是支持 RESTful 风格编码的重要注解。

5.4 @PathVariable注解的使用

要解析 @RequestMapping 及它的派生注解中的 uri 参数,就需要借助 @PathVariable 注解了。使用 @PathVariable 注解标注在 Controller 方法的参数上,可以指定解析 @RequestMapping 中 uri 的参数。就比如说上面的 findById 方法,它的 uri 就是 /department/{id} ,那实际发请求的时候,整个的 url 就应该是 http://localhost:8080/spring-webmvc/department/6ded6d3bdc8f4fc70bcc4347822a5ca3 。

当然,如果 @RequestMapping 中的请求参数有多个,那也好办,往 uri 后面继续追加就得了:/department/{id}/{name}/{tel} ,随便追加,只要 Controller 方法的参数列表里,都能找到一一对应的参数,并且配置 @PathVariable 注解即可。

这里面有一个小细节,上面的编码中小册给每个 @PathVariable 都设置了 value ,一般情况下如果属性名和 uri 中的参数占位符名一致时,是不需要显式声明 value 的,但作者本人一般都会写上,这也是受微服务框架的影响留下的习惯而已,小伙伴们不必觉得奇怪。

上一章咱把部门列表的加载,以及按部门名称的模糊查询都搞定了,最后的小结与练习中咱留下了两个功能,这里咱简单回顾一下。

1. 上章练习回顾

1.1 部门的删除

部门的删除主要想让小伙伴们巩固的是请求参数的收集。编写的 Controller 方法也很简单:

    @RequestMapping("/department/delete")
    public String delete(String id) {
        departmentService.deleteById(id);
        return "dept/deptList";
    }

1.2 部门的修改

部门信息的修改主要巩固的点是基于模型的请求参数绑定,编写的 Controller 代码也不复杂:

    @RequestMapping("/department/edit")
    public String edit(HttpServletRequest request, String id) {
        request.setAttribute("dept", departmentService.findById(id));
        return "dept/deptInfo";
    }
    
    @RequestMapping("/department/save")
    public void save(Department department) {
        System.out.println(department);
    }

1.3 练习的小问题

可能有不少小伙伴在练习时发现了一些问题:当删除 / 修改完成后,直接跳转回部门列表页,因为没有查数据库,将数据放到 request ,会导致跳转过来的表格是没有数据的:

Spring WebMvc基础-RESTful与mvc中的常用注解

而且还有一点,浏览器中地址栏的路径也不对劲,按道理来讲这里应该有一个重定向的动作才对!修改完成后重定向到 /department/list ,而不是直接跳转到 deptList.jsp !

还有,如果部门信息编辑的时候,如果输入中文的话,Controller 拿到的数据是乱码的,这很明显也有问题:

Department{id='84fef077c8dd97be89b2dff7de601073', name='??????'}

最后还有一个小细节,现在写的几个 Controller 的方法,它们都有一个共同的前缀,但每次都要写,很费劲:

@RequestMapping("/department/list")
@RequestMapping("/department/delete")
@RequestMapping("/department/edit")
@RequestMapping("/department/save")

好,下面我们针对这里面的问题,先讲解它们的解决办法,再继续新的内容。

2. Controller方法的返回值【掌握】

我们先捋一下,现在我们学过的 Controller 方法的返回值有哪些了?

    @RequestMapping("/department/list")
    public String list(HttpServletRequest request, String name) {
        request.setAttribute("deptList", departmentService.findDepartmentsByName(name));
        return "dept/deptList";
    }

    @RequestMapping("/department/list2")
    public ModelAndView list2(ModelAndView mav) {
        mav.addObject("deptList", departmentService.findDepartments(null));
        mav.setViewName("dept/deptList");
        return mav;
    }

好像就这两种吧:一个是直接返回视图(jsp)页面的路径,一个是返回 ModelAndView ,它里面也有视图的路径。

除此之外,还有几种返回的内容,一一来看。

2.1 void

其实 Controller 方法的返回值是可以为 void 的,也就是不返回。其实这个 void 一点也不意外,因为 HttpServlet 定义的 doGet 也好,doPost 也好,这些方法的返回值不也是 void 嘛,所以小伙伴不要觉得这个 void 很奇怪哈。

不过一般情况下我们不会常用它,在 SpringWebMvc 的早期,没有注解驱动的时候,可以用返回值 void 来实现 json 数据的响应。比方说这样:

    @RequestMapping("/department/get")
    public void get(HttpServletResponse response, String id) throws IOException {
        Department department = departmentService.findById(id);
        response.setCharacterEncoding("utf-8");
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().println("department序列化后的json");
    }

不过在 SpringWebMvc 发展到 3.0 之后,这种写法便慢慢被淘汰了,新的写法是配合一个 @ResponseBody 注解来的,咱下一章会讲到,这里先简单提一下。

2.2 json

刚才说了,配合 @ResponseBody 注解可以实现 json 数据的输出,所以在 Controller 方法的返回值其实可以是任意类型,只需要在方法上标注一个 @ResponseBody 注解即可。这个注解咱下一章就会讲到,小伙伴们稍安勿躁。

2.3 请求转发与重定向

上面咱提到的一个重定向的问题,其实在返回值为 String 的时候,是可以实现转发和重定向的,它们的写法都很简单。

  • 请求转发:forward:/department/list ,等同于 request.getRequestDispatcher("/department/list").forward(request, response);
  • 重定向:redirect:/department/list ,等同于 response.sendRedirect("/department/list");

这里又要夸一下 IDEA 了,对于请求转发和重定向的路径,IDEA 都能给出提示,真的是非常智能了:

Spring WebMvc基础-RESTful与mvc中的常用注解

3. 请求中文乱码问题【掌握】

紧接着,我们来解决表单提交的中文乱码问题。

3.1 POST请求乱码

Department{id='84fef077c8dd97be89b2dff7de601073', name='??????'}

这种情况的出现,原因是浏览器提交表单的时候,字符编码集与后端 Tomcat 不一致。通常情况下,浏览器并不会发送带 content-type 请求头的字符编码标识符,而是会把字符编码的决定留在读取 HTTP 请求的时候。如果此时浏览器没有指明字符编码集, Web 容器(Tomcat)用来创建请求读和解析 POST 表单数据的的时候,默认编码就是 "ISO-8859-1" 。

解决乱码的问题非常简单,只需要在 web.xml 中添加一个过滤器:

    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

这个过滤器是 SpringWebMvc 自带的,我们只需要配好注册,并声明字符编码集为 UTF-8 ,就可以了。

如果小伙伴使用 Servlet 3.0 规范搭建的工程,则需要再写一个原生的 WebApplicationInitializer 的实现类,在其中注册 CharacterEncodingFilter :

public class FilterWebApplicationInitializer implements WebApplicationInitializer {
    
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        FilterRegistration.Dynamic filter = servletContext
                .addFilter("characterEncodingFilter", CharacterEncodingFilter.class);
        filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
        filter.setInitParameter("encoding", "UTF-8");
    }
}

这样编写完成之后,效果是一样的。

3.2 GET请求乱码

可能有些特殊情况下,GET 请求中文也会乱码,这种情况通常是 Web 容器,也就是 Tomcat 有问题。这个时候需要小伙伴检查你的 Tomcat 中 server.xml 配置的下面部分是否正确:

    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" 
               useBodyEncodingForURI="true"/>

最后一行的 useBodyEncodingForURI="true" 如果没有,需要加上。

如果加上还不行的话,有可能是你发 ajax 请求的时候出现的乱码,则上面的 useBodyEncodingForURI="true" 改为 URIEncoding="UTF-8" ,即可解决问题。

4. @RequestMapping注解的使用【掌握】

接下来,我们来重点学习一下 @RequestMapping 这个注解,它的作用我们都已经体会到了,不过这个注解还大有讲究呢,咱下面来学习它的使用。

4.1 请求路径的拼接

先解决刚才在上面提到的那个小问题,对于 DepartmentController 来讲,它的所有请求都应该以 /department 开头。为此,@RequestMapping 可以直接标注在 Controller 类上:

@Controller
@RequestMapping("/department")
public class DepartmentController {

这样里面定义的所有方法,就都不用加 /department 前缀了:

    @RequestMapping("/list")
    public String list(HttpServletRequest request, String name) {
        request.setAttribute("deptList", departmentService.findDepartmentsByName(name));
        return "dept/deptList";
    }

4.2 请求方式的限定

早在 JavaWeb 阶段,我们已经学过 HTTP 的 8 种请求方式是吧,它们分别是 GET POST PUT DELETE HEAD CONNECT OPTIONS TRACE 。当然,我们目前最熟悉的是 GET 和 POST 吧。通过 @RequestMapping ,是可以给这个请求方式做限定的。它的使用方式也很简单:

    @RequestMapping(value = "/list", method = RequestMethod.GET)
    public String list(HttpServletRequest request, String name) {
        request.setAttribute("deptList", departmentService.findDepartmentsByName(name));
        return "dept/deptList";
    }

如此标注之后,用 POST 请求就无法再访问了。到底是不是真的无法访问呢?我们可以借助 postman 来检验一下。

在 postman 中输入 http://localhost:8080/spring-webmvc/department/list ,并选择 POST 方式请求,服务器会给我们响应 405 错误:

Spring WebMvc基础-RESTful与mvc中的常用注解

Spring WebMvc基础-RESTful与mvc中的常用注解

我们都知道当一个路径的请求方式只允许为 GET 时,其余的所有方式请求过来都是会报 405 错误,所以 @RequestMapping 的请求方式限制时已经起了作用的。

4.3 注解的扩展

在 SpringFramework 4.2 之后,@RequestMapping 注解多了几个扩展的注解:

@RequestMapping(value = "/list", method = RequestMethod.GET)
@GetMapping("/list")
@PostMapping("/list")
@PutMapping("/list")
@DeleteMapping("/list")

这样看是不是就方便多了呀,不用每次都写那么一大串了。

其实它的底层封装简单的很,咱看一眼源码就知道了:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {
    
	@AliasFor(annotation = RequestMapping.class)
	String name() default "";

	@AliasFor(annotation = RequestMapping.class)
	String[] value() default {};

嚯,除了在注解上多了一个已经限定死的 method = RequestMethod.GET 之外,其余的都是 @AliasFor 啊,那这肯定能用。。。

当然,也正是这样一些小小的优化,给我们开发者带来的便捷是很大的。

5. RESTful编码风格【熟悉】

既然上面都提到了这些 @RequestMapping 注解的扩展注解,那下面我们就可以来讲讲 RESTful 了。

5.1 RESTful概述

在好几年前,Java 开发圈子里的一位大佬阮一峰发表过一篇文章 《理解RESTful架构》 ,在这篇文章中他解释了 RESTful 的一些相关的概念,小册在这里也引用这篇文章的一些内容来讲解。

RESTful ,全名叫 Representational State Transfer ,表现层状态转化 。这个名听起来很抽象,没关系,咱用比较容易理解的话来解释。

HTTP 协议中不是有 8 种请求方式嘛,里面包含 GET POST PUT DELETE ,RESTful 的编码风格将这四种方式赋予真正的意义:

  • GET :获取资源 / 数据
  • POST :新建资源 / 数据
  • PUT :更新资源 / 数据
  • DELETE :删除资源 / 数据

注意,RESTful 只是一种编码的风格,不是标准、不是协议、不是规范,仅仅是风格而已。只是说,如果用这种编码风格的话,设计的 API 请求路径看上去更简洁、有层次性。

OK RESTful 的概述就说这么一点吧,不用了解太多,主要掌握怎么编码即可。其余的内容,小伙伴们现在可以不用知道太多,如果想深入探索的话,就去好好读一读阮一峰大佬的那篇文章吧。

5.2 uri的RESTful

对于目前来讲,我们说的 RESTful 风格的编码,一般都是在接口 API 层面的 RESTful ,也就是基于 uri 的 RESTful 。这种风格的编码,小册给各位列一个对比:

请求路径定义 普通接口请求路径 RESTful请求路径
根据id查部门 【GET】/department/findById?id=1 【GET】/department/1
新增部门 【POST】/department/save 【POST】/department
修改部门 【POST】/department/update 【PUT】/department/1
删除部门 【POST】/department/deleteById?id=1 【DELETE】/department/1

能看出来这样路径的对比吧,我们平时写的这些普通的接口请求路径,都是通过路径名称来区分动作的;而 RESTful 的请求路径是通过 HTTP 请求方式来区分的。另外,传参的方式也不一样,我们写的普通接口,传参 id 是放在 url 后面拼接参数,而 RESTful 请求的传参是直接写在 uri 上的,所以这样看上去才比较简洁。

要真说对比起来,没有谁好谁坏,用哪种形式,全靠个人喜好,或者团队风格统一。

5.3 基于RESTful风格的编码编写

下面我们来单独写一个 RestfulDepartmentController ,在这里面我们练习一下如何编写基于 RESTful 风格的接口。

多的咱就不说了,直接上代码吧:

@GetMapping("/{id}")
public Department findById(@PathVariable("id") String id) {
    return departmentService.findById(id);
}

@PostMapping("/")
public void save(Department department) {
    departmentService.save(department);
}

@PutMapping("/{id}")
public void update(Department department, @PathVariable("id") String id) {
    // update ......
}

@DeleteMapping("/{id}")
public void delete(@PathVariable("id") String id) {
    departmentService.deleteById(id);
}

可以发现,这里面的接口声明,就是仿照着上面表格的内容写的,看上去还都算熟悉。唯一一个陌生的注解是这个 @PathVariable ,它是支持 RESTful 风格编码的重要注解。

5.4 @PathVariable注解的使用

要解析 @RequestMapping 及它的派生注解中的 uri 参数,就需要借助 @PathVariable 注解了。使用 @PathVariable 注解标注在 Controller 方法的参数上,可以指定解析 @RequestMapping 中 uri 的参数。就比如说上面的 findById 方法,它的 uri 就是 /department/{id} ,那实际发请求的时候,整个的 url 就应该是 http://localhost:8080/spring-webmvc/department/6ded6d3bdc8f4fc70bcc4347822a5ca3 。

当然,如果 @RequestMapping 中的请求参数有多个,那也好办,往 uri 后面继续追加就得了:/department/{id}/{name}/{tel} ,随便追加,只要 Controller 方法的参数列表里,都能找到一一对应的参数,并且配置 @PathVariable 注解即可。

这里面有一个小细节,上面的编码中小册给每个 @PathVariable 都设置了 value ,一般情况下如果属性名和 uri 中的参数占位符名一致时,是不需要显式声明 value 的,但作者本人一般都会写上,这也是受微服务框架的影响留下的习惯而已,小伙伴们不必觉得奇怪。

上一章咱把部门列表的加载,以及按部门名称的模糊查询都搞定了,最后的小结与练习中咱留下了两个功能,这里咱简单回顾一下。

1. 上章练习回顾

1.1 部门的删除

部门的删除主要想让小伙伴们巩固的是请求参数的收集。编写的 Controller 方法也很简单:

    @RequestMapping("/department/delete")
    public String delete(String id) {
        departmentService.deleteById(id);
        return "dept/deptList";
    }

1.2 部门的修改

部门信息的修改主要巩固的点是基于模型的请求参数绑定,编写的 Controller 代码也不复杂:

    @RequestMapping("/department/edit")
    public String edit(HttpServletRequest request, String id) {
        request.setAttribute("dept", departmentService.findById(id));
        return "dept/deptInfo";
    }
    
    @RequestMapping("/department/save")
    public void save(Department department) {
        System.out.println(department);
    }

1.3 练习的小问题

可能有不少小伙伴在练习时发现了一些问题:当删除 / 修改完成后,直接跳转回部门列表页,因为没有查数据库,将数据放到 request ,会导致跳转过来的表格是没有数据的:

Spring WebMvc基础-RESTful与mvc中的常用注解

而且还有一点,浏览器中地址栏的路径也不对劲,按道理来讲这里应该有一个重定向的动作才对!修改完成后重定向到 /department/list ,而不是直接跳转到 deptList.jsp !

还有,如果部门信息编辑的时候,如果输入中文的话,Controller 拿到的数据是乱码的,这很明显也有问题:

Department{id='84fef077c8dd97be89b2dff7de601073', name='??????'}

最后还有一个小细节,现在写的几个 Controller 的方法,它们都有一个共同的前缀,但每次都要写,很费劲:

@RequestMapping("/department/list")
@RequestMapping("/department/delete")
@RequestMapping("/department/edit")
@RequestMapping("/department/save")

好,下面我们针对这里面的问题,先讲解它们的解决办法,再继续新的内容。

2. Controller方法的返回值【掌握】

我们先捋一下,现在我们学过的 Controller 方法的返回值有哪些了?

    @RequestMapping("/department/list")
    public String list(HttpServletRequest request, String name) {
        request.setAttribute("deptList", departmentService.findDepartmentsByName(name));
        return "dept/deptList";
    }

    @RequestMapping("/department/list2")
    public ModelAndView list2(ModelAndView mav) {
        mav.addObject("deptList", departmentService.findDepartments(null));
        mav.setViewName("dept/deptList");
        return mav;
    }

好像就这两种吧:一个是直接返回视图(jsp)页面的路径,一个是返回 ModelAndView ,它里面也有视图的路径。

除此之外,还有几种返回的内容,一一来看。

2.1 void

其实 Controller 方法的返回值是可以为 void 的,也就是不返回。其实这个 void 一点也不意外,因为 HttpServlet 定义的 doGet 也好,doPost 也好,这些方法的返回值不也是 void 嘛,所以小伙伴不要觉得这个 void 很奇怪哈。

不过一般情况下我们不会常用它,在 SpringWebMvc 的早期,没有注解驱动的时候,可以用返回值 void 来实现 json 数据的响应。比方说这样:

    @RequestMapping("/department/get")
    public void get(HttpServletResponse response, String id) throws IOException {
        Department department = departmentService.findById(id);
        response.setCharacterEncoding("utf-8");
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().println("department序列化后的json");
    }

不过在 SpringWebMvc 发展到 3.0 之后,这种写法便慢慢被淘汰了,新的写法是配合一个 @ResponseBody 注解来的,咱下一章会讲到,这里先简单提一下。

2.2 json

刚才说了,配合 @ResponseBody 注解可以实现 json 数据的输出,所以在 Controller 方法的返回值其实可以是任意类型,只需要在方法上标注一个 @ResponseBody 注解即可。这个注解咱下一章就会讲到,小伙伴们稍安勿躁。

2.3 请求转发与重定向

上面咱提到的一个重定向的问题,其实在返回值为 String 的时候,是可以实现转发和重定向的,它们的写法都很简单。

  • 请求转发:forward:/department/list ,等同于 request.getRequestDispatcher("/department/list").forward(request, response);
  • 重定向:redirect:/department/list ,等同于 response.sendRedirect("/department/list");

这里又要夸一下 IDEA 了,对于请求转发和重定向的路径,IDEA 都能给出提示,真的是非常智能了:

Spring WebMvc基础-RESTful与mvc中的常用注解

3. 请求中文乱码问题【掌握】

紧接着,我们来解决表单提交的中文乱码问题。

3.1 POST请求乱码

Department{id='84fef077c8dd97be89b2dff7de601073', name='??????'}

这种情况的出现,原因是浏览器提交表单的时候,字符编码集与后端 Tomcat 不一致。通常情况下,浏览器并不会发送带 content-type 请求头的字符编码标识符,而是会把字符编码的决定留在读取 HTTP 请求的时候。如果此时浏览器没有指明字符编码集, Web 容器(Tomcat)用来创建请求读和解析 POST 表单数据的的时候,默认编码就是 "ISO-8859-1" 。

解决乱码的问题非常简单,只需要在 web.xml 中添加一个过滤器:

    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

这个过滤器是 SpringWebMvc 自带的,我们只需要配好注册,并声明字符编码集为 UTF-8 ,就可以了。

如果小伙伴使用 Servlet 3.0 规范搭建的工程,则需要再写一个原生的 WebApplicationInitializer 的实现类,在其中注册 CharacterEncodingFilter :

public class FilterWebApplicationInitializer implements WebApplicationInitializer {
    
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        FilterRegistration.Dynamic filter = servletContext
                .addFilter("characterEncodingFilter", CharacterEncodingFilter.class);
        filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
        filter.setInitParameter("encoding", "UTF-8");
    }
}

这样编写完成之后,效果是一样的。

3.2 GET请求乱码

可能有些特殊情况下,GET 请求中文也会乱码,这种情况通常是 Web 容器,也就是 Tomcat 有问题。这个时候需要小伙伴检查你的 Tomcat 中 server.xml 配置的下面部分是否正确:

    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" 
               useBodyEncodingForURI="true"/>

最后一行的 useBodyEncodingForURI="true" 如果没有,需要加上。

如果加上还不行的话,有可能是你发 ajax 请求的时候出现的乱码,则上面的 useBodyEncodingForURI="true" 改为 URIEncoding="UTF-8" ,即可解决问题。

4. @RequestMapping注解的使用【掌握】

接下来,我们来重点学习一下 @RequestMapping 这个注解,它的作用我们都已经体会到了,不过这个注解还大有讲究呢,咱下面来学习它的使用。

4.1 请求路径的拼接

先解决刚才在上面提到的那个小问题,对于 DepartmentController 来讲,它的所有请求都应该以 /department 开头。为此,@RequestMapping 可以直接标注在 Controller 类上:

@Controller
@RequestMapping("/department")
public class DepartmentController {

这样里面定义的所有方法,就都不用加 /department 前缀了:

    @RequestMapping("/list")
    public String list(HttpServletRequest request, String name) {
        request.setAttribute("deptList", departmentService.findDepartmentsByName(name));
        return "dept/deptList";
    }

4.2 请求方式的限定

早在 JavaWeb 阶段,我们已经学过 HTTP 的 8 种请求方式是吧,它们分别是 GET POST PUT DELETE HEAD CONNECT OPTIONS TRACE 。当然,我们目前最熟悉的是 GET 和 POST 吧。通过 @RequestMapping ,是可以给这个请求方式做限定的。它的使用方式也很简单:

    @RequestMapping(value = "/list", method = RequestMethod.GET)
    public String list(HttpServletRequest request, String name) {
        request.setAttribute("deptList", departmentService.findDepartmentsByName(name));
        return "dept/deptList";
    }

如此标注之后,用 POST 请求就无法再访问了。到底是不是真的无法访问呢?我们可以借助 postman 来检验一下。

在 postman 中输入 http://localhost:8080/spring-webmvc/department/list ,并选择 POST 方式请求,服务器会给我们响应 405 错误:

Spring WebMvc基础-RESTful与mvc中的常用注解

Spring WebMvc基础-RESTful与mvc中的常用注解

我们都知道当一个路径的请求方式只允许为 GET 时,其余的所有方式请求过来都是会报 405 错误,所以 @RequestMapping 的请求方式限制时已经起了作用的。

4.3 注解的扩展

在 SpringFramework 4.2 之后,@RequestMapping 注解多了几个扩展的注解:

@RequestMapping(value = "/list", method = RequestMethod.GET)
@GetMapping("/list")
@PostMapping("/list")
@PutMapping("/list")
@DeleteMapping("/list")

这样看是不是就方便多了呀,不用每次都写那么一大串了。

其实它的底层封装简单的很,咱看一眼源码就知道了:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {
    
	@AliasFor(annotation = RequestMapping.class)
	String name() default "";

	@AliasFor(annotation = RequestMapping.class)
	String[] value() default {};

嚯,除了在注解上多了一个已经限定死的 method = RequestMethod.GET 之外,其余的都是 @AliasFor 啊,那这肯定能用。。。

当然,也正是这样一些小小的优化,给我们开发者带来的便捷是很大的。

5. RESTful编码风格【熟悉】

既然上面都提到了这些 @RequestMapping 注解的扩展注解,那下面我们就可以来讲讲 RESTful 了。

5.1 RESTful概述

在好几年前,Java 开发圈子里的一位大佬阮一峰发表过一篇文章 《理解RESTful架构》 ,在这篇文章中他解释了 RESTful 的一些相关的概念,小册在这里也引用这篇文章的一些内容来讲解。

RESTful ,全名叫 Representational State Transfer ,表现层状态转化 。这个名听起来很抽象,没关系,咱用比较容易理解的话来解释。

HTTP 协议中不是有 8 种请求方式嘛,里面包含 GET POST PUT DELETE ,RESTful 的编码风格将这四种方式赋予真正的意义:

  • GET :获取资源 / 数据
  • POST :新建资源 / 数据
  • PUT :更新资源 / 数据
  • DELETE :删除资源 / 数据

注意,RESTful 只是一种编码的风格,不是标准、不是协议、不是规范,仅仅是风格而已。只是说,如果用这种编码风格的话,设计的 API 请求路径看上去更简洁、有层次性。

OK RESTful 的概述就说这么一点吧,不用了解太多,主要掌握怎么编码即可。其余的内容,小伙伴们现在可以不用知道太多,如果想深入探索的话,就去好好读一读阮一峰大佬的那篇文章吧。

5.2 uri的RESTful

对于目前来讲,我们说的 RESTful 风格的编码,一般都是在接口 API 层面的 RESTful ,也就是基于 uri 的 RESTful 。这种风格的编码,小册给各位列一个对比:

请求路径定义 普通接口请求路径 RESTful请求路径
根据id查部门 【GET】/department/findById?id=1 【GET】/department/1
新增部门 【POST】/department/save 【POST】/department
修改部门 【POST】/department/update 【PUT】/department/1
删除部门 【POST】/department/deleteById?id=1 【DELETE】/department/1

能看出来这样路径的对比吧,我们平时写的这些普通的接口请求路径,都是通过路径名称来区分动作的;而 RESTful 的请求路径是通过 HTTP 请求方式来区分的。另外,传参的方式也不一样,我们写的普通接口,传参 id 是放在 url 后面拼接参数,而 RESTful 请求的传参是直接写在 uri 上的,所以这样看上去才比较简洁。

要真说对比起来,没有谁好谁坏,用哪种形式,全靠个人喜好,或者团队风格统一。

5.3 基于RESTful风格的编码编写

下面我们来单独写一个 RestfulDepartmentController ,在这里面我们练习一下如何编写基于 RESTful 风格的接口。

多的咱就不说了,直接上代码吧:

@GetMapping("/{id}")
public Department findById(@PathVariable("id") String id) {
    return departmentService.findById(id);
}

@PostMapping("/")
public void save(Department department) {
    departmentService.save(department);
}

@PutMapping("/{id}")
public void update(Department department, @PathVariable("id") String id) {
    // update ......
}

@DeleteMapping("/{id}")
public void delete(@PathVariable("id") String id) {
    departmentService.deleteById(id);
}

可以发现,这里面的接口声明,就是仿照着上面表格的内容写的,看上去还都算熟悉。唯一一个陌生的注解是这个 @PathVariable ,它是支持 RESTful 风格编码的重要注解。

5.4 @PathVariable注解的使用

要解析 @RequestMapping 及它的派生注解中的 uri 参数,就需要借助 @PathVariable 注解了。使用 @PathVariable 注解标注在 Controller 方法的参数上,可以指定解析 @RequestMapping 中 uri 的参数。就比如说上面的 findById 方法,它的 uri 就是 /department/{id} ,那实际发请求的时候,整个的 url 就应该是 http://localhost:8080/spring-webmvc/department/6ded6d3bdc8f4fc70bcc4347822a5ca3 。

当然,如果 @RequestMapping 中的请求参数有多个,那也好办,往 uri 后面继续追加就得了:/department/{id}/{name}/{tel} ,随便追加,只要 Controller 方法的参数列表里,都能找到一一对应的参数,并且配置 @PathVariable 注解即可。

这里面有一个小细节,上面的编码中小册给每个 @PathVariable 都设置了 value ,一般情况下如果属性名和 uri 中的参数占位符名一致时,是不需要显式声明 value 的,但作者本人一般都会写上,这也是受微服务框架的影响留下的习惯而已,小伙伴们不必觉得奇怪。

上一章咱把部门列表的加载,以及按部门名称的模糊查询都搞定了,最后的小结与练习中咱留下了两个功能,这里咱简单回顾一下。

1. 上章练习回顾

1.1 部门的删除

部门的删除主要想让小伙伴们巩固的是请求参数的收集。编写的 Controller 方法也很简单:

    @RequestMapping("/department/delete")
    public String delete(String id) {
        departmentService.deleteById(id);
        return "dept/deptList";
    }

1.2 部门的修改

部门信息的修改主要巩固的点是基于模型的请求参数绑定,编写的 Controller 代码也不复杂:

    @RequestMapping("/department/edit")
    public String edit(HttpServletRequest request, String id) {
        request.setAttribute("dept", departmentService.findById(id));
        return "dept/deptInfo";
    }
    
    @RequestMapping("/department/save")
    public void save(Department department) {
        System.out.println(department);
    }

1.3 练习的小问题

可能有不少小伙伴在练习时发现了一些问题:当删除 / 修改完成后,直接跳转回部门列表页,因为没有查数据库,将数据放到 request ,会导致跳转过来的表格是没有数据的:

Spring WebMvc基础-RESTful与mvc中的常用注解

而且还有一点,浏览器中地址栏的路径也不对劲,按道理来讲这里应该有一个重定向的动作才对!修改完成后重定向到 /department/list ,而不是直接跳转到 deptList.jsp !

还有,如果部门信息编辑的时候,如果输入中文的话,Controller 拿到的数据是乱码的,这很明显也有问题:

Department{id='84fef077c8dd97be89b2dff7de601073', name='??????'}

最后还有一个小细节,现在写的几个 Controller 的方法,它们都有一个共同的前缀,但每次都要写,很费劲:

@RequestMapping("/department/list")
@RequestMapping("/department/delete")
@RequestMapping("/department/edit")
@RequestMapping("/department/save")

好,下面我们针对这里面的问题,先讲解它们的解决办法,再继续新的内容。

2. Controller方法的返回值【掌握】

我们先捋一下,现在我们学过的 Controller 方法的返回值有哪些了?

    @RequestMapping("/department/list")
    public String list(HttpServletRequest request, String name) {
        request.setAttribute("deptList", departmentService.findDepartmentsByName(name));
        return "dept/deptList";
    }

    @RequestMapping("/department/list2")
    public ModelAndView list2(ModelAndView mav) {
        mav.addObject("deptList", departmentService.findDepartments(null));
        mav.setViewName("dept/deptList");
        return mav;
    }

好像就这两种吧:一个是直接返回视图(jsp)页面的路径,一个是返回 ModelAndView ,它里面也有视图的路径。

除此之外,还有几种返回的内容,一一来看。

2.1 void

其实 Controller 方法的返回值是可以为 void 的,也就是不返回。其实这个 void 一点也不意外,因为 HttpServlet 定义的 doGet 也好,doPost 也好,这些方法的返回值不也是 void 嘛,所以小伙伴不要觉得这个 void 很奇怪哈。

不过一般情况下我们不会常用它,在 SpringWebMvc 的早期,没有注解驱动的时候,可以用返回值 void 来实现 json 数据的响应。比方说这样:

    @RequestMapping("/department/get")
    public void get(HttpServletResponse response, String id) throws IOException {
        Department department = departmentService.findById(id);
        response.setCharacterEncoding("utf-8");
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().println("department序列化后的json");
    }

不过在 SpringWebMvc 发展到 3.0 之后,这种写法便慢慢被淘汰了,新的写法是配合一个 @ResponseBody 注解来的,咱下一章会讲到,这里先简单提一下。

2.2 json

刚才说了,配合 @ResponseBody 注解可以实现 json 数据的输出,所以在 Controller 方法的返回值其实可以是任意类型,只需要在方法上标注一个 @ResponseBody 注解即可。这个注解咱下一章就会讲到,小伙伴们稍安勿躁。

2.3 请求转发与重定向

上面咱提到的一个重定向的问题,其实在返回值为 String 的时候,是可以实现转发和重定向的,它们的写法都很简单。

  • 请求转发:forward:/department/list ,等同于 request.getRequestDispatcher("/department/list").forward(request, response);
  • 重定向:redirect:/department/list ,等同于 response.sendRedirect("/department/list");

这里又要夸一下 IDEA 了,对于请求转发和重定向的路径,IDEA 都能给出提示,真的是非常智能了:

Spring WebMvc基础-RESTful与mvc中的常用注解

3. 请求中文乱码问题【掌握】

紧接着,我们来解决表单提交的中文乱码问题。

3.1 POST请求乱码

Department{id='84fef077c8dd97be89b2dff7de601073', name='??????'}

这种情况的出现,原因是浏览器提交表单的时候,字符编码集与后端 Tomcat 不一致。通常情况下,浏览器并不会发送带 content-type 请求头的字符编码标识符,而是会把字符编码的决定留在读取 HTTP 请求的时候。如果此时浏览器没有指明字符编码集, Web 容器(Tomcat)用来创建请求读和解析 POST 表单数据的的时候,默认编码就是 "ISO-8859-1" 。

解决乱码的问题非常简单,只需要在 web.xml 中添加一个过滤器:

    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

这个过滤器是 SpringWebMvc 自带的,我们只需要配好注册,并声明字符编码集为 UTF-8 ,就可以了。

如果小伙伴使用 Servlet 3.0 规范搭建的工程,则需要再写一个原生的 WebApplicationInitializer 的实现类,在其中注册 CharacterEncodingFilter :

public class FilterWebApplicationInitializer implements WebApplicationInitializer {
    
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        FilterRegistration.Dynamic filter = servletContext
                .addFilter("characterEncodingFilter", CharacterEncodingFilter.class);
        filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
        filter.setInitParameter("encoding", "UTF-8");
    }
}

这样编写完成之后,效果是一样的。

3.2 GET请求乱码

可能有些特殊情况下,GET 请求中文也会乱码,这种情况通常是 Web 容器,也就是 Tomcat 有问题。这个时候需要小伙伴检查你的 Tomcat 中 server.xml 配置的下面部分是否正确:

    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" 
               useBodyEncodingForURI="true"/>

最后一行的 useBodyEncodingForURI="true" 如果没有,需要加上。

如果加上还不行的话,有可能是你发 ajax 请求的时候出现的乱码,则上面的 useBodyEncodingForURI="true" 改为 URIEncoding="UTF-8" ,即可解决问题。

4. @RequestMapping注解的使用【掌握】

接下来,我们来重点学习一下 @RequestMapping 这个注解,它的作用我们都已经体会到了,不过这个注解还大有讲究呢,咱下面来学习它的使用。

4.1 请求路径的拼接

先解决刚才在上面提到的那个小问题,对于 DepartmentController 来讲,它的所有请求都应该以 /department 开头。为此,@RequestMapping 可以直接标注在 Controller 类上:

@Controller
@RequestMapping("/department")
public class DepartmentController {

这样里面定义的所有方法,就都不用加 /department 前缀了:

    @RequestMapping("/list")
    public String list(HttpServletRequest request, String name) {
        request.setAttribute("deptList", departmentService.findDepartmentsByName(name));
        return "dept/deptList";
    }

4.2 请求方式的限定

早在 JavaWeb 阶段,我们已经学过 HTTP 的 8 种请求方式是吧,它们分别是 GET POST PUT DELETE HEAD CONNECT OPTIONS TRACE 。当然,我们目前最熟悉的是 GET 和 POST 吧。通过 @RequestMapping ,是可以给这个请求方式做限定的。它的使用方式也很简单:

    @RequestMapping(value = "/list", method = RequestMethod.GET)
    public String list(HttpServletRequest request, String name) {
        request.setAttribute("deptList", departmentService.findDepartmentsByName(name));
        return "dept/deptList";
    }

如此标注之后,用 POST 请求就无法再访问了。到底是不是真的无法访问呢?我们可以借助 postman 来检验一下。

在 postman 中输入 http://localhost:8080/spring-webmvc/department/list ,并选择 POST 方式请求,服务器会给我们响应 405 错误:

Spring WebMvc基础-RESTful与mvc中的常用注解

Spring WebMvc基础-RESTful与mvc中的常用注解

我们都知道当一个路径的请求方式只允许为 GET 时,其余的所有方式请求过来都是会报 405 错误,所以 @RequestMapping 的请求方式限制时已经起了作用的。

4.3 注解的扩展

在 SpringFramework 4.2 之后,@RequestMapping 注解多了几个扩展的注解:

@RequestMapping(value = "/list", method = RequestMethod.GET)
@GetMapping("/list")
@PostMapping("/list")
@PutMapping("/list")
@DeleteMapping("/list")

这样看是不是就方便多了呀,不用每次都写那么一大串了。

其实它的底层封装简单的很,咱看一眼源码就知道了:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {
    
	@AliasFor(annotation = RequestMapping.class)
	String name() default "";

	@AliasFor(annotation = RequestMapping.class)
	String[] value() default {};

嚯,除了在注解上多了一个已经限定死的 method = RequestMethod.GET 之外,其余的都是 @AliasFor 啊,那这肯定能用。。。

当然,也正是这样一些小小的优化,给我们开发者带来的便捷是很大的。

5. RESTful编码风格【熟悉】

既然上面都提到了这些 @RequestMapping 注解的扩展注解,那下面我们就可以来讲讲 RESTful 了。

5.1 RESTful概述

在好几年前,Java 开发圈子里的一位大佬阮一峰发表过一篇文章 《理解RESTful架构》 ,在这篇文章中他解释了 RESTful 的一些相关的概念,小册在这里也引用这篇文章的一些内容来讲解。

RESTful ,全名叫 Representational State Transfer ,表现层状态转化 。这个名听起来很抽象,没关系,咱用比较容易理解的话来解释。

HTTP 协议中不是有 8 种请求方式嘛,里面包含 GET POST PUT DELETE ,RESTful 的编码风格将这四种方式赋予真正的意义:

  • GET :获取资源 / 数据
  • POST :新建资源 / 数据
  • PUT :更新资源 / 数据
  • DELETE :删除资源 / 数据

注意,RESTful 只是一种编码的风格,不是标准、不是协议、不是规范,仅仅是风格而已。只是说,如果用这种编码风格的话,设计的 API 请求路径看上去更简洁、有层次性。

OK RESTful 的概述就说这么一点吧,不用了解太多,主要掌握怎么编码即可。其余的内容,小伙伴们现在可以不用知道太多,如果想深入探索的话,就去好好读一读阮一峰大佬的那篇文章吧。

5.2 uri的RESTful

对于目前来讲,我们说的 RESTful 风格的编码,一般都是在接口 API 层面的 RESTful ,也就是基于 uri 的 RESTful 。这种风格的编码,小册给各位列一个对比:

请求路径定义 普通接口请求路径 RESTful请求路径
根据id查部门 【GET】/department/findById?id=1 【GET】/department/1
新增部门 【POST】/department/save 【POST】/department
修改部门 【POST】/department/update 【PUT】/department/1
删除部门 【POST】/department/deleteById?id=1 【DELETE】/department/1

能看出来这样路径的对比吧,我们平时写的这些普通的接口请求路径,都是通过路径名称来区分动作的;而 RESTful 的请求路径是通过 HTTP 请求方式来区分的。另外,传参的方式也不一样,我们写的普通接口,传参 id 是放在 url 后面拼接参数,而 RESTful 请求的传参是直接写在 uri 上的,所以这样看上去才比较简洁。

要真说对比起来,没有谁好谁坏,用哪种形式,全靠个人喜好,或者团队风格统一。

5.3 基于RESTful风格的编码编写

下面我们来单独写一个 RestfulDepartmentController ,在这里面我们练习一下如何编写基于 RESTful 风格的接口。

多的咱就不说了,直接上代码吧:

@GetMapping("/{id}")
public Department findById(@PathVariable("id") String id) {
    return departmentService.findById(id);
}

@PostMapping("/")
public void save(Department department) {
    departmentService.save(department);
}

@PutMapping("/{id}")
public void update(Department department, @PathVariable("id") String id) {
    // update ......
}

@DeleteMapping("/{id}")
public void delete(@PathVariable("id") String id) {
    departmentService.deleteById(id);
}

可以发现,这里面的接口声明,就是仿照着上面表格的内容写的,看上去还都算熟悉。唯一一个陌生的注解是这个 @PathVariable ,它是支持 RESTful 风格编码的重要注解。

5.4 @PathVariable注解的使用

要解析 @RequestMapping 及它的派生注解中的 uri 参数,就需要借助 @PathVariable 注解了。使用 @PathVariable 注解标注在 Controller 方法的参数上,可以指定解析 @RequestMapping 中 uri 的参数。就比如说上面的 findById 方法,它的 uri 就是 /department/{id} ,那实际发请求的时候,整个的 url 就应该是 http://localhost:8080/spring-webmvc/department/6ded6d3bdc8f4fc70bcc4347822a5ca3 。

当然,如果 @RequestMapping 中的请求参数有多个,那也好办,往 uri 后面继续追加就得了:/department/{id}/{name}/{tel} ,随便追加,只要 Controller 方法的参数列表里,都能找到一一对应的参数,并且配置 @PathVariable 注解即可。

这里面有一个小细节,上面的编码中小册给每个 @PathVariable 都设置了 value ,一般情况下如果属性名和 uri 中的参数占位符名一致时,是不需要显式声明 value 的,但作者本人一般都会写上,这也是受微服务框架的影响留下的习惯而已,小伙伴们不必觉得奇怪。

 

免责声明:
1.本站所有内容由本站原创、网络转载、消息撰写、网友投稿等几部分组成。
2.本站原创文字内容若未经特别声明,则遵循协议CC3.0共享协议,转载请务必注明原文链接。
3.本站部分来源于网络转载的文章信息是出于传递更多信息之目的,不意味着赞同其观点。
4.本站所有源码与软件均为原作者提供,仅供学习和研究使用。
5.如您对本网站的相关版权有任何异议,或者认为侵犯了您的合法权益,请及时通知我们处理。
火焰兔 » Spring WebMvc基础-RESTful与mvc中的常用注解