互联网应用主流框架整合之构建REST风格的系统

发布于:2024-06-16 ⋅ 阅读:(15) ⋅ 点赞:(0)

REST(Representational State Transfer),中文译为“表述性状态转移”,是由Roy Fielding博士在他的博士论文中提出的一种软件架构风格,特别适用于网络应用的设计。REST不是一个标准,而是一种设计原则和约束集,它基于HTTP协议,以资源为中心,通过统一的接口和标准的方法来访问和操作这些资源。以下是REST风格的几个核心概念和原则:

核心概念

  • 资源(Resources):在REST中,网络上的所有内容都可以被视为资源。资源可以是文本、图片、视频、服务等任何可以命名的东西。每个资源都有一个唯一的标识符,即URI(Uniform Resource Identifier)
  • 表现层(Representations):资源的表现形式,即资源的具体数据表现,如HTML、XML、JSON等。客户端通过HTTP请求获取资源时,服务器返回的是资源的一个表现层,而不是资源本身
  • 状态转换(State Transfer):客户端和服务器之间通过HTTP协议进行交互,从而改变客户端的状态。这里的“状态转移”不是指服务器端的状态,而是指通过HTTP请求响应的方式,让客户端从一个状态转移到另一个状态

设计原则

  • 客户端-服务器(Client-Server):保持客户端和服务器职责的分离,使得它们可以独立地进化。客户端负责展示,服务器负责数据的管理和业务逻辑
  • 无状态(Stateless):服务器不保存客户端的会话信息。每次请求都必须包含理解该请求所必需的所有信息。这提高了系统的可伸缩性,因为服务器不需要为每个用户的会话维护状态
  • 可缓存(Cacheable):利用HTTP协议的缓存机制,使得响应可以在客户端、代理服务器等多级进行缓存,减少网络请求,提高响应速度
  • 分层系统(Layered System):系统可以设计成多层结构,每一层只与相邻层通信,这样可以简化复杂度,并且允许更容易地添加、修改或移除中间层,而不会影响整体架构
  • 统一接口(Uniform Interface):所有资源都通过统一的接口进行访问,主要通过HTTP标准方法(GET, POST, PUT, DELETE等)来实现对资源的增删查改操作
  • 按需代码(Code-On-Demand,可选):服务器可以提供可执行代码(如JavaScript),客户端可以选择执行这段代码来实现更丰富的功能。但这不是REST定义中的强制要求

REST风格的应用设计强调简单、灵活和高效,广泛应用于现代Web服务和API设计中,特别是对于需要跨平台、跨语言交互的场景,RESTful API因其规范性和易用性而成为首选。

最佳实践

RESTful风格则是遵循REST原则设计的Web服务。简单来说,当一个Web服务的设计完全符合REST的约束条件和原则时,我们称这个Web服务为RESTful,总体来说,REST是一种架构设计风格,而RESTful是这种风格的具体实践

RESTful API(Representational State Transferful Application Programming Interface)是一种遵循REST架构风格设计的Web服务API。它利用HTTP协议的特性,提供一套统一、简洁、无状态的接口设计模式,用于在客户端和服务器之间交换数据和管理资源。RESTful API的核心在于如何组织和访问网络上的资源,以及如何表述这些资源的状态。以下是RESTful API的一些关键特征和最佳实践:

关键特征

  • 资源导向(Resource-Oriented):RESTful API围绕资源展开,每个资源通过唯一的URL(Uniform Resource Locator)来标识,资源的URL应该清晰、直观,反映资源的层次关系
  • 标准HTTP方法(Standard HTTP Methods):利用HTTP协议预定义的方法来对资源进行操作,常见的有:
    • GET:从服务器检索资源(应该是安全和幂等的)
    • POST:向服务器提交数据,常用于创建新资源
    • PUT:替换服务器上的现有资源或创建指定资源(如果不存在)
    • PATCH:部分更新已有资源
    • DELETE:删除指定资源
  • 表述层多样性(Diverse Representations):支持多种数据格式(如JSON/XML/YAML等)来表示资源,客户端可以通过Accept头部指定期望的响应格式
  • 无状态(Statelessness):服务器不存储关于客户端的上下文信息,每次请求都包含完成该请求所需的所有信息,这有利于扩展性和负载均衡
  • 可缓存性(Cachability):利用HTTP的缓存机制,可以对响应进行缓存,减少网络请求,提高效率

最佳实践

  • 版本控制:在API的URL中加入版本号,以便于不同版本间的兼容和迁移,如/api/v1/users
  • 使用复数名词:资源的URL推荐使用复数形式,如使用/users而非/user,以更好地表达资源集合的概念
  • 过滤、排序和分页:提供查询参数来支持资源列表的过滤、排序和分页,如/users?state=active&sort=name&limit=10
  • 错误处理:使用合适的HTTP状态码来表示错误,如404表示资源未找到,同时返回易于理解的错误消息
  • HATEOAS(Hypermedia as the Engine of Application State):虽然在实际应用中较少严格遵循,但理想上,响应中应包含链接,指示客户端下一步可能的动作或相关资源,促进API的自发现性

RESTful API的设计旨在简化客户端与服务端之间的交互,提高系统的可扩展性和可维护性。通过遵循上述原则和最佳实践,可以构建出既强大又易于使用的Web服务接口

一些简单的例子如下所示:

#获取角色信息,1是角色编号
GET /role/1

#查询多个角色
GET /roles/{roleName}

#新建角色
POST /role/{roleName}/{note}

#修改角色
PUT /role/{id}/{roleName}/{note}

#使用动词,在REST风格设计中URI不该存在动词
GET /role/get/{id}

#按版本获取角色
#这里请注意,当无论何种版本都指向同一个角色时,不建议将版本参数{version}放在URI中
#因为在REST风格中,一个URI就代表一个资源,不同的URI不该指向同一个资源
#可以考虑放在请求头中,这样URI依旧是GET role/{id}, 在请求头中放入版本参数即可
GET /role/{version}/{id}

#错误使用HTTP参数,这里问号加id参数是不符合REST风格的
PUT /role/{roleName}?id=1
#可以修改为
PUT role/{id}/{roleName}

@ResponseBody

之前文章的代码中,使用MappingJackson2JsonView将结果转化为JSON视图,还有更简单的方法,就是使用注解@ResponseBody,只是它的原理和视图不同,功能上主要用于标注控制器的映射方法,将方法返回的结果转变为JSON数据集展示,示例代码如下

package com.springrest.controller;

import com.springrest.pojo.Role;
import com.springrest.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
/**
 * RoleController类负责处理与角色相关的HTTP请求。
 * 它使用RoleService来执行具体的业务逻辑。
 */
@Controller
@RequestMapping("/role")
public class RoleController {

	/**
	 * 自动注入RoleService实例,用于处理角色相关的业务逻辑。
	 */
	@Autowired
	private RoleService roleService = null;
	/**
	 * 处理GET请求,根据角色ID获取角色信息。
	 * @param id 角色的唯一标识符。
	 * @return 对应于请求ID的角色对象。
	 */
	@GetMapping(value = "/info/{id}")
	@ResponseBody
	public Role getRole(@PathVariable("id") Long id) {
		return roleService.getRole(id);
	}
}

这样在请求/mvc/role/info/2就可以看到如下的页面了
在这里插入图片描述
服务启动过程中经常会遇到的一个问题是无法创建连接,其中有一种原因是数据库的版本和连接驱动类的版本不匹配,如果使用的是MySQL8.0以下的版本,那在POM中添加的依赖应该是

	<!-- 引入MySQL数据库连接驱动依赖 -->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>5.1.29</version>
	</dependency>

配置数据源的时候,driverClassName配置应该是props.setProperty("driverClassName", "com.mysql.jdbc.Driver");而如果MySQL用的是8.0以上的版本,那么POM中应该添加的依赖是

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.30</version> <!-- 使用实际的版本号 -->
    </dependency>

配置数据源的时候,driverClassName配置应该是props.setProperty("driverClassName", "com.mysql.cj.jdbc.Driver");

但这是在Spring5基础上的,如果使用低版本的Spring MVC,需要自己创建MappingJackson2HttpMessageConverter(如下代码所示),Spring5之后RequestMappingHandlerAdapter再初始化过程中,会自动注册MappingJackson2HttpMessageConverter对象,所以只需要依赖相关的JSON类库就可以了,自己创建的话代码如下所示;

	/**
	 * 初始化并配置RequestMappingHandlerAdapter,用于处理RESTful API的请求。
	 * @return 配置好的RequestMappingHandlerAdapter实例。
	 */
	@Bean(name = "requestMappingHandlerAdapter")
	public HandlerAdapter initRequestMappingHandlerAdapter() {
		// 创建RequestMappingHandlerAdapter实例
		RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();

		// 创建MappingJackson2HttpMessageConverter实例,用于处理JSON格式的HTTP消息
		MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();

		// 定义支持的媒体类型为application/json
		MediaType mediaType = MediaType.APPLICATION_JSON;

		// 创建支持的媒体类型列表,并添加application/json
		List<MediaType> mediaTypes = new ArrayList<MediaType>();
		mediaTypes.add(mediaType);

		// 配置converter支持的媒体类型
		converter.setSupportedMediaTypes(mediaTypes);

		// 将converter添加到handler adapter的转换器列表中
		adapter.getMessageConverters().add(converter);

		// 返回配置好的handler adapter
		return adapter;
	}

也可以通过XML创建MappingJackson2HttpMessageConverter

    <!-- 配置处理HTTP请求和响应的适配器 -->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <!-- 配置消息转换器,用于支持JSON格式的请求和响应 -->
        <property name="messageConverters">
            <list>
                <!-- 引用JSON消息转换器 bean -->
                <ref bean = "converter"/>
            </list>
        </property>
    </bean>

    <!-- 配置JSON消息转换器 -->
    <bean id="converter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
        <!-- 配置支持的媒体类型,这里指定为UTF-8编码的JSON -->
        <property name="supportedMediaTypes">
            <list>
                <value>application/json;charset=UTF-8</value>
            </list>
        </property>
    </bean>

SpringMVC的REST风格

为了更好地支持REST风格,Spring MVC4.3之后更新了对REST更多的支持,增加了更多的注解,例如@GetMapping@PostMapping@PutMapping@DeleteMapping@RestController等等,大致可以将这些注解分为两类,其一是映射路由类,包括@GetMapping@PostMapping@PutMapping@DeleteMapping等,其二是标注控制器类,就只有一个@RestController,它将控制器映射方法的返回结果默认为JSON数据集

Rest风格的注解

REST风格映射路由,实际上是使用@GetMapping@PostMapping@PutMapping@DeleteMapping等注解简化@RequestMapping的编写,例如@GetMapping("/info/{id}")就相当于@RequestMapping(value="/info/{id}",method=RequestMethod.GET),对应的其他几个也是类似的等效,但这些注解和@RequestMapping的不同是,它们只能标注在方法上,不能标注在类上

package com.springrest.vo;

/**
 * 结果消息类,用于封装操作结果的成功状态和相关消息。
 */
public class ResultMessage {

    /**
     * 操作是否成功的标志。
     */
    private Boolean success = false;

    /**
     * 操作结果的消息,用于描述操作的具体情况。
     */
    private String message = null;

    /**
     * 构造函数,用于创建一个带有成功状态和消息的结果消息对象。
     *
     * @param success 操作的成功状态。
     * @param message 操作的结果消息。
     */
    public ResultMessage(Boolean success, String message) {
        this.success = success;
        this.message = message;
    }

    /**
     * 默认构造函数,用于创建一个成功状态为false,消息为空的结果消息对象。
     */
    public ResultMessage() {
    }

    /**
     * 获取操作成功的标志。
     *
     * @return 操作成功的布尔值。
     */
    public Boolean getSuccess() {
        return success;
    }

    /**
     * 设置操作成功的标志。
     *
     * @param success 操作的成功状态。
     */
    public void setSuccess(Boolean success) {
        this.success = success;
    }

    /**
     * 获取操作结果的消息。
     *
     * @return 操作结果的消息字符串。
     */
    public String getMessage() {
        return message;
    }

    /**
     * 设置操作结果的消息。
     *
     * @param message 操作结果的消息。
     */
    public void setMessage(String message) {
        this.message = message;
    }

}

package com.springrest.controller;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

import com.springrest.pojo.Role;
import com.springrest.service.RoleService;
import com.springrest.vo.ResultMessage;


@RestController
@RequestMapping("/role2")
public class RoleControllerII {

    @Autowired
    private RoleService roleService = null;


    @GetMapping("/page")
    public ModelAndView page() {
        ModelAndView mv = new ModelAndView("restful");
        return mv;
    }


    @GetMapping("/info/{id}")
    public Role getRole(@PathVariable("id") Long id) {
        return roleService.getRole(id);
    }

    @PostMapping("/")
    public ResultMessage newRole(@RequestBody Role role) {
        Integer result = roleService.insertRole(role);
        if (result > 0) {
            return new ResultMessage(true, "新增角色成功,编号为:" + role.getId());
        }
        return new ResultMessage(false, "新增角色失败!");
    }

    @PutMapping("/")
    public ResultMessage updateRole(@RequestBody Role role) {
        Integer result = roleService.updateRole(role);
        if (result > 0) {
            return new ResultMessage(true, "修改角色成功,编号为:" + role.getId());
        }
        return new ResultMessage(false, "修改角色失败!");
    }

    @DeleteMapping("/{id}")
    public ResultMessage deleteRole(@PathVariable("id") Long id) {
        Integer result = roleService.deleteRole(id);
        if (result > 0) {
            return new ResultMessage(true, "删除角色成功,编号为:" + id);
        }
        return new ResultMessage(false, "新增角色失败!编号为" + id);
    }
}

在Controller类上标注了@RestController,表示该控制器将采用REST风格,其他的URI都采用了REST风格设计,这里需要特别注意的是public ModelAndView page()方法,它返回的是ModelAndView对象,而不是字符串,因为标注了@RestController后,视图解析器就失去了解析字符串的能力,必须使用ModelAndView才能定位到视图,而page返回一个的是一个字符串"restful",它指向一个/WEB-INF/jsp/JSP文件,源码如下

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
"http://www.w3.org/TR/html4/loose.dtd">
<html>
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<title>REST风格测试</title>
		<script type="text/javascript"
			src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
		<script type="text/javascript">
		<!-- 此处加入对应的JavaScript脚本,进行测试 -->
		function post() {
			var role = {
				'roleName' : 'role_name_new',
				'note' : "note_new"
			};
			$.post({
				url : "./",
				//此处需要告知传递参数类型为JSON,不能缺少
				contentType : "application/json",
				//将JSON转化为字符串传递
				data : JSON.stringify(role),
				//成功后的方法
				success : function(result) {
					if (result == null || result.success == false) {
						alert("插入失败");
						return;
					}
					alert(result.message);
				}
			});
		}
		post();
		function put() {
			var role = {
				'id' : 15,
				'roleName' : 'role_name_update',
				'note' : "note_update"
			};
			
			$.ajax({url : "./", 
			    // 此处告知使用PUT请求
			    type :'PUT', 
		        //此处需要告知传递参数类型为JSON,不能缺少
		        contentType : "application/json",
		        //将JSON转化为字符串传递
		        data : JSON.stringify(role),
		        success : function(result, status) {
		            if (result == null) {
		                alert("结果为空")
		            } else {
		                alert(JSON.stringify(result));
		            }
		        }
		    });
		}
		put();
		
		function del() {
			var id = 17;
		    $.ajax({
		        url : "./" + id, 
		        // 告知请求类型为“DELETE”
		        type :'DELETE', 
		        success : function(result) {
		        if (result == null) {
		            alert("后台出现异常。")
		        } else {
		            alert(result.message);
		        }
		    }});
		}
		del();
		</script>
	</head>
	<body>
	</body>
</html>

JSP文件中加入了JQuery脚本,使用它来简化验证新建的控制器,例如使用了$.post(...)对后端发送Ajax请求,它代表发送POST请求到后端,并组织了一个媒体类型为JSON的请求体发送到后端,这很显然就能匹配上控制器的newRole方法,其他的请求同理

以上是一些正常情况,但经常会出现后端不能正常返回的情况,比如尝试访问编号为1000的角色信息,但数据库中并没有这个角色,用如下代码模拟

        function get() {
        	var id = 1000;
        	// 通过GET请求获取角色信息
        	$.get("./info/" + id,  function(role) { 
        		alert("role_name-> " + role.roleName); 
        	});
        }
        get();

如果角色不存在而返回一个空值,很显然不是友好的结果,比较好的处理是提示用户不存在,实际上使用HTTP请求会有响应码,比如200,比如POST请求创建资源的响应码201分别表示成功,使用响应码比较简单,Spring中提供了枚举类HttpStatus定义各种HTTP响应码,同时提供了注解@ResponseStatus, 修改一下控制器的newRole方法

    /**
     * 通过POST请求创建新角色。
     *
     * @param role 包含新角色信息的请求体。
     * @return 如果角色创建成功,返回包含成功消息和角色ID的结果消息;如果创建失败,返回失败消息。
     */
    @PostMapping("/")
    // 定义响应码为创建成功(201)
    @ResponseStatus(HttpStatus.CREATED)
    public ResultMessage newRole(@RequestBody Role role) {
        // 调用角色服务插入新角色
        Integer result = roleService.insertRole(role);
        // 根据插入结果判断角色创建是否成功
        if (result > 0) {
            // 如果插入成功,返回成功消息和角色ID
            return new ResultMessage(true, "新增角色成功,编号为:" + role.getId());
        }
        // 如果插入失败,返回失败消息
        return new ResultMessage(false, "新增角色失败!");
    }

然后执行到这个方法的时候会看到如下的信息
在这里插入图片描述
使用注解@ResponseStatus,得到了状态码201,状态码比响应码更准确,可以通过状态码确定结果是否正确,这样客户端便可以通过状态码分析请求的结果,然而仅仅有状态码是不够的,有时候请求的失败是后端的限制造成的,比如请求编号为200的角色对象,事实上如果它根本不存在,这个时候应该把状态和原因插入响应头,这样请求者就能更明确地知道原因,并能更便利且直接的提示给用户,处理此类问题,SpringMVC提供了类ResponseEntity<T>,这个类存在3个属性status:HttpStatus类型,表示响应码,headers:HTTP响应头,可以自定义消息,body:响应体,HTTP请求响应的正文

		/**
		 * 执行删除操作的函数。
		 * 该函数通过AJAX请求向服务器发送一个DELETE请求,以删除指定的资源。
		 * 请求成功后,根据服务器返回的结果展示相应的提示信息。
		 */
		function del() {
			// 定义待删除资源的ID
			var id = 17;
			// 发起AJAX请求
			$.ajax({
				// 构造请求的URL,基于当前路径和资源ID
				url : "./" + id,
				// 告知请求类型为“DELETE”
				type :'DELETE',
				// 请求成功回调函数
				success : function(result) {
					// 判断服务器返回的结果是否为空
					if (result == null) {
						// 如果结果为空,提示“后台出现异常”
						alert("后台出现异常。")
					} else {
						// 如果结果不为空,显示服务器返回的提示信息
						alert(result.message);
					}
				},
				// 请求错误回调函数,当执行Ajax请求返回500时,则执行error属性对应的如下方法
				error:function (request, textStatus, errorThrown){
					// 显示请求错误的提示信息
					alert('访问后端失败'+ errorThrown)
				}
			});
		}

上边这段代码中,有个error对应的函数,如果请求错误就会回调该函数,就是当执行Ajax请求返回500时,则执行error属性对应的方法,然而同类的还有很多状态码1xx、2xx、3xx、4xx、5xx等如果每个都这么写代码就会相当复杂

通过status可以设置HTTP的响应码,而一般来说可以设置为200,即便产生错误请求也可以设置为200,这样有利于客户端的编写;Header属性可以设置一些值,作为服务器后端的返回信息,例如设置"success"属性表示该请求是否正常,如果不正常再通过属性"message"告诉服务器后端的问题是什么,这样更有利于客户端的编写,在Controller中新增方法,代码如下

    @GetMapping("/info2/{id}")
    public ResponseEntity<Role> getRole2(@PathVariable("id") Long id) {
        // 响应体
        Role body = roleService.getRole(id);
        // 响应头
        HttpHeaders headers = new HttpHeaders();
        if (body != null) { // 获取角色成功
            headers.add("success", "true");
            headers.add("message", "ok!!");
        } else { // 获取角色失败
            headers.add("success", "false");
            headers.add("message", "no id=[" + id + "] role info!!");
        }
        // 创建ResponseEntity
        ResponseEntity<Role> roleEntity = new ResponseEntity<>(body, headers, HttpStatus.OK);
        return roleEntity;
    }

然后用JQuery模拟请求,代码如下

        function get2() {
            var id = 200;
            $.ajax({
                type: "get",
                url: './info2/' +id,
                success: function(role,status,xhr) {
                    // 获取响应头
                    var success = xhr.getResponseHeader("success");
                    // 通过响应头判定获取失败
                    if ("false" == success) { 
                        // 响应错误信息
                        var message = xhr.getResponseHeader("message");
                        alert(message);
                    } else { // 获取结果成功
                        alert(role.roleName)
                    }
                    
                }
            });
        }
        get2();

在执行到对应的控制器方法后,会的到如下信息
在这里插入图片描述
这样前端也可以更好的利用这些资源,但会增加一些代码量,如果有很好的开发规范,也不算什么大问题

RestTemplate

在当今的架构中,微服务已经是主流,而在微服务中,会将一个很大的系统拆分为多个子系统,REST风格请求是系统之间交互的基本方式,通常情况下各子系统或者各服务会以HTTP的REST风格暴露服务接口,各子系统或服务之间通过RestTemplate进行服务调用完成交互的目的,而SpringMVC提供的RestTemplate的作用是简化调用过程的,实例代码如下

package com.springrest.rest.client;


import java.util.HashMap;
import java.util.Map;

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import com.springrest.pojo.Role;
import com.springrest.vo.ResultMessage;

public class RestTemplateDemo {

    // 创建RestTemplate
    private static RestTemplate restTemplate = new RestTemplate();

    // 基础HTTP请求路径
    private static String baseUrl = "http://localhost:8080/springrest_war/mvc";

    public static void main(String[] args) {
//		testGet();
//		testPost();
//		testDelet();
//		testPut();
//		testEnity();
        exchange();
    }

    /**
     * 通过HTTP GET请求获取角色信息。
     * 此方法演示了如何使用RestTemplate从指定的URL获取特定角色的信息。
     * 它通过将URL模板与特定ID结合使用,构建一个请求URL,并期望返回一个Role对象。
     * @see Role 用于表示角色的数据类。
     * @see RestTemplate 用于执行RESTful请求的Spring框架类。
     */
    private static void testGet() {
        // 构建请求URL,其中{id}是一个占位符,用于动态插入角色ID。
        String url = baseUrl + "/role2/info/{id}";
        // 使用RestTemplate的getForObject方法,从指定URL获取Role对象。
        // 1L是参数,代替URL中的{id}占位符
        Role role = restTemplate.getForObject(url, Role.class, 1L);
        // 输出角色名称。
        System.out.println(role.getRoleName());
    }


    /**
     * 使用RESTful API进行角色信息的创建测试。
     * 通过POST请求向指定URL发送角色信息,期望返回操作结果的消息。
     * 这个方法展示了如何使用Spring的RestTemplate类进行HTTP请求,以及如何处理响应。
     */
    private static void testPost() {
        // 初始化HTTP请求头,指定请求内容类型为JSON。
        HttpHeaders headers = new HttpHeaders();
        // 设置请求内容为JSON类型
        headers.setContentType(MediaType.APPLICATION_JSON);
        // 创建一个新的角色实例,设置角色的名称和备注。
        Role role = new Role("tmpl_name", "tmpl_note");
        // 构建HTTP请求实体,包含角色信息和请求头,role作为请求体对象
        HttpEntity<Role> request = new HttpEntity<>(role, headers);
        // 拼接URL,指定请求的资源路径。
        String url = baseUrl + "/role2/";
        // 这里使用了RestTemplate的postForObject方法,该方法用于发送POST请求并解析响应,期望返回一个ResultMessage对象,其中包含操作结果的消息。
        ResultMessage resultMsg = restTemplate.postForObject(url, request, ResultMessage.class);
        // 输出操作结果的消息。
        System.out.println(resultMsg.getMessage());
    }


    /**
     * 测试删除操作。
     * 该方法通过发送一个DELETE请求到指定的URL来删除一个角色。
     * 删除操作的特定目标由URL中的{id}占位符和请求参数中的"id"值共同确定。
     * 使用REST模板的delete方法简化了HTTP删除请求的发送过程。
     */
    private static void testDelete() {
        // 初始化请求参数映射,用于传递删除操作的特定ID。
        Map<String, Object> params = new HashMap<>();
        params.put("id", 20);
        // 构建请求的URL,其中包括基础URL和动态部分{id}。
        String url = baseUrl + "/role2/{id}";
        // 发送DELETE请求到指定URL,带上参数。
        restTemplate.delete(url, params);
    }


    /**
     * 测试使用REST模板更新角色信息的方法。
     * 该方法通过构造HTTP请求,包括请求头和请求体,来更新指定角色的信息。
     * 请求体中包含了角色的名称和备注信息,以及要更新的角色ID。
     * 使用REST模板的put方法发送PUT请求到指定的URL,完成角色信息的更新。
     */
    private static void testPut() {
        // 初始化HTTP请求头,指定请求内容类型为JSON
        HttpHeaders headers = new HttpHeaders();
        // 设置请求内容为JSON类型
        headers.setContentType(MediaType.APPLICATION_JSON);
        // 创建一个新的角色对象,设置角色的名称、备注和ID
        Role role = new Role("u_tmpl_name", "u_tmpl_note");
        role.setId(19L);
        // 构造包含请求头和请求体的HTTP请求实体,role作为请求体对象
        HttpEntity<Role> request = new HttpEntity<>(role, headers);
        // 拼接角色信息更新的URL
        String url = baseUrl + "/role2/";
        // 使用REST模板发送PUT请求,更新角色信息
        restTemplate.put(url, request);
    }


    /**
     * 测试通过RESTful API获取实体对象。
     * 该方法通过发送HTTP GET请求到指定URL来获取一个Role实体。如果请求成功,它将打印出角色名称;
     * 如果请求失败,它将打印出错误消息。
     * 使用RestTemplate类来发送HTTP请求,并通过ResponseEntity来处理响应,包括响应体和响应头信息。
     */
    private static void testEnity() {
        // 构建请求URL,其中{id}是一个占位符,用于动态插入角色ID。
        String url = baseUrl + "/role2/info2/{id}";
        // 指定要查询的角色ID。
        Long id = 1L;
        // 发送GET请求并获取响应实体。
        ResponseEntity<Role> roleEntity = restTemplate.getForEntity(url, Role.class, id);
        // 从响应头中获取"success"字段,判断请求是否成功。
        String success = roleEntity.getHeaders().get("success").get(0);
        // 将"success"字段的值转换为boolean类型。
        boolean flag = Boolean.parseBoolean(success);
        // 如果请求成功。
        if (flag) { // 获取成功
            // 提取响应体中的Role对象。
            Role role = roleEntity.getBody();
            // 打印角色名称。
            System.out.println(role.getRoleName());
        } else {
            // 如果请求失败,从响应头中获取"message"字段,获取后端响应头信息
            String message = roleEntity.getHeaders().get("message").get(0);
            System.out.print(message);
        }
    }


    /**
     * 调用API交换角色信息。
     * 该方法通过PUT请求更新指定角色的信息。它构造请求URL、请求头和请求体,然后发送请求。
     * 请求体是一个Role对象,包含要更新的角色名称和备注信息。
     * 方法打印出API响应中的消息部分。
     */
    private static void exchange() {
        // 构造请求的URL
        String url = baseUrl + "/role2/";
        // 初始化HTTP请求头,指定请求内容类型为JSON
        HttpHeaders headers = new HttpHeaders();
        // 设置请求内容为JSON类型
        headers.setContentType(MediaType.APPLICATION_JSON);
        // 创建一个新的角色对象,设置角色的名称、备注和ID
        Role role = new Role("u_tmpl_name", "u_tmpl_note");
        role.setId(19L);
        // 将角色对象和请求头封装成一个HttpEntity对象,作为PUT请求的请求体
        HttpEntity<Role> request = new HttpEntity<>(role, headers);
        // 发送PUT请求,更新角色信息,并接收响应
        // 使用更为底层的exchange方法执行请求
        ResponseEntity<ResultMessage> result = restTemplate.exchange(url, HttpMethod.PUT, request, ResultMessage.class);
        // 打印响应体中的消息字段
        System.out.println(result.getBody().getMessage());
    }

}

RestTemplate的PUT和DELETE请求都不返回结果,因此无法鉴别服务调用的成败,但大部分调用都需要鉴别请求结果,正如前边的代码将HTTP请求的结果返回ResponseEntity<T>一样,对此RestTemplate也给予了支持,正如private static void testEnity() 方法中所写,此外RestTemplate还提供了一个底层的exchange方法,通过这个方法也可以获取PUT请求返回的ResponseEntity<T>,正如代码中private static void exchange()方法所写

exchange方法还支持GET,HEAD,POST,PATCH,DELETE,OPTIONS,TRACE;,具体情况可以直接看HttpMethod的源码