在HTTP协议发展的过程中,提出了许多规则,但是有些规则繁琐,于是又提出了一种风格约定,便是REST风格。严格地说,REST不是一种标准,而是一种风格。在现今流行的微服务中,这样的风格甚至被推荐为各个微服务系统之间用于交互的方式。首先在REST风格中,每一个资源都只是对应一个网址,而一个代表资源的网址应该是一个名词,而不存在动词,这代表对一个资源的操作。在这样的风格下,对于简易参数则尽量通过网址进行传递。例如,获取id为1的用户的URL可能就设计成http://localhost:8080/user/1。其中user是名词,代表用户信息,1则是用户的编号,他的含义就是获取用户id为1的资源信息。
REST简述
- 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协议所定义的动作对资源进行操作,以实现资源的状态转换。
- 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的方式传递参数。
- 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¬e=note
这样传递参数。这是一个更新用户的URI,按REST风格的建议是采用
PUT users/{userName}/{note}
但是有时候会出现参数很多的情况,如果全部写入到URI中,可读性和使用就会带来很大的困难。这时就不应该考虑使用URI传递参数,而是考虑请求体获取参数。
使用Spring MVC开发REST风格端点
Spring对REST风格的支持是基于SpringMVC 设计基础的,因此对于开发REST风格网站的开发着,熟悉springMVC的开发是十分必要的。在spring4.3之前只能使用@RequestMapping设计URI,在spring4.3之后则有更多的注解引入使得REST风格的开发更为便捷。
- 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数据集**。
- 使用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。
- 使用@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跳转页面
- 渲染结果
在学习注解@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();
}
- 处理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请求进行系统之间的调用,完成系统之间的数据集成。
- 使用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);
}
- 获取响应头、状态码
获取服务器响应头属性和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);
}
}