Springboot学习笔记(九)——构建REST风格的网站

在HTTP协议发展的过程中,提出了许多规则,但是有些规则繁琐,于是又提出了一种风格约定,便是REST风格。严格地说,REST不是一种标准,而是一种风格。在现今流行的微服务中,这样的风格甚至被推荐为各个微服务系统之间用于交互的方式。首先在REST风格中,每一个资源都只是对应一个网址,而一个代表资源的网址应该是一个名词,而不存在动词,这代表对一个资源的操作。在这样的风格下,对于简易参数则尽量通过网址进行传递。例如,获取id为1的用户的URL可能就设计成http://localhost:8080/user/1。其中user是名词,代表用户信息,1则是用户的编号,他的含义就是获取用户id为1的资源信息。

REST简述

  1. REST名词解释
    REST按其英文名称(Representational State Transfer)翻译为表现层状态转换,首先要有资源才能表现,所以第一个名词是“资源”。有了资源也要根据需要以合适的形式表现资源。这就是第二个名词“表现层”。最后是资源可以被新增、修改、删除等,也就是第三个名词“状态转换”。这就是REST风格的三个主要名词。下面对他们做进一步阐述。
  • 资源:它可以是系统资源用户、角色和菜单等,也可以是一些媒体类型,如文本、图片、歌曲,总之他就是一个具体存在的对象。可以用一个URI(Uniform Resource Indetifier,统一资源定位符)指向它,每个资源对应一个特定的URI。要获取这个资源,访问他的URI即可。而在REST中每一个资源都会对应一个独一无二的URI。在REST中,URI也可以称为端点(End Point)。
  • 表现层:有了资源还需要确定如何表现这个资源。例如,一个用户可以使用JSON、XML或者其他的形式表现出来,又如可能返回的是一幅图片。在现今的互联网开发中,JSON数据集已经是一种最常用的表现形式。
  • 状态转换:现实中资源并不是一成不变的,它是一个变化的过程,一个资源可以经历创建(create)、访问(visit)、修改(update)和删除(delete)的过程。对于HTTP协议,是一个没有状态的协议,这也就意味着对于资源的变化就只能在服务器端保存和变化。

总结REST风格架构的特点:

  • 服务器存在一系列的资源,每一个资源通过单独唯一的URI进行标识;
  • 客户端和服务器之间可以互相传递资源,而资源会以某种表现层得以展示;
  • 客户端通过HTTP协议所定义的动作对资源进行操作,以实现资源的状态转换。
  1. HTTP的动作
    REST风格的资源存在创建(create)、访问(visit)、修改(update)和删除(delete)的状态转换,这样就对于HTTP的行为的5种动作。
  • GET(VISIT):访问服务器资源(一个或多个资源)。
  • POST(CREATE):提交服务器资源信息,用来创建新的资源。
  • PUT(UPDATE):修改服务器已存在的资源,使用PUT时需要把资源的所有属性一并提交。
  • PATCH(UPDATE):修改服务器已经存在的资源,使用PATCH时只需要将部分资源属性提交。
  • DELETE(DELETE):从服务器将资源删除。
    以上是需要重点掌握的内容,其中POST动作对应创建资源,PUT和PATCH对应更新资源,GET请求对应访问资源,DELETE对应删除资源。对于HTTP协议,还有另外两种不常用的动作行为。
  • HEAD:获取资源的元数据(Content-type)。
  • OPTIONS:提供资源可供客户端修改的属性信息。

REST风格的URI设计

#获取用户信息,1是用户编号
GET /user/1
#查询多个用户信息
GET /users/{userName}/{note}
#创建用户
POST /user/{userName}/{sex}/{note}
#修改用户全部属性
PUT /user/{id}/{userName}/{sex}/{note}
#修改用户名称(部分属性)
PATCH /user/{id}/{userName}

注意在URI中并没有出现动词,而对于参数主要通过URI设计去获取。对于参数数量超过5个的可以考虑使用JSON的方式传递参数。

  1. REST风格的一些误区
    在设计URI时REST风格存在一些规范,例如,一般不应该在URI中存在动词:
GET /user/get/1

这里get是一个动词,在REST风格是不应该存在这样的动词,可以修改为:

GET /user/1

这样就代表获取id为1的用户信息。
另外一个误区是加入版本号,例如:

GET /v1/user/1

其中v1是一个版本号,而user代表用户信息,1则代表用户编号。这是一个错误的表达,因为在REST风格中资源的URI都是唯一的,如果存在版本号,可以设置HTTP 请求头,使用请求头的信息进行区分。例如,设置请求头的version参数为1.0:

Accpet:version = 1.0

在很多时候REST都不推荐使用类似于

PUT users?userName=user_name&note=note

这样传递参数。这是一个更新用户的URI,按REST风格的建议是采用

PUT users/{userName}/{note}

但是有时候会出现参数很多的情况,如果全部写入到URI中,可读性和使用就会带来很大的困难。这时就不应该考虑使用URI传递参数,而是考虑请求体获取参数。

使用Spring MVC开发REST风格端点

Spring对REST风格的支持是基于SpringMVC 设计基础的,因此对于开发REST风格网站的开发着,熟悉springMVC的开发是十分必要的。在spring4.3之前只能使用@RequestMapping设计URI,在spring4.3之后则有更多的注解引入使得REST风格的开发更为便捷。

  1. Spring MVC整合REST

如果使用@RequestMapping让URL映射到对应的控制器,只要把URI设计为符合REST风格规范,那么显然就已经满足REST风格了。不过为了更为便捷的支持REST风格的开发,spring4.3之后除了@RequestMapping外,还可以使用以下5个注解。

  • @GetMapping:对应HTTP的get请求,获取资源
  • @PostMapping:对应HTTP的post请求,创建资源
  • @PutMapping:对应HTTP的put请求,提价所有资源属性以修改资源
  • @PatchMapping:对应HTTP的patch请求,提交资源部分修改的属性
  • @DeleteMapping:对应HTTP的delete请求,删除服务器端的资源
    在REST风格的设计中,如果是简单的参数,往往会通过URL直接传递,在springMVC可以使用注解@PathVariable进行获取,这样就满足REST风格传递参数的要求。对于那些复杂的参数,例如,传递一个复杂的资源需要十几个甚至几十个字段,可以考虑使用请求体JSON的方式提交给服务器,这样就可以使用注解@RequestBody将json数据集转换为java对象。
    通过**@RequestMapping、@GetMapping等注解就能把URI定位到对应的控制器方法上**,通过注解**@PathVariable就能将URI地址的参数获取**,通过**@RequestBody将请求体为json的数据转化为复杂的java对象**,其他均可以依据springMVC的参数规则进行处理。这样就能够进入到对应的控制器,进入控制器之后,就可以根据获取的参数来处理对应的逻辑。最后得到后台的数据,准备渲染给请求。在现今的开发中,数据转化为json是最为常见的方式,这个时候可以考虑使用注解@ResponseBody,这样springMVC就会通过MappingJackson2HttpMessageConverter最终将数据转换为json数据集,而在springMVC对REST风格的设计中,甚至可以使用注解**@RestController让整个控制器都默认转换为json数据集**。
  1. 使用Spring开发REST风格的端点

用户PO对象

package com.springboot.chapter11.pojo;
/**imports**/
@Alias("user")
public class User {
    private Integer id;
    private String userName;
    private SexEnum sex = null;
    private String note;
    //get和set方法
}

用户VO对象(视图对象)

package com.springboot.chapter11.vo;
public class UserVo {
    private Integer id;
    private String userName;
    private int sexCode;
    private String sexName;
    private String note;
    //get和set方法

把枚举转变为了简单的字符串和代码,使用它就可以对前端表达清晰的含义。
性别转换器

package com.springboot.chapter11.enumeration;
public enum SexEnum {
    MALE(0,"男"),
    FEMALE(1,"女");
    private int code;
    private String name;
    private SexEnum(int code, String name) {
        this.code = code;
        this.name = name;
    }
    //get和set方法
    public static SexEnum getSexEnum(int code) {
        for (SexEnum sex : SexEnum.values()) {
            if(sex.getCode() == code) {
                return sex;
            }
        }
        return null;
    }
}
package com.springboot.chapter11.typeHandler;
/**imports**/
@MappedTypes(SexEnum.class)
@MappedJdbcTypes(JdbcType.INTEGER)
public class SexTypeHandler extends BaseTypeHandler<SexEnum> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int index, SexEnum sexEnum, JdbcType jdbcType)
            throws SQLException {
        // TODO Auto-generated method stub
        ps.setInt(index, sexEnum.getCode());
    }
    @Override
    public SexEnum getNullableResult(ResultSet rs, String columnName) throws SQLException {
        // TODO Auto-generated method stub
        int code = rs.getInt(columnName);
        return SexEnum.getSexEnum(code);
    }
    @Override
    public SexEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        // TODO Auto-generated method stub
        int code = rs.getInt(columnIndex);
        return SexEnum.getSexEnum(code);
    }
    @Override
    public SexEnum getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        // TODO Auto-generated method stub
        int code = cs.getInt(columnIndex);
        return SexEnum.getSexEnum(code);
    }
}

数据访问层

package com.springboot.chapter11.dao;
/**imports**/
@Mapper
public interface UserDao {
    public User getUser(Integer id);
    public int insertUser(User user);
    public List<User> findUsers(@Param("userName") String userName, @Param("note") String note, @Param("start") int start,  @Param("limit") int limit);
    public int updateUser(User user);
    public int updateUserName(@Param("id") Integer id, @Param("userName") String userName);
    public int deleteUser(Integer id);
}

userMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.springboot.chapter11.dao.UserDao">
    <resultMap type="user" id="userMapper">
        <result column="id" property="id"/>
        <result column="user_name" property="userName"/>
        <result column="sex" property="sex" typeHandler="com.springboot.chapter11.typeHandler.SexTypeHandler"/>
        <result column="note" property="note"/>
    </resultMap>
    <select id="getUser" parameterType="Integer" resultMap="userMapper">
        select id,user_name,sex,note from t_user where id = #{id}
    </select>
    <insert id="insertUser" parameterType="com.springboot.chapter11.pojo.User" useGeneratedKeys="true" keyProperty="id">
        insert into t_user (user_name,sex,note) values (#{userName},#{sex,typeHandler=com.springboot.chapter11.typeHandler.SexTypeHandler},#{note})
    </insert>
    <select id="findUsers" parameterType="com.springboot.chapter11.pojo.User" resultMap="userMapper">
        select id,user_name,sex,note from t_user
        <where>
            <if test="userName != null">and user_name like concat('%','${userName}','%')</if>
            <if test="note != null">and note like concat('%','${note}','%')</if>
        </where>
        limit #{start},#{limit}
    </select>
    <update id="updateUser" >
        update t_user
        <set>
            <if test="userName != null">user_name = #{userName},</if>
            <if test="sex != null">sex = #{sex,typeHandler=com.springboot.chapter11.typeHandler.SexTypeHandler},</if>
            <if test="note != null">note = #{note}</if>
        </set>
        where id = #{id}
    </update>
    <update id="updateUserName" parameterType="com.springboot.chapter11.pojo.User">
        update t_user
        <set>
             <if test="userName != null">user_name = #{userName}</if>
        </set>
        where id = #{id}
    </update>
    <delete id="deleteUser" parameterType="Integer">
        delete from t_user where id = #{id}
    </delete>
</mapper>

服务层

package com.springboot.chapter11.service.impl;
/**imports**/
@Service
@Transactional
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao = null;
    @Override
    public User getUser(Integer id) {
        // TODO Auto-generated method stub
        return this.userDao.getUser(id);
    }
    @Override
    public User insertUser(User user) {
        // TODO Auto-generated method stub
        return this.userDao.insertUser(user)>0? user : null;
    }
    @Override
    public List<User> findUsers(String userName, String note, int start, int limit) {
        // TODO Auto-generated method stub
        return this.userDao.findUsers(userName, note, start, limit);
    }
    @Override
    public int updateUser(User user) {
        // TODO Auto-generated method stub
        return this.userDao.updateUser(user);
    }
    @Override
    public int updateUserName(Integer id, String userName) {
        // TODO Auto-generated method stub
        return this.userDao.updateUserName(id, userName);
    }
    @Override
    public int deleteUser(Integer id) {
        // TODO Auto-generated method stub
        return this.userDao.deleteUser(id);
    }
}

控制层

package com.springboot.chapter11.controller;
/**imports**/
@Controller
public class UserController {
    @Autowired
    private UserService userService = null;
    
    //映射jsp视图
    //@GetMapping("/index")
    @RequestMapping("/index")
    public String index() {
        return "/restful";
    }
    //转换vo为po
    public User changeToPo(UserVo vo) {
        User user = new User();
        user.setId(vo.getId());
        user.setUserName(vo.getUserName());
        user.setNote(vo.getNote());
        user.setSex(SexEnum.getSexEnum(vo.getSexCode()));
        return user;
    }
    //转换po为vo
    public UserVo changeToVo(User user) {
        UserVo uv = new UserVo();
        uv.setId(user.getId());
        uv.setNote(user.getNote());
        uv.setUserName(user.getUserName());
        uv.setSexCode(user.getSex().getCode());
        uv.setSexName(user.getSex().getName());
        return uv;
    }
    //将po列表转换为vo列表
    public List<UserVo> changeToVoes(List<User> listUser){
        List<UserVo> list = new ArrayList<UserVo>();
        for (User user : listUser) {
            UserVo userVo = changeToVo(user);
            list.add(userVo);
        }
        return list;
    }
 // 结果VO
    public class ResultVo {
        private Boolean success = null;
        private String message = null;
        public ResultVo() {
        }
        public ResultVo(Boolean success, String message) {
            this.success = success;
            this.message = message;
        }
        public Boolean getSuccess() {
            return success;
        }
        public void setSuccess(Boolean success) {
            this.success = success;
        }
        public String getMessage() {
            return message;
        }
        public void setMessage(String message) {
            this.message = message;
        }
    }
}

这里的index方法用来跳转到一个jsp视图中去,这个jsp可用于编写js脚本来测试请求。
用于测试的jsp视图

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script type="text/javascript">
<!--测试js代码-->
</script>
</head>
<body>
    <h1>测试RESTful下的请求</h1>
</body>
</html>

使用HTTP post动作创建资源(用户)
要有资源,首先需要创建资源(用户)。这里会用到post动作,所以使用@PostMapping,如下代码:

 //使用HTTP post动作创建资源(用户)
    @PostMapping("/user")
    @ResponseBody
    public User insertUser(@RequestBody UserVo userVo) {
        User user = this.changeToPo(userVo);
        return userService.insertUser(user);
    }

测试POST请求

function post(){
	var params = {
			'userName':'user_name_new',
			'sexCode':'1',
			'note':'note_new'
	}
	$.post({
		url : "./user",
		contentType:"application/json",
		data:JSON.stringify(params),//将JSON转换为字符串传递
		success:function(result){
			if(result == null&&result.id==null){
				alert("插入失败!");
				retrun;
			}
			alert("插入成功!");
		}
	});
}
post();

这里使用了jQuery的post函数进行提交,这样它就以post方法请求REST端点。请求体声明为JSON数据集,并且将请求体转换为了JSON字符串进行提交后台。于是对于控制器的insertUser方法就可以接受这个请求,由于insertUser方法标注了@ResponseBody,因此最后会将其转化为JSON返回给前端的js请求。

获取用户的GET请求

 //获取用户的get请求
    @GetMapping(value = "/user/{id}")
    @ResponseBody
    public UserVo getUser(@PathVariable("id") Integer id) {
        User user = userService.getUser(id);
        return changeToVo(user); 
    }

采用注解@GetMapping声明HTTP的GET请求,并且把参数编号(id),以URI的形式传递,这符合了REST风格的要求。在getUser方法中使用了@PathVariable从URI中获取参数,而方法标注@ResponseBody表示将REST的表现层的形式设置为JSON数据集。

验证get请求

function get(){
	$.get("./user/1",function(user,status){
		if(user == null){
			alert("用户为空!");
		}else{
			alert("用户信息为:"+JSON.stringify(user));
		}
	});
}
get();

使用了jQuery的get请求,并且将1作为用户编号(id)传递给后台的方法,这样就可以请求后台的控制器方法。

查询符合要求的用户

 //查询符合要求的用户
    @GetMapping(value = "/users/{userName}/{note}/{start}/{limit}")
    @ResponseBody
    public List<UserVo> findusers(
            @PathVariable("userName")String userName,
            @PathVariable("note")String note,
            @PathVariable("start")Integer start,
            @PathVariable("limit")Integer limit){
        List<User> findUsers = userService.findUsers(userName, note, start, limit);
        return changeToVoes(findUsers); 
    }

测试

function findUsers(){
	$.get("./users/user/note/0/3",function(users,status){
		if(users==null){
			alert("用户为空!");
		}else{
			alert("用户信息为:"+JSON.stringify(users));
		}
	});
}
findUsers();

使用HTTP的put请求修改用户信息

 //使用HTTP的put请求修改用户信息
    @PutMapping("/user/{id}")
    @ResponseBody
    public User updateUser(@PathVariable("id")Integer id,@RequestBody UserVo vo) {
        User user = changeToPo(vo);
        user.setId(id);
        userService.updateUser(user);
        return user;
    }

使用@PutMapping标注他是一个HTTP的put请求,按REST风格的特点是要求传递所有的属性,这里的编号(id)是通过URI进行传递的,而请求体需要修改的数据则是通过JSON格式传递的,所以在获取参数时使用注解@PathVariable获取编号,而采用@RequestBody获取修改的数据。
测试修改用户的put请求

function updateUser(){
	var params = {
            'userName':'1111111',
            'sexCode':1,
            'note':'11111111'
    }
	$.ajax({
		url:"./user/17",
		type:'PUT',
		contentType:"application/json",
		data:JSON.stringify(params),
		success:function(user,status){
			if(user==null){
				alert("结果为空!");
			}else{
				alert(JSON.stringify(user));
			}
		}
	});
}
updateUser();

因为jQuery不存在put方法,所以使用ajax方法代替。

使用PATCH请求修改用户名称

//使用patch请求修改用户名称
    @PatchMapping("/user/{id}/{userName}")
    @ResponseBody
    public ResultVo updateUserName(@PathVariable("id")Integer id,@PathVariable("userName")String userName) {
        int updateUserName = userService.updateUserName(id, userName);
        ResultVo resultVo = new ResultVo(updateUserName>0, updateUserName>0?"更新成功!":"更新用户【"+id+"】失败!");
        return resultVo;
    }

使用@PatchMapping标注他为一个HTTP的patch请求,意味着将对资源的属性做部分修改。
测试PATCH请求

function updateUserName(){
	$.ajax({
		url:'./user/17/user_name_new',
		type:'PATCH',
		contypeType:'application/json',
		success:function(result,status){
			if(result==null){
				alert("结果为空");
			}else{
				alert(result.success?"更新成功!":"更新失败!");
				alert(result.message);
			}
		}
	});
}
updateUserName();

使用HTTP的DELETE请求

 //使用HTTP的delete请求
    @DeleteMapping("/user/{id}")
    @ResponseBody
    public ResultVo deleteUser(@PathVariable("id")Integer id) {
        int deleteUser = userService.deleteUser(id);
        ResultVo resultVo = new ResultVo(deleteUser>0, deleteUser>0?"删除成功!":"删除用户【"+id+"】失败!");
        return resultVo;
    }

使用@DeleteMapping标注他是一个HTTP的DELETE请求,而用户编号(id)则采用URI进行传递。
测试删除用户的HTTP的DELET请求

function deleteUser(){
	$.ajax({
		url:'./user/23',
		type:'DELETE',
		contentType:"application/json",
		success:function(result,status){
			if(result==null){
				alert("结果为空");
			}else{
				alert(result.message);
			}
		}
	});
}
deleteUser();

上面都是REST风格下的POST、PUT、PATCH、DELETE和GET请求,但是都是通过JS来完成的。在一些表单提交中,也许不需要在使用JS进行提交,这时需要采用别的方式进行提交。

 //控制器修改用户名称
    @PatchMapping("/user/name")
    @ResponseBody
    public ResultVo changeUserName(Integer id,String userName) {
        int updateUserName2 = userService.updateUserName(id, userName);
        ResultVo resultVo = new ResultVo(updateUserName2>0, updateUserName2>0?"更新成功!":"更新用户【"+id+"】失败!");
        return resultVo;
    }
    //映射jsp视图
    @GetMapping("/user/name")
    public String changeUserName2() {
        return "/change_user_name";
    }

修改用户名称表单

<form id="form" action="./name" method="post">
        <table>
            <tr>
                <td>用户编号</td>
                <td><input id="id" name="id" /></td>
            </tr>
            <tr>
                <td>用户名称</td>
                <td><input id="userName" name="userName" /></td>
            </tr>
            <tr>
                <td></td>
                <td align="right"><input id="submit" name="submit"
                    type="submit" /></td>
            </tr>
        </table>
        <input type="hidden" name="_method" id="_method" value="PATCH" />
    </form>

这个表单中,form定义的是post请求,form中还存在一个命名为_method的隐藏字段,并且定义其值为PATCH。

  1. 使用@RestController

现在使用JSON作为前后端交互已经十分普遍。如果每一个方法都加入@ResponseBody才能将数据模型转换为JSON,这显然是有些冗余。springmvc在支持REST风格中还存在一个注解@RestController,通过它可以将控制器返回的对象转换为JSON数据集。

package com.springboot.chapter11.controller;
/**imports**/
@RestController//方法默认使用json视图
//@Controller
public class UserController2 {
    @Autowired
    private UserService userServce = null;
    //映射jsp视图
    @GetMapping("/index2")
    public ModelAndView index() {
        ModelAndView mav = new ModelAndView("/restful");
        return mav;
    }
    //转换vo为po
    public User changeToPo(UserVo vo) {
        User user = new User();
        user.setId(vo.getId());
        user.setUserName(vo.getUserName());
        user.setNote(vo.getNote());
        user.setSex(SexEnum.getSexEnum(vo.getSexCode()));
        return user;
    }
    //转换po为vo
    public UserVo changeToVo(User user) {
        UserVo uv = new UserVo();
        uv.setId(user.getId());
        uv.setNote(user.getNote());
        uv.setUserName(user.getUserName());
        uv.setSexCode(user.getSex().getCode());
        uv.setSexName(user.getSex().getName());
        return uv;
    }
}

这里需要注意的是用@RestController标注之后,只能用ModelAndView跳转页面

  1. 渲染结果

在学习注解@RestController时,可以看到将数据转变为JSTL或者JSON展示给客户端,但是实际上还可能有更多的资源类型视图,如PDF、Excel等媒体类型(MediaType)。对于返回数据表现在流程中可能存在两种:一种是类似于注解@ResponseBody那样,在返回结果后,由处理器使用已经注册在SpringIoC容器中的HttpMessageConverter接口实现类——MappingJackson2HttpMessageConverter进行直接转换,这时就不需要在使用视图解析器对数据模型进行处理;另外一种是使用ModelAndView捆绑视图,然后让后面的视图解析器进行处理。
先看HttpMessageConverter接口实现类的方案。在springMVC中,IoC容器启动时注册了两个HttpMessageConverter接口的实现类,他们分别是StringHttpMessageConverter和MappingJackson2HttpMessageConverter。HttpMessageConverter接口定义了一个canWriter方法:

boolean canWriter(Class<?> clazz,MediaType mediaType)

他返回一个布尔值,而MediaType 则是可以传入的媒体类型,SpringMVC在执行控制器的方法后,会去遍历注册的HttpMessageConverter接口的实现类,并使用canWriter方法去判断是否拦截控制器的返回。
在@RequestMapping、GetMapping等注解中还存在consumes和produces两个属性。其中consumes代表的是限制该方法接收什么类型的请求体(body),produces代表的是限定返回的媒体类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回。

使用字符串作为REST风格的表示层

//使用字符串作为rest风格的表示层
    @GetMapping(value = "/user2/name/{id}",
            //接受任意类型的请求体
            consumes = MediaType.ALL_VALUE,
            //限定返回的媒体类型为文本
            produces = MediaType.TEXT_PLAIN_VALUE)
    public String getUserName(@PathVariable("id")Integer id) {
        User user = userServce.getUser(id);
        return user.getUserName();
    }
  1. 处理HTTP状态码、异常和响应头

当发生资源找不到或者异常发生时,需要考虑的是返回给客户端的HTTP状态码和错误消息的问题,为了简化这些开发,Spring提供了实体封装类ResponseEntity和注解@ResponseStatus、ResponseEntity可以有效封装错误消息和状态码,通过@ResponseStatus可以配置指定的响应码给客户端。
在大部分情况下,后台请求成功后会返回一个200的状态码,代表请求成功。但是有时候这些还不够具体,例如,新增用户,使用200的状态码固然没有错,但使用201状态码会更加具体一些。这是就可以使用ResponseEntity类或者@ResponseStatus来标识本次请求的状态码。处理可以在HTTP响应头中加入属性码之外,还可以给相应头加入属性来提供成功或失败的消息。

使用状态码

//使用状态码
    @PostMapping("/user2/entity")
    public ResponseEntity<UserVo> insertUserEntity(@RequestBody UserVo userVo){
        User user = changeToPo(userVo);
        userServce.insertUser(user);
        UserVo result = this.changeToVo(user);
        HttpHeaders headers = new HttpHeaders();
        String success = (result == null||result.getId() == null)?"false":"true";
        //设置响应头,比较常用的方式
        headers.add("success", success);
        //设置响应头,不常用
        headers.put("success", Arrays.asList(success));
        //返回创建成功的状态码
        return new ResponseEntity<UserVo>(result, headers, HttpStatus.CREATED);
    }
    @PostMapping("/user2/annotation")
    //指定状态码为201(资源创建成功)
    @ResponseStatus(HttpStatus.CREATED)
    public UserVo insertUserAnnotation(@RequestBody UserVo userVo) {
        User user = this.changeToPo(userVo);
        userServce.insertUser(user);
        UserVo userVo2 = this.changeToVo(user);
        return userVo2;
    }

测试使用响应码

function postStatus(){
	var params = {
			userName:'000',
			sexCode:'1',
			note:'000'
	}
//	var url = './user2/entity';
	var url = './user/exp/17';
	$.post({
		url:url,
		contentType:'application/json',
		data:JSON.stringify(params),
		success:function(result,status,jqXHR){
			//获取响应头
			var success = jqXHR.getResponseHeader("success");
			//获取状态码
			var status = jqXHR.status;
			alert("响应头参数是:"+success+"状态码是:"+status);
			if(result == null){
				alert("插入失败!");
				return;
			}
			alert("插入成功!");
		}
	});
}
postStatus();

定义查找失败异常

package com.springboot.chapter11.exception;

public class NotFoundException extends RuntimeException {
    private static final long serialVersionUID = 1L;
    //异常编码;
    private Integer code;
    //异常自定义信息
    private String message;
    public NotFoundException() {}
    public NotFoundException(Integer code, String message) {
        super();
        this.code = code;
        this.message = message;
    }
   //get和set方法
}

定义控制器通知来处理异常

package com.springboot.chapter11.exception;
/**imports**/
@ControllerAdvice(
        //指定拦截包的控制器
        basePackages = "com.springboot.chapter11.*",
        //限定标注为@Controller和@RestController的类才被拦截
    annotations = { Controller.class,RestController.class})
public class VoControllerAdvice {
    //异常处理,可以定义异常类型进行拦截处理
    @ExceptionHandler(value = NotFoundException.class)
    //以json表达方式响应
    @ResponseBody
    //定义为服务器错误状态码
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Map<String,Object> exception(HttpServletRequest request,NotFoundException ex){
        Map<String, Object> map = new HashMap<String, Object>();
        //获取异常信息
        map.put("code", ex.getCode());
        map.put("message", ex.getMessage());
        return map;
    }
}

其中@ControllerAdvice是用来定义控制器通知的,@ExceptionHandler则是指定异常发生时处理方法。

测试控制器通知异常处理

 //测试控制器通知异常处理
    @GetMapping(value = "/user/exp/{id}",
            //产生json数据集
                produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ResponseBody
    //响应成功
    @ResponseStatus(HttpStatus.OK)//标注响应码为200
    public UserVo getUserForExp(@PathVariable("id")Integer id) {
        User user = userService.getUser(id);
        //找不到用户,抛出异常,进入控制器通知
        if(user == null) {
            throw new NotFoundException(1, "找不到用户【"+id+"】信息");
        }
        return changeToVo(user);
    }

在这里插入图片描述

客户端请求RestTemplate

在当今微服务中,会将一个大系统拆分为多个微服务系统。按微服务应用的建议,每个微服务系统都会暴露REST风格的URI请求给别的微服务系统所调用。为了方便完成系统之间的相互调用,Spring还给予了模板类RestTemplate,通过它可以很方便的对REST请求进行系统之间的调用,完成系统之间的数据集成。

  1. 使用RestTemplate请求后端

RestTemplate的底层是通过类HttpURLConnection实现的。

使用RestTemplate进行HTTP get请求

//使用RestTemplate进行HTTP get请求
    public static UserVo getUser(Integer id) {
        //创建一个RestTemplate对象
        RestTemplate restTemplate = new RestTemplate();
        //消费服务,第一个参数为URL。第二个参数为返回类型,第三个参数为URI路径参数
        UserVo userVo = restTemplate.getForObject("http://localhost:8080/user/{id}", UserVo.class, id);
        System.out.println(userVo.getUserName());
        return userVo;
    }

运行方式是先启动springboot的启动类,在运行main方法。上述方法是在main方法中运行的。http://localhost:8080/user/{id},会调用控制器中的getUser方法,类似于远程调用。
在这里插入图片描述
RestTemplate使用多参数的HTTP get请求

 //RestTemplate使用多参数的HTTP get请求
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static List<UserVo> findUser(String userName,String note,int start,int limit){
      //创建一个RestTemplate对象
        RestTemplate restTemplate = new RestTemplate();
        //使用map封装多个参数,以提高可读性
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("userName", "user");
        map.put("note", "note");
        map.put("start", start);
        map.put("limit", limit);
        //map中的key和URI中的参数一一对应
        String url = "http://localhost:8080/users/{userName}/{note}/{start}/{limit}";
        //请求后端
        ResponseEntity<List> forEntity = restTemplate.getForEntity(url, List.class, map);
        List<UserVo> list = forEntity.getBody();
        System.out.println(list);
        return list;
    }

在这里插入图片描述
通过post请求传递json请求体(body)

//通过post请求传递json请求体(body)
    public static User insertUser(UserVo userVo) {
        //请求头
        HttpHeaders headers = new HttpHeaders();
        //设置请求内容为json类型
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
        //创建请求实体对象
        HttpEntity<UserVo> request = new HttpEntity<UserVo>(userVo, headers);
        RestTemplate restTemplate = new RestTemplate();
        //请求时传递请求实体对象,并返回回填id的用户
        User user = restTemplate.postForObject("http://localhost:8080/user", request, User.class);
        System.out.println(user.getId());
        return user;
    }

使用RestTemplate执行delete请求

 //使用RestTemplate执行delete请求
    public static void deleteUser(Integer id) {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.delete("http://localhost:8080/user/{id}", id);
    }
  1. 获取响应头、状态码

获取服务器响应头属性和http状态码

//获取服务器响应头属性和http状态码
    public static User insertUserEntity(UserVo vo) {
        //请求头
        HttpHeaders headers = new HttpHeaders();
        //请求类型
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
        //绑定请求体和头
        HttpEntity<UserVo> request = new HttpEntity<UserVo>(vo, headers);
        RestTemplate restTemplate = new RestTemplate();
        //请求服务器
        ResponseEntity<User> responseEntity = restTemplate.postForEntity("http://localhost:8080/user2/entity", request, User.class);
        User user = responseEntity.getBody();
        //获取请求头
        HttpHeaders httpHeaders = responseEntity.getHeaders();
        //获取响应属性
        List<String> success = httpHeaders.get("success");
        //获取http的响应码
        int statusCode = responseEntity.getStatusCodeValue();
        System.out.println(user.getId());
        System.out.println(success);
        System.out.println(statusCode);
        return user;
    }

main方法

package com.springboot.chapter11.main;

import com.springboot.chapter11.resttemplate.RestTemplateTest;
import com.springboot.chapter11.vo.UserVo;

public class RestTemplateMain {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
//        RestTemplateTest.getUser(1);
 //       RestTemplateTest.findUser("user", "note", 0, 3);
        UserVo userVo = new UserVo();
        userVo.setNote("note000");
        userVo.setUserName("user_name_000");
        userVo.setSexCode(1);
        userVo.setSexName("女");
//        RestTemplateTest.insertUser(userVo);
//        RestTemplateTest.deleteUser(27);
        RestTemplateTest.insertUserEntity(userVo);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值