总览
什么是REST?
REST(代表状态转移)是Web构建的体系结构样式,已成为用于Web应用程序的标准软件设计模式 。 代表性国家转移一词最早由REST的发起人,HTTP规范的主要作者之一Roy Fielding在其博士论文中使用 。
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 –项目结构
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
@GeneratedValue
private Long id;
@JsonIgnore
public 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
@ManyToOne
private 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
@GeneratedValue
private 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;
@Autowired
UsernamesRestController(UsernamesRepository usernamesRepository, AccountRepository accountRepository){
this.usernamesRepository = usernamesRepository;
this.accountRepository = accountRepository;
}
@GetMapping
Collection<Usernames> readUsernames (@PathVariable String userId){
this.validateUser(userId);
return this.usernamesRepository.findByAccountUsername(userId);
}
@PostMapping
ResponseEntity<?> 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);
}
@Bean
CommandLineRunner 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