总览
什么是REST?
REST(代表状态转移)是Web构建的体系结构样式,已成为用于Web应用程序的标准软件设计模式 。 代表性国家转移一词最早由REST的发起人,HTTP规范的主要作者之一Roy Fielding在其博士论文中使用 。
REST上有很多很好的参考,包括:
- 维基百科
- 理查森成熟度模型
- Ryan Tomayko如何向他的妻子解释REST
- Roy Fielding的REST API必须由超文本驱动
- Stackoverflow SOAP与REST
本教程基于使用Spring构建Rest Services,并且本教程的开头也对REST进行了很好的概述。
什么是弹簧启动执行器?
Spring Boot Actuator是Spring Boot的子项目。 它为您的应用程序添加了几项生产级服务,而您只需花费很少的精力。
执行器的定义
致动器是负责移动或控制系统的组件。
执行器一词不限于Spring Boot; 但是,这是我们在这里的重点。
在Spring Boot应用程序中配置Actuator之后,它允许您通过调用Spring Boot Actuator公开的不同技术不可知端点(例如应用程序运行状况,Bean,记录器,映射和跟踪)来交互和监视应用程序。 在此Spring文档中列出了更多信息。
0 –带启动器的Spring Boot RESTful Web服务示例应用程序
我们将使用Spring Boot和Actuator构建一个示例RESTful Web应用程序。
该应用程序将是“用户名跟踪器”。 在此应用程序中,一个人拥有一个帐户,他们的帐户可能具有许多用户名。
查看并从 Github 下载代码
1 –项目结构
和往常一样,我们有一个正常的Maven项目结构。
2 –项目依赖性
除了典型的Spring Boot依赖关系之外,我们还为嵌入式数据库提供了HSQLDB,并为所有Actuator依赖关系提供了spring-boot-starter-actuator。
<?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.michaelcgood</groupId><artifactId>michaelcgood-springbootactuator</artifactId><version>0.0.1</version><packaging>jar</packaging><name>Spring-Boot-Actuator-Example</name><description>Michael C Good - Spring Boot Actuator Example</description><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>1.5.6.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></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.hsqldb</groupId><artifactId>hsqldb</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
3 –运行空应用程序
尽管我们没有编写任何代码,但是我们将运行Spring Boot应用程序。
转到您的终端并按照命令进行操作。
mikes-MacBook-Air:Spring-Boot-Actuator-Example mike$ curl localhost:8080
{"timestamp":1505235455245,"status":404,"error":"Not Found","message":"No message available","path":"/"}
我们尚未编写任何代码,除了默认的容器生成HTML错误响应外,Actuator还从/ error端点生成JSON响应。
mikes-MacBook-Air:Spring-Boot-Actuator-Example mike$ curl localhost:8080/health
{"status":"UP"}
执行器/运行状况端点将让您知道您的应用程序是否启动。
4 –模型
现在,为用户名跟踪器应用程序定义模型的字段。
- 如前所述,一个人有一个帐户,可能有许多用户名。 所以我们用@OneToMany注释映射Set
- 用户名模型将具有密码和用户名
- 我们的模型将需要一个ID,并使其自动生成
- 我们进行类构造以定义可以使用用户名和密码创建的帐户。 由于使用了这种自定义构造函数,因此我们还需要设置一个没有参数的默认构造函数。
Account.java
package com.michaelcgood.model;import java.util.HashSet;
import java.util.Set;import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;import com.fasterxml.jackson.annotation.JsonIgnore;@Entity
public class Account {public Set<Usernames> getUsernames() {return usernames;}public void setUsernames(Set<Usernames> usernames) {this.usernames = usernames;}public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}@OneToMany(mappedBy= "account")private Set<Usernames> usernames = new HashSet<>();@Id@GeneratedValueprivate Long id;@JsonIgnorepublic String password;public String username;public Account(String name, String password) {this.username = name;this.password = password;}Account(){}}
用户名.java
- 由于一个帐户可以使用多个用户名,因此情况也相反:一个帐户可以使用多个用户名。 因此,我们使用@ManyToOne注释映射Account
- 要跟踪用户名,我们需要:URL和用户名
- 我们再次定义一个自动生成的ID
- 我们定义了一个自定义类构造函数,该构造函数需要account,url和username参数。 再一次,我们需要定义一个默认的构造函数方法,以避免引发错误。
package com.michaelcgood.model;import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;import com.fasterxml.jackson.annotation.JsonIgnore;@Entity
public class Usernames {@JsonIgnore@ManyToOneprivate Account account;public Account getAccount() {return account;}public void setAccount(Account account) {this.account = account;}public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}@Id@GeneratedValueprivate Long id;public String url;public String username;Usernames(){}public Usernames(Account account, String url, String username){this.url=url;this.account=account;this.username=username;}}
5 –储存库
我们为两个模型创建一个存储库,并使用派生查询创建搜索功能。
AccountRepository.java
package com.michaelcgood.dao;import java.util.Optional;import org.springframework.data.jpa.repository.JpaRepository;import com.michaelcgood.model.Account;public interface AccountRepository extends JpaRepository<Account,Long> {Optional<Account> findByUsername(String username);
}
用户名Repository.java
package com.michaelcgood.dao;import java.util.Collection;import org.springframework.data.jpa.repository.JpaRepository;import com.michaelcgood.model.Usernames;public interface UsernamesRepository extends JpaRepository<Usernames,Long> {Collection<Usernames> findByAccountUsername(String username);}
6 –控制器
在控制器中,我们定义将用于RESTful Web服务的所有映射。
- 我们用@RestController而不是@Controller注释控制器。 如javadoc中所述,它是“一种方便注释,其本身通过@Controller和@ResponseBody进行了注释。”
- 我们声明UsernamesRepository和AccountRepository的变量,并使其成为最终变量,因为我们只希望将值分配一次。 我们通过UsernamesRestController类构造函数将它们注释为@Autowired。
- {userId}和{usernamesId}是路径变量 。 这意味着这些值在URL中提供。 这将在我们的演示中显示。
- Controller方法返回POJO(普通的旧Java对象) 。 Spring Boot自动连接HttpMessageConverter以将这些通用对象转换为JSON。
用户名RestController.java
package com.michaelcgood.controller;import java.net.URI;
import java.util.Collection;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;import com.michaelcgood.dao.AccountRepository;
import com.michaelcgood.dao.UsernamesRepository;
import com.michaelcgood.model.Usernames;@RestController
@RequestMapping("/{userId}/usernames")
public class UsernamesRestController {private final UsernamesRepository usernamesRepository;private final AccountRepository accountRepository;@AutowiredUsernamesRestController(UsernamesRepository usernamesRepository, AccountRepository accountRepository){this.usernamesRepository = usernamesRepository;this.accountRepository = accountRepository;}@GetMappingCollection<Usernames> readUsernames (@PathVariable String userId){this.validateUser(userId);return this.usernamesRepository.findByAccountUsername(userId);}@PostMappingResponseEntity<?> add(@PathVariable String userId,@RequestBody Usernames input){this.validateUser(userId);return this.accountRepository.findByUsername(userId).map(account -> {Usernames result = usernamesRepository.save(new Usernames(account,input.url,input.username));URI url = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(result.getId()).toUri();return ResponseEntity.created(url).build(); }).orElse(ResponseEntity.noContent().build());}@GetMapping(value="{usernamesId}")Usernames readUsername(@PathVariable String userId, @PathVariable Long usernameId){this.validateUser(userId);return this.usernamesRepository.findOne(usernameId);}private void validateUser(String userId){this.accountRepository.findByUsername(userId).orElseThrow(() -> new UserNotFoundException(userId));}}
UserNotFoundException.java
在这里,我们定义了在Controller类中用来解释找不到用户的自定义异常。
package com.michaelcgood.controller;import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {/*** */private static final long serialVersionUID = 7537022054146700535L;public UserNotFoundException(String userId){super("Sorry, we could not find user '" + userId +"'.");
}}
7 – @SpringBootApplication
我们使用CommandLineRunner创建帐户并插入用户名。 每个帐户都有两个用户名。
package com.michaelcgood;import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;import com.michaelcgood.dao.AccountRepository;
import com.michaelcgood.dao.UsernamesRepository;
import com.michaelcgood.model.Account;
import com.michaelcgood.model.Usernames;import java.util.Arrays;@SpringBootApplication
public class SpringBootActuatorExampleApplication {public static void main(String[] args) {SpringApplication.run(SpringBootActuatorExampleApplication.class, args);}@BeanCommandLineRunner init(AccountRepository accountRepository,UsernamesRepository usernamesRepository) {return (evt) -> Arrays.asList("ricksanchez,mortysmith,bethsmith,jerrysmith,summersmith,birdperson,squanchy,picklerick".split(",")).forEach(a -> {Account account = accountRepository.save(new Account(a,"password"));usernamesRepository.save(new Usernames(account,"http://example.com/login", a +"1"));usernamesRepository.save(new Usernames(account,"http://example2.com/login", "the_"+a));});}
}
8 –配置
在Spring文档中有说明 :
默认情况下,所有敏感的HTTP端点都是安全的,因此只有具有ACTUATOR角色的用户才能访问它们。 使用标准的HttpServletRequest.isUserInRole方法可以增强安全性。
我们尚未设置任何安全性和用户角色,因为这只是一个示例。 因此,为了便于演示,我将禁用安全性要求。 否则,我们将立即收到一个“未经授权”的错误,如下图所示。
{"timestamp":1505321635068,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource.","path":"/beans"}
application.properties
将此添加到application.properties以禁用身份验证。
management.security.enabled=false
9 –演示
要从服务器检索响应,您可以在浏览器中访问URL或使用curl。 对于我的演示,我正在使用curl。
REST查询存储库中的数据
查询属于帐户jerrysmith的用户名。
mikes-MacBook-Air:Spring-Boot-Actuator-Example mike$ curl localhost:8080/jerrysmith/usernames
[{"id":7,"url":"http://example.com/login","username":"jerrysmith1"},{"id":8,"url":"http://example2.com/login","username":"the_jerrysmith"}]
查询属于帐户picklerick的用户名
mikes-MacBook-Air:Spring-Boot-Actuator-Example mike$ curl localhost:8080/picklerick/usernames
[{"id":15,"url":"http://example.com/login","username":"picklerick1"},{"id":16,"url":"http://example2.com/login","username":"the_picklerick"}]
执行器查询
该查询的响应非常长,因此被截断了。
豆子
mikes-MacBook-Air:Spring-Boot-Actuator-Example mike$ curl localhost:8080/beans
[{"context":"application","parent":null,"beans":[{"bean":"springBootActuatorExampleApplication","aliases":[],"scope":"singleton","type":"com.michaelcgood.SpringBootActuatorExampleApplication$$EnhancerBySpringCGLIB$$509f4984","resource":"null","dependencies":[]},{"bean":"org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory","aliases":[],"scope":"singleton","type":"org.springframework.core.type.classreading.CachingMetadataReaderFactory","resource":"null","dependencies":[]},{"bean":"usernamesRestController","aliases":[],"scope":"singleton","type":"com.michaelcgood.controller.UsernamesRestController","resource":"file [/Users/mike/javaSTS/Spring-Boot-Actuator-Example/target/classes/com/michaelcgood/controller/UsernamesRestController.class]","dependencies":["usernamesRepository","accountRepository"]},{"bean":"init","aliases":[],"scope":"singleton","type":"com.michaelcgood.SpringBootActuatorExampleApplication$$Lambda$11/889398176","resource":"com.michaelcgood.SpringBootActuatorExampleApplication",
[...]
指标
mikes-MacBook-Air:Spring-Boot-Actuator-Example mike$ curl localhost:8080/metrics
{"mem":350557,"mem.free":208275,"processors":4,"instance.uptime":213550,"uptime":240641,"systemload.average":1.6552734375,"heap.committed":277504,"heap.init":131072,"heap.used":69228,"heap":1864192,"nonheap.committed":74624,"nonheap.init":2496,"nonheap.used":73062,"nonheap":0,"threads.peak":27,"threads.daemon":23,"threads.totalStarted":30,"threads":25,"classes":9791,"classes.loaded":9791,"classes.unloaded":0,"gc.ps_scavenge.count":11,"gc.ps_scavenge.time":139,"gc.ps_marksweep.count":2,"gc.ps_marksweep.time":148,"httpsessions.max":-1,"httpsessions.active":0,"datasource.primary.active":0,"datasource.primary.usage":0.0,"gauge.response.beans":14.0,"gauge.response.info":13.0,"counter.status.200.beans":2,"counter.status.200.info":1}
9 –结论
恭喜,您已经创建了一个可以用Actuator监视的RESTful Web服务。 REST实际上是不同客户端进行通信的最有机的方式,因为它由于HTTP而起作用。
源代码在 Github上
翻译自: https://www.javacodegeeks.com/2017/10/building-spring-boot-restful-service-spring-boot-actuator.html