Spring 与 Restful 整合才是微架构的核心,虽然在整个 SpringBoot(SpringCloud)之中提供有大量的
服务方便整合,但是这些 整合都不如 Rest 重要,因为 Rest 是整个在微架构之中进行通讯的基础模式。
那么对于 Rest 首先必须对其有一个最为核心的解释: 利用 JSON 实现数据的交互处理。而且 Spring 里面
提供有一个非常强大的 RestTemplate 操作模版,利用此模版可以非常轻松的实现 Rest 的 JSON 数据与
各种对象间的自动转换。
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.study.microboot.vo.Member;
@RestController
public class MemberController {
@RequestMapping(value = "/member/add")
public Object add(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】定义程序启动类,启动服务,而后测试当前服务是否可用:
package cn.study.microboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
@SpringBootApplication
public class RestfulProviderStartSpringBootMain extends SpringBootServletInitializer {
// 必须继承指定的父类
@Override
protected SpringApplicationBuilder configure(
SpringApplicationBuilder builder) {
return builder.sources(RestfulProviderStartSpringBootMain.class) ;
}
public static void main(String[] args) throws Exception {
SpringApplication.run(RestfulProviderStartSpringBootMain.class, args);
}
}
· 获取对象信息:
http://localhost:8080/member/get?mid=110
{"mid":110,"name":"studyjava - 110","age":16,"salary":99999.99,"birthday":1551841349990}
· 增加对象信息:
http://localhost:8080/member/add?mid=110&name=smith&age=12
true
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
package cn.study.microboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ConsumerStartSpringBootMain{ // 必须继承指定的父类
public static void main(String[] args) throws Exception {
SpringApplication.run(ConsumerStartSpringBootMain.class, args);
}
}
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 = ConsumerStartSpringBootMain.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.vo.Member;
@Controller
public class MemberConsumerController extends AbstractBaseController{
@Resource
private RestTemplate restTemplate;
@RequestMapping(value = "/consumer/get", method = RequestMethod.GET)
@ResponseBody
public Member getMember(long mid,Model model) {
Member member = this.restTemplate.getForObject(
"http://localhost:8080/member/get/" + mid, Member.class);
model.addAttribute("member", member) ;
return member;
}
@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;
}
}
package cn.study.microboot.controller;
import java.text.SimpleDateFormat;
import java.util.Locale;
import javax.annotation.Resource;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.context.MessageSource;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
public abstract class AbstractBaseController {
// 自动注入此资源的对象
@Resource
private MessageSource messageSource;
public String getMessage(String key, String ...args) {
return this.messageSource.getMessage(key, args,Locale.getDefault());
}
@InitBinder
public void initBinder(WebDataBinder binder) { // 在本程序里面需要针对于日期格式进行处理
// 首先建立一个可以将字符串转换为日期的工具程序类
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd") ;
// 明确的描述此时需要注册一个日期格式的转化处理程序类
binder.registerCustomEditor(java.util.Date.class, new CustomDateEditor(sdf, true));
}
}
11、 【microboot-restful-consumer】访问消费端服务:
· 测试信息追加操作:
http://localhost/consumer/add?mid=120&name=ALLEN&age=10&salary=9.9&birthday=2000-10-10
true
测试信息获得操作:
http://localhost/consumer/get?mid=120
{"mid":120,"name":"studyjava - 120","age":16,"salary":99999.99,"birthday":1551844293999}
· Rest 服务的生产者只是按照自己返回的内容进行 JSON 数据的输出;
· 消费者利用 RestTemplate 进行 JSON 数据的获得以及自动向指定类型的对象进行转换;
· 为了达到这种转换的操作标准,特意准备了一个 api 项目保存公共的 VO 类型。
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.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;
}
}
<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>microboot-restful-consumer</groupId>
<artifactId>microboot-restful-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>
<thymeleaf-layout-dialect.version>2.3.0</thymeleaf-layout-dialect.version>
<!-- 布局功能的支持程序 thymeleaf3主程序 layout2以上版本 -->
<!-- thymeleaf2 layout1 -->
<thymeleaf-layout-dialect.version>2.2.2</thymeleaf-layout-dialect.version>
</properties>
<dependencies>
<dependency>
<!-- 引入web模块 -->
<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>
<dependency>
<groupId>microboot-restful-api</groupId>
<artifactId>microboot-restful-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
</project>
member_show.html
<!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>
http://localhost/consumer/get?mid=120
用户编号:120
用户姓名:studyjava - 120
用户年龄:16
用户工资:99999.99
用户生日:Wed Mar 06 12:01:14 CST 2019
而对于 Rest 服务的更多考虑,应该包含如下几点:
· 既然服务的提供者只能够被消费者所访问,证明其不可能被所有用户操作,一定需要安全认证;
· 服务端一定要进行指定业务层和数据层的编写,也就是说每一个服务端都应该具备有一个自己的服务器信息;
· 在服务端访问非常繁忙的时候,消费端执行时有可能需要进行短期的熔断处理;
· 服务端既然是一个独立的组件,那么就必须考虑负载均衡问题;
· 消费端进行服务端的调用操作,如果所有的调用都写上明确的调用地址,太麻烦了;
· 消费端进行处理的时候如果都是自己来直接采用 RestTemplate 做处理,代码结构太差了,因为毕竟服务端
是远程业务端,远程业务端最好的调用应该就用接口完成。
当你现在建立一些公共的 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 里面提供有一个比较有意思的
功能,就是说用户可以通过 远程的控制追加日志的显示级别的操作。
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 add() { // 表示当前的配置可以直接将参数变为VO对象
this.log.info("【*** INFO ***】 日志输出");
this.log.info("【*** ERROR ***】 日志输出");
return true;
}
}
http://localhost:8080/test
true
2、 如果现在希望只进行 error 级别的日志输出,则修改 application.yml 配置文件:
application.properties
logging:
level:
cn.study.microboot.controller: ERROR
3、 现在希望在以后程序运行的时候这个日志的输出级别可以动态的做一个扩充,所以这个时候要想达到这样的
目的就可以必须 进行安全的关闭操作,修改 pom.xml和application.properties 配置文件:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management.security.enabled
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;
}
@Override
public String toString() {
return "LogInfo [level=" + level + "]";
}
}
5、 随后编写一个测试类修改日志级别:
package cn.study.microboot.test;
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.ProviderStartSpringBootMain;
import cn.study.microboot.vo.LogInfo;
@SpringBootTest(classes = ProviderStartSpringBootMain.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 给出的一个简单支持,但是在实际之中日志的处理更多的情况下不会取消
安全配置,所以这种日 志的配置也不是全部可以使用。