SpringBoot系列十一:SpringBoot整合Restful架构(使用 RestTemplate 模版实现 Rest 服务调用、Swagger 集成、动态修改日志级别)
声明:本文来源于MLDN培训视频的课堂笔记,写在这里只是为了方便查阅。
1、概念:SpringBoot整合Restful架构
2、背景
Spring 与 Restful 整合才是微架构的核心,虽然在整个 SpringBoot(SpringCloud)之中提供有大量的服务方便整合,但是这些 整合都不如 Rest 重要,因为 Rest 是整个在微架构之中进行通讯的基础模式。那么对于 Rest 首先必须对其有一个最为核心的解释: 利用 JSON 实现数据的交互处理。而且 Spring 里面提供有一个非常强大的 RestTemplate 操作模版,利用此模版可以非常轻松的实现 Rest 的 JSON 数据与各种对象间的自动转换。
在默认状态下 Spring 里面针对于 Rest 的处理使用的都是 jackson 开发包支持包。
2.1、使用 RestTemplate 模版实现 Rest 服务调用
由于 Rest 属于分布式的项目开发环境,所以本次进行项目建立的时候一共建立有三个子模块:
· microboot-restful-api:作为公共的类定义,例如:可以将所有的 VO 类定义在此项目之中;
· microboot-restful-provider:作为服务提供者,这次的服务提供者提供两个服务(获得对象、增加对象);
· micorboot-restful-consumer:作为服务的消费者,消费者就是利用 RestTemplate 实现 Rest 服务的调用以及对象转换
1、 【microboot-restful-api】建立一个公共的 VO 类对象:
package cn.study.microboot.vo; import java.io.Serializable; import java.util.Date; @SuppressWarnings("serial") public class Member implements Serializable { private Long mid ; private String name ; private Integer age ; private Double salary ; private Date birthday ; public Long getMid() { return mid; } public void setMid(Long mid) { this.mid = mid; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Double getSalary() { return salary; } public void setSalary(Double salary) { this.salary = salary; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override public String toString() { return "Member [mid=" + mid + ", name=" + name + ", age=" + age + ", salary=" + salary + ", birthday=" + birthday + "]"; } }
2、 【microboot-restful-provider】修改 pom.xml 配置文件,去引用 microboot-restful-api模块:
<dependency> <groupId>cn.mldn</groupId> <artifactId>microboot-restful-api</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
3、 【microboot-restful-provider】建立一个控制器实现 Rest 服务的处理:
package cn.study.microboot.controller; import java.util.Date; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import cn.study.microboot.vo.Member; @RestController public class MemberController { @RequestMapping(value = "/member/add") public Object add(@RequestBody Member member) { // 表示当前的配置可以直接将参数变为VO对象 System.err.println("【MemberController.add()接收对象】" + member); return true; } @RequestMapping(value = "/member/get") public Member get(long mid) { Member vo = new Member(); vo.setMid(mid); vo.setName("studyjava - " + mid); vo.setBirthday(new Date()); vo.setSalary(99999.99); vo.setAge(16); return vo; } }
4、 【microboot-restful-provider】定义程序启动类,启动服务,而后测试当前服务是否可用:
· 获取对象信息:http://localhost:8080/member/get?mid=110;
· 增加对象信息:http://localhost:8080/member/add?mid=110&name=smith&age=12;
5、 【microboot-restful-consumer】如果要进行 Rest 操作,那么一定要注意使用一个 RestTemplate 模版完成处理,所以首先要建立一个程序配置类,进行 RestTemplate 模版对象创建:
package cn.study.microboot.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class RestConfig { @Bean public RestTemplate getRestTemplate() { return new RestTemplate() ; } }
6、 【microboot-restful-consumer】修改 application.yml 配置端口:
server:
port: 80
7、 【microboot-restful-consumer】编写测试程序类测试远程 Rest 服务是否可用?
package cn.study.microboot; import javax.annotation.Resource; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.web.client.RestTemplate; import cn.study.microboot.vo.Member; @SpringBootTest(classes = StartSpringBootMain.class) @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration public class TestMemberRestful { @Resource private RestTemplate restTemplate; @Test public void testAdd() { Boolean flag = this.restTemplate.getForObject( "http://localhost:8080/member/add?mid=110&name=SMITH&age=10", Boolean.class); System.out.println("【ConsumerTest.add()】" + flag); } @Test public void testGet() { // 通过远程的Rest服务中的信息将其自动转换为Member对象实例 Member member = this.restTemplate.getForObject( "http://localhost:8080/member/get?mid=110", Member.class); System.out.println("【ConsumerTest.get()】" + member); } }
8、 【microboot-restful-provider】为了更方便的进行内容的传输,此时 Rest 服务的提供方一定要做出一点点修改:
package cn.study.microboot.controller; import java.util.Date; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import cn.study.microboot.vo.Member; @RestController public class MemberController { @RequestMapping(value = "/member/add",method=RequestMethod.POST) public Object add(@RequestBody Member member) { // 表示当前的配置可以直接将参数变为VO对象 System.err.println("【MemberController.add()接收对象】" + member); return true; } @RequestMapping(value = "/member/get/{mid}",method=RequestMethod.GET) public Member get(@PathVariable("mid") long mid) { Member vo = new Member(); vo.setMid(mid); vo.setName("studyjava - " + mid); vo.setBirthday(new Date()); vo.setSalary(99999.99); vo.setAge(16); return vo; } }
9、 【microboot-restful-consumer】编写一个调用控制器进行处理;
package cn.study.microboot.controller; import javax.annotation.Resource; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.client.RestTemplate; import cn.study.microboot.util.controller.AbstractBaseController; import cn.study.microboot.vo.Member; @Controller public class MemberConsumerController extends AbstractBaseController { @Resource private RestTemplate restTemplate; @RequestMapping(value = "/consumer/get", method = RequestMethod.GET) public String getMember(long mid,Model model) { Member member = this.restTemplate.getForObject( "http://localhost:8080/member/get/" + mid, Member.class); model.addAttribute("member", member) ; return "member_show"; } @RequestMapping(value = "/consumer/add", method = RequestMethod.GET) @ResponseBody public Object addMember(Member member) { Boolean flag = this.restTemplate.postForObject( "http://localhost:8080/member/add", member, Boolean.class); return flag; } }
10、 【microboot-restful-consumer】为了方便进行接收数据的演示,建立一个普通的 thymeleaf 页面:
<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>SpringBoot模版渲染</title> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" /> </head> <body> <p th:text="'用户编号:' + ${member.mid}"/> <p th:text="'用户姓名:' + ${member.name}"/> <p th:text="'用户年龄:' + ${member.age}"/> <p th:text="'用户工资:' + ${member.salary}"/> <p th:text="'用户生日:' + ${member.birthday}"/> </body> </html>
11、 【microboot-restful-consumer】访问消费端服务:
· 测试信息获得操作:http://localhost/consumer/get?mid=120;
· 测试信息追加操作:http://localhost/consumer/add?mid=120&name=ALLEN&age=10&salary=9.9&birthday=2000-10-10;
12、 现在在整个的项目处理之中会发现以下的几个特点:
· Rest 服务的生产者只是按照自己返回的内容进行 JSON 数据的输出;
· 消费者利用 RestTemplate 进行 JSON 数据的获得以及自动向指定类型的对象进行转换;
· 为了达到这种转换的操作标准,特意准备了一个 api 项目保存公共的 VO 类型。
而对于 Rest 服务的更多考虑,应该包含如下几点:
· 既然服务的提供者只能够被消费者所访问,证明其不可能被所有用户操作,一定需要安全认证;
· 服务端一定要进行指定业务层和数据层的编写,也就是说每一个服务端都应该具备有一个自己的服务器信息;
· 在服务端访问非常繁忙的时候,消费端执行时有可能需要进行短期的熔断处理;
· 服务端既然是一个独立的组件,那么就必须考虑负载均衡问题;
· 消费端进行服务端的调用操作,如果所有的调用都写上明确的调用地址,太麻烦了;
· 消费端进行处理的时候如果都是自己来直接采用 RestTemplate 做处理,代码结构太差了,因为毕竟服务端是远程业务端,远程业务端最好的调用应该就用接口完成。
2.2、Swagger 集成
当你现在建立一些公共的 Rest 服务的时候就可以利用 Swagger 进行所有 Rest 服务的描述了。也就是说它提供的只是一个说明 工具的概念。
1、 如果要想去使用 swagger 说明操作,则必须引入相应的依赖支持包:
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.7.0</version> </dependency>
2、 定义一个进行 Swagger2 的配置程序类:
package cn.study.microboot.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration @EnableSwagger2 public class Swagger2Config { @Bean public Docket getDocket() { // 此类主要是整个的Swagger配置项,利用这个类需要来指派扫描包 return new Docket(DocumentationType.SWAGGER_2) .apiInfo(this.getApiInfo()).select() .apis(RequestHandlerSelectors .basePackage("cn.study.microboot.controller")) .paths(PathSelectors.any()).build(); // 设置文档的显示类型 } private ApiInfo getApiInfo() { return new ApiInfoBuilder().title("SpringBoot中使用Swagger构建项目说明信息") .description("接口描述信息") .termsOfServiceUrl("http://www.study.cn").contact("study——springbooot") .license("small lee").version("1.0").build(); } }
3、 修改 MemberController 程序类,追加相关注解信息:
package cn.study.microboot.controller; import java.util.Date; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import cn.study.microboot.vo.Member; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; @RestController public class MemberController { @ApiOperation(value = "实现人员信息的添加处理", notes = "添加") @ApiImplicitParams({ @ApiImplicitParam(name = "member", value = "用户描述的详细实体信息", required = true, dataType = "MemberClass")}) @RequestMapping(value = "/member/add", method = RequestMethod.POST) public Object add(@RequestBody Member member) { // 表示当前的配置可以直接将参数变为VO对象 System.err.println("【MemberController.add()接收对象】" + member); return true; } @ApiOperation(value = "获取指定编号的人员信息", notes = "只需要设置mid的信息就可以获取Member的完整内容") @ApiImplicitParams({ @ApiImplicitParam(name = "mid", value = "用户编号", required = true, dataType = "String")}) @RequestMapping(value = "/member/get/{mid}", method = RequestMethod.GET) public Member get(@PathVariable("mid") long mid) { Member vo = new Member(); vo.setMid(mid); vo.setName("studyjava - " + mid); vo.setBirthday(new Date()); vo.setSalary(99999.99); vo.setAge(16); return vo; } }
4、 正常进行程序的启动配置处理,而后打开浏览器进入到界面:http://localhost:8080/swagger-ui.html;
2.3、动态修改日志级别
在项目开发之中日志可以使用 info()、error()进行输出在 SpringBoot 里面提供有一个比较有意思的功能,就是说用户可以通过 远程的控制追加日志的显示级别的操作。
1、 定义一个简单的控制器程序:
package cn.study.microboot.controller; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class MessageController { private Logger log = LoggerFactory.getLogger(MessageController.class); @RequestMapping(value = "/test") public Object test() { this.log.info("【*** INFO ***】日志输出"); this.log.error("【*** ERROR ***】日志输出"); return true; } }
2、 如果现在希望只进行 error 级别的日志输出,则修改 application.yml 配置文件:
logging:
level:
cn.mldn.microboot.controller: ERROR
3、 现在希望在以后程序运行的时候这个日志的输出级别可以动态的做一个扩充,所以这个时候要想达到这样的目的就可以必须 进行安全的关闭操作,修改 pom.xml和application.yml 配置文件:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
management:
security :
enabled: false
4、 随后在一个客户端上进行修改,直接利用测试类完成。
package cn.study.microboot.vo; public class LogInfo { private String level; public String getLevel() { return level; } public void setLevel(String level) { this.level = level; } }
5、 随后编写一个测试类修改日志级别:
package cn.study.microboot; import javax.annotation.Resource; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.web.client.RestTemplate; import cn.study.microboot.vo.LogInfo; @SpringBootTest(classes = StartSpringBootMain.class) @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration public class TestLogger { @Resource private RestTemplate restTemplate; @Test public void testLevel() { LogInfo log = new LogInfo(); log.setLevel("INFO"); // 新的日志级别 this.restTemplate.postForLocation("http://localhost:8080/loggers/cn.study.microboot.controller", log); } }
动态修改日志级别是 actuator 给出的一个简单支持,但是在实际之中日志的处理更多的情况下不会取消安全配置,所以这种日 志的配置也不是全部可以使用。