SpringBoot中REST风格

67 篇文章 0 订阅
认SpringBoot中REST风格
学习目标
    1、关于REST
    2、Spring MVC开发REST风格
    3、客户端请求RestTemplate
一、关于REST
    REST(Representational State Transfer),是由Fielding在2000年的博士论文中提出来的。它可以翻译为表现层状态转换。
    它有如下三个主要名词:
        资源:它是一个具体存在的对象,可以是系统权限用户、角色菜单、文本、图片……,可以由一个URI来指向它,每一个资源对应一个特定的URI。要获取资源则访问它对应的URI即可。在REST中,URI也可以称为端点(End Point)。
        表现层:有了资源还需要确定如何表现这个资源。现在互联网开发中,JSON已存为一种最为常用的表现形式。
        状态转换:随着系统的运行资源会有一些变化,一个资源会经历创建(create)、访问(view)、修改(update)和删除(delete)的过程。
通过上同的这些REST风格架构可的特点主要有如下:
    服务器存在一系列的资源,每个资源都通过唯一的URI进行标识
    客户端与服务器之间可以相互传递资源,而资源会以某种表现层来展示
    客户端通过HTTP协议所定义的动作对资源进行操作,用以来实现资源的状态转换
资源的状态转换对应HTTP的动作
    GET(view):访问服务器资源(一个或多个)
    POST(create):提交服务器资源信息,创建新的资源
    PUT(update):修改服务器存在的资源,使用PUT时需要把资源的所有属性一并提交
    PATCH(update):修改服务器已存在资源,使用PATCH时需要把部分资源属性提交(不常用)
    DELETE(delete):从服务器将资源删除
除了上述5个,HTTP协议中还有两个不常用的动作
    HEAD:获取资源元数据(Content-type)
    OPTIONS:提供资源可供客户端修改的属性信息
常见的REST风格URI
    GET /user/{id}    获取用户信息
    GET /user/{userName}/{note}    获取多个用户信息
    POST /user/{userName}/{sex}/{note}    创建用户
    PUT /user/{id}/{userName}/{sex}/{note}    修改用户全部属性
    PATCH /user/{id}/{userName}    修改用户部分属性
在上面的URI中并没有出现动词,参数则主要通过URI设计去获取,对于参数量过多时可以考虑使用JSON作为参数来传递。
二、Spring MVC开发REST风格
Spring MVC整合REST
为了更加便捷地支持REST风格,Spring 4.3之后除了@RequetMapping外还可以使用如下5种注解
    @GetMapping:对应HTTP的GET请求,获取资源
    @PostMapping:对应HTTP的POST请求,创建资源
    @PutMapping:对应HTTP的PUT请求,提交所有资源属性以修改资源
    @PatchMapping:对应HTTP的PATCH请求,提交资源部分修改的属性
    @DeleteMapping:对应HTTP的DELETE请求,删除服务器端的资源
在REST风格的设计中,如果是简单的参数,一般会通过URL直接传递,Spring MVC中可以使用@PathVariable进行获取,这样就可以满足REST风格传递参数的要求,如果传递的参数复杂可以考虑使用请求体JSON的方式提交给服务器,使用@RequestBody把JSON数据集转换为Java对象。
@RequestMapping、@GetMapping等注解可以把uri定位到控制器方法上
@PathVariable可以把uri地址的参数获取到
@RequestBody可以把请求体为JSON的数据转换为复杂的Java对象
经过上面的注解,请求到达控制器后,可以根据获取的参数来处理对应的逻辑,最终得到后台数据准备渲染给请求。
新增一个Spring Boot的Maven项目,在pom.xml中导入相关的依赖,pom.xml的内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.xiaoxie</groupId>
    <artifactId>SpringBoot_REST</artifactId>
    <version>1.0-SNAPSHOT</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
    </parent>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!-- AOP依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!-- Web开发包 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 测试依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <!-- jstl,jsp依赖 -->
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
        </dependency>
        <!-- mysql及jdbc依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!--Druid依赖-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <!-- mybatis依赖 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.1</version>
        </dependency>
    </dependencies>
    <!-- 引入相关插件 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
在src/main下新增web目录:webapp,同时在其它新增WEB-INF目录以及web.xml
上面的这个添加过程在Project Structure--->Modules中进行新增
在resources下新增相应的mybatis的mapper配置文件的目录:resources/mybatis/mapper
resources下新增Spring的配置文件 application.properties
#Spring MVC的视图解析配置
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
#datasource配置
#spring.datasource.druid.db-type=mysql  #这里可以不用配置会根据连接url来自动识别
#spring.datasource.druid.driver-class-name=org.gjt.mm.mysql.Driver #这里可以不用配置会根据连接url自动识别
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#连接信息
spring.datasource.driver-class-name=org.gjt.mm.mysql.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test
spring.datasource.username=root
spring.datasource.password=root
#连接池配置
spring.datasource.druid.initial-size=15
spring.datasource.druid.max-active=100
spring.datasource.druid.min-idle=15
spring.datasource.druid.max-wait=60000
spring.datasource.druid.keep-alive=true
#默认事务隔离级别“读写提交”
spring.datasource.druid.default-transaction-isolation=2
#Mybatis配置
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
mybatis.type-aliases-package=com.xiaoxie.pojo
#日志配置为DEBUG级别
logging.level.root=DEBUG
logging.level.org.springframework=DEBUG
logging.level.org.org.mybatis=DEBUG
新增一个实体类:com.xiaoxie.pojo.User
package com.xiaoxie.pojo;
import com.xiaoxie.enums.SexEnum;
import org.apache.ibatis.type.Alias;
@Alias("user")
public class User {
    private Long id;
    private String userName;
    private SexEnum sex = null;
    private String note;
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public SexEnum getSex() {
        return sex;
    }
    public void setSex(SexEnum sex) {
        this.sex = sex;
    }
    public String getNote() {
        return note;
    }
    public void setNote(String note) {
        this.note = note;
    }
}
上面这个实体与数据库中的表实体进行对应,注意这里使用了@Alias("user")注解,它是对实体做了别名的定义
新增增一个实体的转换类:com.xiaoxie.vo.UserVo
package com.xiaoxie.vo;
/**视图转换对象*/
public class UserVo {
    private Long id;
    private String userName;
    private int sexCode;
    private String sexName;
    private String note;
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public int getSexCode() {
        return sexCode;
    }
    public void setSexCode(int sexCode) {
        this.sexCode = sexCode;
    }
    public String getSexName() {
        return sexName;
    }
    public void setSexName(String sexName) {
        this.sexName = sexName;
    }
    public String getNote() {
        return note;
    }
    public void setNote(String note) {
        this.note = note;
    }
}
新增一上Dao接口:com.xiaoxie.dao.UserDao
package com.xiaoxie.dao;
import com.xiaoxie.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Component;
import java.util.List;
@Mapper
@Component
public interface UserDao {
    public int insertUser(User user);
    public User getUser(Long id);
    public List<User> getUserList(@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") Long id, @Param("userName") String UserName);
    public int deleteUser(Long id);
}
注意这里的dao接口使用了@Mapper来进行标注,同时使用了@Component,这个是为了在后面Service中做自动注入时不出现报错提示。
在resources/mybatis/mapper下新增一个mybatis的mapper映射文件: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.xiaoxie.dao.UserDao">
    <insert id="insertUser" useGeneratedKeys="true" keyProperty="id" parameterType="user">
        insert into t_user(user_name,note,sex) VALUES (#{userName},#{note},#{sex.id})
    </insert>
    <select id="getUser" parameterType="long" resultType="user">
      select id,user_name as userName,case sex when 1 then 'MALE' else 'FEMALE' end as sex,note from t_user where id=#{id}
    </select>
    <select id="getUserList" resultType="user">
        select id,user_name as userName,case sex when 1 then 'MALE' else 'FEMALE' end as sex,note from t_user
        <where>
            <if test="userName != null">
                and user_name = #{userName}
            </if>
            <if test="note != null">
                and note = #{note}
            </if>
        </where>
        limit #{start},#{limit}
    </select>
    <update id="updateUser" parameterType="user">
        update t_user
        <set>
            <if test="userName != null">user_name=#{userName},</if>
            <if test="note != null">note=#{note},</if>
            <if test="sex != null">sex=#{sex.id}</if>
        </set>
        where id=#{id}
    </update>
    <update id="updateUserName">
        update t_user
        <set>
            <if test="userName != null">user_name = #{userName}</if>
        </set>
        where id=#{id}
    </update>
    <delete id="deleteUser" parameterType="long">
        delete from t_user where id = #{id}
    </delete>
</mapper>
注意这个xml配置文件中namespace指定了具体的dao接口,同时在配置文件中使用user则代表User类(因为在User类添加过别名注解)
新增Spring Boot的启动类:com.xiaoxie.SpringBootStartApplication
package com.xiaoxie;
import org.apache.ibatis.annotations.Mapper;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan(basePackages = "com.xiaoxie.dao",annotationClass = Mapper.class)
public class SpringBootStartApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootStartApplication.class,args);
    }
}
注意这里的@MapperScan注解,其中定义了,扫描的基础包以及对应有Mapper注解的类
新增service的的接口:com.xiaoxie.service.UserService
package com.xiaoxie.service;
import com.xiaoxie.pojo.User;
import java.util.List;
public interface UserService {
    public User insertUser(User user);
    public User getUser(Long id);
    public List<User> getUserList(String userName, String note, int start, int limit);
    public User updateUser(User user);
    public User updateUserName(Long id,String userName);
    public User deleteUser(Long id);
    public User updateUserName2(Long id,String userName);
}
新增service的实现类:com.xiaoxie.service.impl.UserServiceImpl
package com.xiaoxie.service.impl;
import com.xiaoxie.dao.UserDao;
import com.xiaoxie.pojo.User;
import com.xiaoxie.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.List;
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao = null;
    @Transactional
    @Override
    public User insertUser(User user) {
        userDao.insertUser(user);
        return user;
    }
    @Override
    public User getUser(Long id) {
        return userDao.getUser(id);
    }
    @Override
    public List<User> getUserList(String userName, String note, int start, int limit) {
        return userDao.getUserList(userName,note,start,limit);
    }
    @Transactional
    @Override
    public User updateUser(User user) {
        userDao.updateUser(user);
        return user;
    }
    @Transactional
    @Override
    public User updateUserName( Long id, String userName) {
        userDao.updateUserName(id,userName);
        return userDao.getUser(id);
    }
    @Transactional
    @Override
    public User deleteUser(Long id){
        //查用户
        User user = userDao.getUser(id);
        if(user == null)
            return null;
        int result =  userDao.deleteUser(id);
        if(result>0)
            return user;
        return null;
    }
    @Override
    public User updateUserName2(Long id, String userName) {
        userDao.updateUserName(id,userName);
        return userDao.getUser(id);
    }
}
新增controller类:com.xiaoxie.controller.UserController
package com.xiaoxie.controller;
import com.xiaoxie.enums.SexEnum;
import com.xiaoxie.pojo.User;
import com.xiaoxie.service.UserService;
import com.xiaoxie.vo.UserVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
@Controller
public class UserController {
    @Autowired
    private UserService userService = null;
    //转换Vo到Po
    private User ChangeToPo(UserVo userVo){
        User user = new User();
        user.setId(userVo.getId());
        user.setUserName(userVo.getUserName());
        user.setSex(SexEnum.getEnumById(userVo.getSexCode()));
        user.setNote(userVo.getNote());
        return user;
    }
    //转换Po到Vo
    private UserVo changeToVo(User user){
        UserVo userVo = new UserVo();
        userVo.setId(user.getId());
        userVo.setUserName(user.getUserName());
        userVo.setSexCode(user.getSex().getId());
        userVo.setSexName(user.getSex().getName());
        userVo.setNote(user.getNote());
        return userVo;
    }
    //po列表转为Vo列表
    private List<UserVo> changeToVoes(List<User> poList){
        List<UserVo> voList = new ArrayList<>();
        for(User user:poList){
            UserVo userVo = changeToVo(user);
            voList.add(userVo);
        }
        return voList;
    }
    //JSP视图
    @GetMapping("/restful")
    public String page(){
        return "restful";
    }
    //用户新增
    @PostMapping("/insertUser")
    @ResponseBody
    public User insertUser(@RequestBody UserVo userVo){
        User user = this.ChangeToPo(userVo);
        return userService.insertUser(user);
    }
    @GetMapping(value = "/getUser/{id}")
    @ResponseBody
    public UserVo getUser(@PathVariable("id") Long id){
        User user = userService.getUser(id);
        return changeToVo(user);
    }
    @GetMapping("/getUserList/{userName}/{note}/{start}/{limit}")
    @ResponseBody
    public List<UserVo> getUserList(
            @PathVariable("userName") String userName,
            @PathVariable("note") String note,
            @PathVariable("start") int start,
            @PathVariable("limit") int limit
    ){
        List<User> userList = userService.getUserList(userName,note,start,limit);
        return changeToVoes(userList);
    }
    @PutMapping("/updateUser/{id}")
    @ResponseBody
    public User updateUser(@PathVariable("id") Long id,
                           @RequestBody UserVo userVo){
        User user = this.ChangeToPo(userVo);
        user.setId(id);
        userService.updateUser(user);
        return user;
    }
    @PatchMapping("/updateUserName/{id}/{userName}")
    @ResponseBody
    public User changeUserName(@PathVariable("id") Long id,@PathVariable("userName") String userName){
        return userService.updateUserName(id,userName);
    }
    @DeleteMapping("/deleteUser/{id}")
    @ResponseBody
    public User deleteUser(@PathVariable("id") Long id){
        return userService.deleteUser(id);
    }
    //对应到jsp视图
    @GetMapping("/updateUserNamePage")
    public String changUserNamePage(){
        return "change_user_name";
    }
    //@PostMapping("/updateUserName2")
    @PatchMapping("/updateUserName2")   //如果使用这个注解则需要在jsp的表单form内部新增隐藏域:<input type="hidden" name="_method" id="_method" value="PATCH"/>
    @ResponseBody
    public User changeUserName2(Long id,String userName){
        return userService.updateUserName2(id,userName);
    }
}
从上面的controller类中我们可以看到有两个JSP页面是需要我们新增的,根据视图映射的配置,新增JSP的位置在webapp/WEB-INF/jsp目录下
在新增jsp页面前我们先把jquery的js文件复制到项目中
新增webapp/js目录并在这个目录下把jquery的js文件复制进来
新增jsp页面如下:
resetful.jsp
<%--
  Created by IntelliJ IDEA.
  User: adven
  Date: 2021/7/13
  Time: 23:16
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>REST测试页面</title>
    <script src="../../js/jquery-3.2.1.min.js"></script>
    <script type="text/javascript">
        function post() {
            var params = {
                'userName':"李白",
                'sexCode':1,
                'note':"诗仙"
            }
            $.post({
                url:"./insertUser",
                //指定传递参数类型为json
                contentType:"application/json",
                //把JSON转化为字符串
                data:JSON.stringify(params),
                //成功后
                success:function(result){
                    if(result == null || result.id == null){
                        alert("插入用户失败!");
                        return;
                    }
                    alert("插入用户成功。" + JSON.stringify(result));
                }
            });
        }
        function get() {
            $.get("./getUser/28",function(user,status){
                if(user==null || user==""){
                    alert("结果为空");
                } else {
                    alert("用户信息为:" + JSON.stringify(user));
                }
            });
        }
        function getList() {
            $.get("./getUserList/王五/来啊/0/3",function (userList,status) {
                if(userList == null){
                    alert("结果为空");
                } else {
                    alert("用户信息为:" + JSON.stringify(userList));
                }
            });
        }
        
        function updateUser() {
            var params = {
                'userName':'杜甫',
                'sexCode':1,
                'note':'诗圣'
            }
            $.ajax({
                url:"./updateUser/30",
                //请求类型
                type:'PUT',
                //传参类型
                contentType:"application/json",
                //把json转为字符串
                data:JSON.stringify(params),
                success:function(user,status){
                    if(user==null){
                        alert("结果为空");
                    } else {
                        alert("更新成功," + JSON.stringify(user));
                    }
                }
            });
        }
        function updateUserName() {
            $.ajax({
                url:"./updateUserName/100/赵六",
                type:"PATCH",
                success:function (user,status) {
                    if(user == null || user == ""){
                        alert("结果为空");
                    } else {
                        alert("更新成功!" + JSON.stringify(user))
                    }
                }
            });
        }
        function deleteUser() {
            $.ajax({
                url:"./deleteUser/12",
                type:"DELETE",
                success:function(user,status){
                    if(user==null || user==""){
                        alert("结果为空");
                    } else {
                        alert("删除用户成功!" + JSON.stringify(user));
                    }
                }
            });
        }
    </script>
</head>
<body>
    <h2>测试RESTful请求</h2>
    <button type="button" οnclick="post();">post</button><br/>
    <button type="button" οnclick="get();">get</button><br/>
    <button type="button" οnclick="getList();">getList</button><br/>
    <button type="button" οnclick="updateUser();">updateUser</button><br/>
    <button type="button" οnclick="updateUserName();">updateUserName</button><br/>
    <button type="button" οnclick="deleteUser();">deleteUser</button>
</body>
</html>
change_user_name.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>表单进行用户名称修改</title>
</head>
<body>
    <form id="form" action="./updateUserName2" 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>
</body>
</html>
启动Spring Boot则可以根据controller中定义的请求路径与请求URI进行对应来进行测试了。
关于@RestController注解说明
前后端的分离,使得使用JSON作为前后端交互已经非常普遍了,向前面的项目中如何每个方法上加上@ResponseBody才能把数据模型转为JSON,显得会有些冗余,这个时候Spring MVC在支持REST风格中提供了@RestController,这个注解放在controller类上,它可以把控制器返回的对象转为JSON数据集。
新增一个工具类:com.xiaoxie.utils.ChangePoAndVo
package com.xiaoxie.utils;
import com.xiaoxie.enums.SexEnum;
import com.xiaoxie.pojo.User;
import com.xiaoxie.vo.UserVo;
import java.util.ArrayList;
import java.util.List;
public class ChangePoAndVo {
    //转换Vo到Po
    public static User ChangeToPo(UserVo userVo){
        User user = new User();
        user.setId(userVo.getId());
        user.setUserName(userVo.getUserName());
        user.setSex(SexEnum.getEnumById(userVo.getSexCode()));
        user.setNote(userVo.getNote());
        return user;
    }
    //转换Po到Vo
    public static UserVo changeToVo(User user){
        UserVo userVo = new UserVo();
        userVo.setId(user.getId());
        userVo.setUserName(user.getUserName());
        userVo.setSexCode(user.getSex().getId());
        userVo.setSexName(user.getSex().getName());
        userVo.setNote(user.getNote());
        return userVo;
    }
    //po列表转为Vo列表
    public static List<UserVo> changeToVoes(List<User> poList){
        List<UserVo> voList = new ArrayList<>();
        for(User user:poList){
            UserVo userVo = changeToVo(user);
            voList.add(userVo);
        }
        return voList;
    }
}
新增一个controller类:com.xiaoxie.controller.UserController2
package com.xiaoxie.controller;
import com.xiaoxie.pojo.User;
import com.xiaoxie.service.UserService;
import com.xiaoxie.utils.ChangePoAndVo;
import com.xiaoxie.vo.UserVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
@RestController //方法默认使用JSON返回
public class UserController2 {
    @Autowired
    private UserService userService = null;
    //映射JSP视图
    @GetMapping(value = "/restful2")
    public ModelAndView index(){
        ModelAndView mv = new ModelAndView("restful");
        return mv;
    }
    //获取用户
    @GetMapping(value = "/getUser2/{id}")
    public UserVo getUser(@PathVariable("id") Long id){
        User user = userService.getUser(id);
        return ChangePoAndVo.changeToVo(user);
    }
}
在这个类上使用了注解:@RestController,则下面的方法默认是返回JSON数据。
如果我们需要映射JSP页面上去则需要使用ModelAndView指定视图位置。
关于视图的渲染
在Spring MVC中,IoC容器启动时注册了两个HttpMessageConverter接口的实现类,分别如下:
StringHttpMessageConverter
MappingJackson2HttpMessageConverter
在HttpMessageConverter接口中定了了canWrite方法,方法如下
boolean canWrite(Class<?> clazz,MediaType mediaType);
这个方法中MediaType可以传入的媒体类型,Spring MVC在执行控制器的方法后回去遍历注册的HttpMessageConverter接口的实现类使用canWrite方法去判断是否拦截控制器的返回。
在@RequestMapping、@GetMapping等注解中还存在consumes和produces两个属性
    consumes:限定方法接收什么类型的请求体
    produces:限定返回的媒体类型,只有request请求头中Accept中包含的类型才返回
在UserController2类中新增方法
//获取用户名称
@GetMapping(value = "/getUserName/{id}",
        //设置可以接受任意类型的请求体
        consumes = MediaType.ALL_VALUE,
        //限定返回的是文本类型
        produces = MediaType.TEXT_PLAIN_VALUE
)
public String getUserName(@PathVariable("id") Long id){
    User user = userService.getUser(id);
    //返回字符串名称
    return user.getUserName();
}
上面这个方法返回的结果会是一个文本
控制器使用了@RestController注解,则默认会使用JSON作为返回类型,它会默认方法标注为“application/json;charset=UTF-8”,这样的话当控制器方法结束后,Spring会遍历注册好HttpMessageConverter接口的实现类,这时MappingJackson2HttpMessageConverter的canWrite方法返回true,则会启用这个实现类转为JSON数据集类型。然而上面的方法中则是修改了原有@ResetController默认的JSON类型,会被Spring MVC注册好的StringHttpMessageConverter拦截,这样这就会转为一个字符串。
当HttpMessageConverter机制没有处理的类型,它会流转到ViewResolver。在Spring对REST风格的支持中,还会提供协商视图解析器(ContentNegotiatingViewResolver),当控制器返回结果找不到HttpMessageConver解析时,主会流转到这里进行解析。如,返回的是ModelAndView,它会去处理这个ModelAndView,先去解析View的类型,然后根据返回,找到最好的视图解析器去处理。
Spring MVC内置好了如下几个视图解析器
    BeanNameViewResolver:根据请求URI名称找到对应的视图
    ViewResolverComposite:视图解析器组合
    InternalResourceViewResolver:逻辑视图解析器(最为常用,JstlViewResolver是它的子类)
对HTTP状态码、异常和响应头处理
Spring提供了实体封装类ResponseEntity和注解@ResponseStatus
ResponseEntity:可以有效封装错误消息和状态码
@ResponseStatus可以配置指定的响应码给客户
通常来说,后台请求成功后会返回一个状态码200,但是要注意这个状态只能表示请求成功,并不能完全表控制器对资源的操作成功。
在控制器类UserController2中新增两个方法
@PostMapping(value = "/insertUser/entity")
public ResponseEntity<UserVo> insertUser(@RequestBody UserVo userVo){
    User user = ChangePoAndVo.ChangeToPo(userVo);
    userService.insertUser(user);
    UserVo userVo1 = ChangePoAndVo.changeToVo(user);
    HttpHeaders headers = new HttpHeaders();
    String success = (userVo1 == null || userVo1.getId() == null)?"false":"true";
    //设置响应头
    headers.add("success",success);
    //返回创建成功的状态码
    return new ResponseEntity<UserVo>(userVo1,headers, HttpStatus.CREATED);
}
@PostMapping(value = "/insertUser/annotation")
@ResponseStatus(HttpStatus.CREATED) //指定资源创建成功 201
public UserVo insertUserAnnotation(@RequestBody UserVo userVo){
    User user = ChangePoAndVo.ChangeToPo(userVo);
    userService.insertUser(user);
    UserVo userVo1 = ChangePoAndVo.changeToVo(user);
    return userVo1;
}
在这两个方法中,第一个使用了ResponseEntity,一个使用了@ResponseStatus注解
在使用ResponseEntity的方法中可以设置对应的响应头,最后把响应的数据、响应头、响应状态一起返回
在使用@ResponseStatus注解的方法中,只是直接在注解中设置响应状态
修改restful.jsp页面,新增js函数:postStatus()
<%--
  Created by IntelliJ IDEA.
  User: adven
  Date: 2021/7/13
  Time: 23:16
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>REST测试页面</title>
    <script src="../../js/jquery-3.2.1.min.js"></script>
    <script type="text/javascript">
        function post() {
            var params = {
                'userName':"李白",
                'sexCode':1,
                'note':"诗仙"
            }
            $.post({
                url:"./insertUser",
                //指定传递参数类型为json
                contentType:"application/json",
                //把JSON转化为字符串
                data:JSON.stringify(params),
                //成功后
                success:function(result){
                    if(result == null || result.id == null){
                        alert("插入用户失败!");
                        return;
                    }
                    alert("插入用户成功。" + JSON.stringify(result));
                }
            });
        }
        function get() {
            $.get("./getUser/28",function(user,status){
                if(user==null || user==""){
                    alert("结果为空");
                } else {
                    alert("用户信息为:" + JSON.stringify(user));
                }
            });
        }
        function getList() {
            $.get("./getUserList/王五/来啊/0/3",function (userList,status) {
                if(userList == null){
                    alert("结果为空");
                } else {
                    alert("用户信息为:" + JSON.stringify(userList));
                }
            });
        }
        
        function updateUser() {
            var params = {
                'userName':'杜甫',
                'sexCode':1,
                'note':'诗圣'
            }
            $.ajax({
                url:"./updateUser/30",
                //请求类型
                type:'PUT',
                //传参类型
                contentType:"application/json",
                //把json转为字符串
                data:JSON.stringify(params),
                success:function(user,status){
                    if(user==null){
                        alert("结果为空");
                    } else {
                        alert("更新成功," + JSON.stringify(user));
                    }
                }
            });
        }
        function updateUserName() {
            $.ajax({
                url:"./updateUserName/100/赵六",
                type:"PATCH",
                success:function (user,status) {
                    if(user == null || user == ""){
                        alert("结果为空");
                    } else {
                        alert("更新成功!" + JSON.stringify(user))
                    }
                }
            });
        }
        function deleteUser() {
            $.ajax({
                url:"./deleteUser/12",
                type:"DELETE",
                success:function(user,status){
                    if(user==null || user==""){
                        alert("结果为空");
                    } else {
                        alert("删除用户成功!" + JSON.stringify(user));
                    }
                }
            });
        }
        function postStatus() {
            //请求体
            var params={
                'userName':'诸葛亮',
                'sexCode':1,
                'note':'诸葛孔明'
            };
            //var url = "./insertUser/entity";
            var url = "./insertUser/annotation";
            $.post({
                url:url,
                //传递参数的类型JSON
                contentType:"application/json",
                //json转字符串
                data:JSON.stringify(params),
                //成功后
                success:function(result,status,jqXHR){
                    //获取响应头
                    var success = jqXHR.getResponseHeader("success");
                    //获取状态码
                    var status = jqXHR.status;
                    if(success != null){
                        alert("响应头参数是:" + success + "状态码是:" + status);
                    }
                    alert("响应状态码:" + status);
                    if(result == null || result.id == null){
                        alert("插入失败");
                        return ;
                    }
                    alert("插入成功");
                }
            });
        }
    </script>
</head>
<body>
    <h2>测试RESTful请求</h2>
    <button type="button" οnclick="post();">post</button><br/>
    <button type="button" οnclick="get();">get</button><br/>
    <button type="button" οnclick="getList();">getList</button><br/>
    <button type="button" οnclick="updateUser();">updateUser</button><br/>
    <button type="button" οnclick="updateUserName();">updateUserName</button><br/>
    <button type="button" οnclick="deleteUser();">deleteUser</button><br/>
    <button type="button" οnclick="postStatus();">postStatus</button>
</body>
</html>
异常处理
自定义一个异常com.xiaoxie.exception.MyUserException
package com.xiaoxie.exception;
/**自定义异常类*/
public class MyUserException extends RuntimeException {
    //异常编码
    private Long code;
    //自定义异常信息
    private String customMsg;
    public MyUserException(){}
    public MyUserException(Long code,String customMsg){
        super();
        this.code = code;
        this.customMsg = customMsg;
    }
    public Long getCode() {
        return code;
    }
    public void setCode(Long code) {
        this.code = code;
    }
    public String getCustomMsg() {
        return customMsg;
    }
    public void setCustomMsg(String customMsg) {
        this.customMsg = customMsg;
    }
}
定义一个控制器通知com.xiaoxie.advice.UserControllerAdvice
package com.xiaoxie.advice;
import com.xiaoxie.exception.MyUserException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/** userController的控制器通知 */
@ControllerAdvice(
      //指定拦截的包
      basePackages = {"com.xiaoxie.controller.*"},
      //限制做了对应注解的才会被拦截
      annotations = {Controller.class, RestController.class}
)
public class UserControllerAdvice {
    //定义拦截的异常类型
    @ExceptionHandler(value = MyUserException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)   //定义服务器错误状态码
    public Map<String,Object> exception(MyUserException ex){
        Map<String,Object> map = new HashMap<>();
        //组装异常信息
        map.put("code",ex.getCode());
        map.put("message",ex.getCustomMsg());
        return map;
    }
}
这个通知器使用注解@ControllerAdvice,它其中属性basePackages指定了拦截的controller所在基础包,annotations限定做了控制器使用了@Controller、@RestController注解的控制器才会拦截(这里了可以自定义一个注解,把这个注解应用到要被拦截的控制器上)
这个类中的方法exception中,使用了如下注解
@ExceptionHandler:表示对异常拦截后做处理,其中value属性指定了对那个异常做拦截,这里指定了自定义的异常类MyUserException.class
在控制器UserController2中新增方法
@GetMapping(value = "getUserExp/{id}")
@ResponseStatus(HttpStatus.OK)  //响应成功
public UserVo getUserExp(@PathVariable Long id){
    User user = userService.getUser(id);
    if(user == null){   //如果没有找到用户则抛出指定异常
        throw new MyUserException(1L,"找不到用户【" + id  + "】的信息" );
    }
    UserVo userVo = ChangePoAndVo.changeToVo(user);
    return userVo;
}
这个方法中可以看到,当查询用户没有查询到时会抛出一个MyUserException的异常
客户端请求RestTemplate
RestTemplate是一个模板类,用这来请求后端暴露的URI以获取资源,它的底层是通过类HttpURLConnectioni实现的。
使用RestTemplate进行HTTP GET请求
新增一个类进行RestTemplate请求测试:com.xiaoxie.restTemplate.RestTemplate4User
package com.xiaoxie.restTemplate;
import com.xiaoxie.vo.UserVo;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class RestTemplate4User {
    public static RestTemplate restTemplate = new RestTemplate();
    /**根据id获取用户*/
    public static UserVo getUser(Long id){
        //消费服务,三个参数:1.请求url;2.返回类型;3.URI路径参数
        UserVo userVo = restTemplate.getForObject("http://127.0.0.1:8080/getUser/{id}" ,
                UserVo.class, id);
        //打印用户名称
        System.out.println("获取到的用户名称:" + userVo.getUserName());
        return userVo;
    }
}
上面的getUser方法中调用了RestTemplate的getForObject方法,这个方法有三个参数
    第一个参数URI,表示请求的资源URI
    第二个参数UserVo.class,表示请求会返回的类型是UserVo,因为服务器返回的是JSON数据,RestTemplate会根据这个参数在内部把JSON转为UserVo对象
    第三个参数,对应为URI中对应的参数,这是一个可变长的参数,当URI中有多个参数时,这里按序顺写上即可
在Spring Boot启动类后面添加如下代码:
System.out.println("****************** 使用RestTemplate请求后端资源 *********************");
UserVo userVo = RestTemplate4User.getUser(30L);
在Spring Boot启动完成后,在控制台可以看到相应的输出
如果存在多个参数的时候,可以把参数使用Map集合进行封装,Map集合中的key要与对应的请求参数名称一一对应。
在RestTemplate4User类中新增一个方法
/**封装多个参数获取用户*/
public static List<UserVo> findUsers(String userName,String note,int start,int limit){
    //封装参数为Map,其中的key需要与请求uri中参数一一对应
    Map<String,Object> params = new HashMap<>();
    params.put("userName",userName);
    params.put("note",note);
    params.put("start",start);
    params.put("limit",limit);
    String url ="http://127.0.0.1:8080/getUserList/{userName}/{note}/{start}/{limit}";
    //请求后端资源
    ResponseEntity<List> responseEntity = restTemplate.getForEntity(url, List.class, params);
    List<UserVo> userVos = responseEntity.getBody();
    return userVos;
}
这里调用的是RestTemplate的getForEntity方法。
POST请求传递JSON请求体
在RestTemplate4User类中新增一个方法
public static User insertUser(UserVo userVo){
    //请求头
    HttpHeaders headers = new HttpHeaders();
    //设置请求内容为JSON类型
    headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
    //创建请求的实体对象
    HttpEntity<UserVo> requestEntity = new HttpEntity<>(userVo,headers);
    //向后端请求资源时传递请求实体对象,并且会回填ID
    User user = restTemplate.postForObject("http://127.0.0.1:8080/insertUser", requestEntity, User.class);
    System.out.println("创建用户成功!新用户ID:" + user.getId());
    return user;
}
先定义一个请求头,在请求头中定定请求体内容为JSON类型,然后把它与请求实体(UserVo对象)绑定到请求实体上,然后restTemplate调用postForObject方法把请求实体对像传递过去。
在ResetTemplate4User中新增一个方法
public static void deleteUser(Long id){
   //先去查询是否存在用户
    UserVo user = getUser(id);
    if(user!=null) {
        restTemplate.delete("http://127.0.0.1:8080/deleteUser/{id}", id);
        System.out.println("删除用户id为:" + id + "的用户!");
    } else {
        System.out.println("要删除的用户不存在!");
    }
}
这个删除的方法有一点要注意的就是,RestTemplate的delete方法返回值是void,方法的参数中也不需要指定返回对象的类型。
获取响应头、状态码和资源交换
当我们对资源请求的时候如果不成功,这些信息可以存放在响应头中,服务器也会返回错误的状态码。
在RestTemplate4User中新增方法
public static User insertUserEntity(UserVo userVo){
    //请求头
    HttpHeaders headers = new HttpHeaders();
    //请求类型
    headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
    //绑定请求体和头
    HttpEntity<UserVo> request = new HttpEntity<>(userVo,headers);
    //请求服务器
    ResponseEntity<User> userResponseEntity = restTemplate.postForEntity(
            "http://localhost:8080/insertUser/entity", request, User.class);
    //获取响应体
    User user = userResponseEntity.getBody();
    //获取响应头
    HttpHeaders respHeaders = userResponseEntity.getHeaders();
    //获取响应属性
    List<String> success = respHeaders.get("success");
    //响应状态码
    int status = userResponseEntity.getStatusCodeValue();
    System.out.println("响应状态码:" + status);
    System.out.println("响应头success:" + success.toString());
    System.out.println("新增用户的ID:" + user.getId());
    return user;
}
这里使用了RestTemplate的postForEntity方法,这个方法会返回一个ResponseEntity对象,这个对象就包含了返回响应体、状态码、响应头信息。
RestTemplate中的exchange方法实例
在RestTemplate4User中新增方法
public static User userExchange(UserVo userVo,Long id){
    //请求头
    HttpHeaders headers = new HttpHeaders();
    //请求类型
    headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
    //绑定请求头与请求体
    HttpEntity<UserVo> request = new HttpEntity<>(userVo,headers);
    String url = "http://localhost:8080/insertUser/entity";
    //请求服务器
    ResponseEntity<User> userEntity = restTemplate.exchange(url, HttpMethod.POST, request, User.class);
    //获取响应体
    User user = userEntity.getBody();
    //获取响应头
    HttpHeaders respHeaders = userEntity.getHeaders();
    //响应头属性
    List<String> success =respHeaders.get("success");
    //响应码
    int status = userEntity.getStatusCodeValue();
    System.out.println("响应状态码:" + status);
    System.out.println("响应头success:" + success.toString());
    System.out.println("新增用户的ID:" + user.getId());
    //修改URL地址
    url = "http://localhost:8080/getUser/{id}";
    ResponseEntity<UserVo> userVoEntity = restTemplate.exchange(url, HttpMethod.GET, null, UserVo.class, id);
    //获取响应体
    UserVo u = userVoEntity.getBody();
    System.out.println("获取到的用户名称:" + u.getUserName());
    return user;
}
在exchange方法中可以指定请求头、请求体、HTTP请求类型及参数……
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot 是一个基于 Spring 框架的开发框架,它可以快速构建基于 RESTful 风格的 Web 应用程序。REST(Representational State Transfer)是一种基于 HTTP 协议的 Web 应用程序架构风格,它使用 HTTP 请求方法(GET、POST、PUT、DELETE 等)来访问和操作资源,通常使用 JSON 或 XML 格式来表示数据。 Spring Boot 提供了许多注解和工具,使得开发 RESTful Web 服务变得简单和快速。例如,使用 @RestController 注解来定义一个 RESTful Web 服务控制器类,使用 @RequestMapping 注解来映射 HTTP 请求到控制器方法,使用 @RequestBody 注解来接收 HTTP 请求的 JSON 或 XML 数据,使用 ResponseEntity 类来构造 HTTP 响应等等。 下面是一个简单的 Spring Boot RESTful Web 服务示例代码: ```java @RestController @RequestMapping("/api") public class UserController { @Autowired private UserService userService; @GetMapping("/users") public List<User> getUsers() { return userService.getAllUsers(); } @GetMapping("/users/{id}") public User getUser(@PathVariable Long id) { return userService.getUserById(id); } @PostMapping("/users") public User createUser(@RequestBody User user) { return userService.createUser(user); } @PutMapping("/users/{id}") public User updateUser(@PathVariable Long id, @RequestBody User user) { return userService.updateUser(id, user); } @DeleteMapping("/users/{id}") public void deleteUser(@PathVariable Long id) { userService.deleteUser(id); } } ``` 在这个示例,我们定义了一个 UserController 类来处理用户管理相关的 RESTful Web 服务请求。通过 @RestController 注解,我们将 UserController 类声明为一个 RESTful Web 服务控制器类。通过 @RequestMapping 注解,我们将所有的请求映射到“/api”路径下。然后我们定义了一些方法来处理 GET、POST、PUT、DELETE 请求,这些方法使用了 @GetMapping、@PostMapping、@PutMapping 和 @DeleteMapping 注解来标识请求类型和请求路径。在这些方法,我们使用了 @RequestBody 注解来接收 HTTP 请求的 JSON 数据,并使用 ResponseEntity 类来构造 HTTP 响应。同时,我们还注入了一个 UserService 类来处理用户相关的业务逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值