обзор
Что такое ОТДЫХ?
REST (REpresentational State Transfer) — это архитектурный стиль, на котором построена сеть, и он стал стандартным шаблоном проектирования программного обеспечения, используемым для веб-приложений. Термин Передача репрезентативного состояния был впервые использован Роем Филдингом, создателем REST и одним из основных авторов спецификации HTTP, в своей докторской диссертации .
Есть много хороших ссылок на REST, включая:
- Википедия
- Модель зрелости Ричардсона
- Как Райан Томайко объяснил REST своей жене
- API REST Роя Филдинга должны управляться с гипертекстом
- Stackoverflow SOAP против REST
Этот учебник основан на Building Rest Services со Spring, и в начале учебника также есть хороший обзор REST.
Что такое привод пружинной загрузки?
Spring Boot Actuator является подпроектом Spring Boot. Он добавляет к вашему приложению несколько сервисов производственного уровня с минимальными усилиями с вашей стороны.
Определение привода
Привод — это компонент, отвечающий за перемещение или управление системой.
Термин исполнительный механизм не ограничен Spring Boot; однако, это наше внимание здесь.
После настройки Actuator в приложении Spring Boot он позволяет взаимодействовать и отслеживать приложение, вызывая различные независимые от технологии конечные точки, предоставляемые Spring Boot Actuator, такие как состояние приложения , компоненты, средства ведения журнала, сопоставления и трассировка . Больше перечислено в этом весеннем документе .
0 — Spring Boot RESTful Web-сервис с примером приложения Actuator
Мы создадим пример веб-приложения RESTful с Spring Boot и Actuator.
Приложение будет «трекер имени пользователя». В этом приложении человек имеет одну учетную запись, и его учетная запись может иметь много имен пользователей.
Посмотреть и скачать код с Github
1 — Структура проекта
Как обычно, у нас есть нормальная структура проекта Maven.

2 — Зависимости проекта
Помимо типичных зависимостей Spring Boot, мы включили HSQLDB для нашей встроенной базы данных и spring-boot-starter-activator для всех зависимостей Actuator.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
<?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" <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.
Подойдите к своему терминалу и следуйте инструкциям.
|
1
2
|
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 генерирует вам ответ JSON из своей конечной точки / error.
|
1
2
|
mikes-MacBook-Air:Spring-Boot-Actuator-Example mike$ curl localhost:8080/health{"status":"UP"} |
Конечная точка Actuator / Health сообщит вам, работает ли ваше приложение.
4 — Модель
Теперь давайте определим поля моделей для нашего приложения для отслеживания имен пользователей.
- Как уже упоминалось, человек имеет одну учетную запись и может иметь много имен пользователей. Таким образом, мы сопоставляем Setwith аннотацию @OneToMany
- Модель имени пользователя будет иметь пароль и имя пользователя, конечно
- Нашей модели понадобится идентификатор, и мы сделаем его автоматически
- Мы делаем конструкцию класса, чтобы определить учетную запись, может быть сделано с именем пользователя и паролем. Из-за этого пользовательского конструктора нам также нужно сделать конструктор по умолчанию без параметров.
Account.java
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
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;@Entitypublic 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(){ } } |
Usernames.java
- Поскольку существует множество учетных записей для многих имен пользователей, верно и обратное: существует много имен пользователей для одной учетной записи. Поэтому мы сопоставляем аккаунт с аннотацией @ManyToOne
- Для отслеживания имени пользователя нам нужны: URL и имя пользователя
- Мы еще раз определяем автоматически сгенерированный идентификатор
- Мы определяем пользовательский конструктор класса, который требует параметр account, url и username. Еще раз нам нужно определить метод конструктора по умолчанию, чтобы избежать появления ошибки.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
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;@Entitypublic 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
|
01
02
03
04
05
06
07
08
09
10
11
12
|
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);} |
UsernamesRepository.java
|
01
02
03
04
05
06
07
08
09
10
11
12
13
|
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.
- Мы аннотируем наш контроллер с помощью @RestController, а не @Controller. Как указано в javadoc , это «удобная аннотация, которая сама аннотируется с помощью @Controller и @ResponseBody».
- Мы объявляем переменные для наших UsernamesRepository и AccountRepository и делаем их окончательными, потому что мы хотим, чтобы значение было назначено только один раз. Мы аннотируем их как @Autowired над конструктором класса UsernamesRestController.
- {userId} и {usernamesId} являются переменными пути . Это означает, что эти значения представлены в URL. Это будет показано в нашем демо.
- Методы контроллера возвращают POJO (простые старые объекты Java) . Spring Boot автоматически связывает HttpMessageConverter для преобразования этих общих объектов в JSON.
UsernamesRestController.java
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
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
Здесь мы определяем пользовательское исключение, которое мы использовали в нашем классе контроллера, чтобы объяснить, что пользователь не может быть найден.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
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 для создания учетных записей и вставки имен пользователей. Каждая учетная запись будет иметь два имени пользователя.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
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;@SpringBootApplicationpublic 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, usernamesRepository.save(new Usernames(account, }); }} |
8 — Конфигурация
В документации Spring указано:
По умолчанию все конфиденциальные конечные точки HTTP защищены так, что только пользователи, имеющие роль ACTUATOR, могут получить к ним доступ. Безопасность обеспечивается с помощью стандартного метода HttpServletRequest.isUserInRole.
Мы не настроили никакой безопасности и роли пользователей, так как это всего лишь пример. Поэтому для простоты демонстрации я отключу требование безопасности. В противном случае мы получим «неавторизованную» ошибку на данный момент, как показано ниже.
|
1
|
{"timestamp":1505321635068,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource.","path":"/beans"} |
application.properties
Добавьте это в ваш application.properties, чтобы отключить необходимость аутентификации.
|
1
|
management.security.enabled=false |
9 — Демо
Чтобы получить ответы с сервера, вы можете посетить URL-адрес в браузере или использовать curl. Для моей демонстрации я использую curl.
REST-запросы для данных в репозиториях
Запрос имени пользователя, принадлежащего учетной записи jerrysmith.
|
1
2
|
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
|
1
2
|
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"}] |
Актуатор запросов
Ответ на этот запрос урезан, потому что он действительно очень длинный.
Фасоль
|
1
2
3
|
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",[...] |
метрика
|
1
2
|
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 — Заключение
Поздравляем, вы создали веб-сервис RESTful, который можно отслеживать с помощью Actuator. REST — действительно самый органичный способ общения между различными клиентами, потому что он работает благодаря HTTP.
Исходный код есть на Github
| Опубликовано на Java Code Geeks с разрешения Майкла Гуда, партнера нашей программы JCG . См. Оригинальную статью здесь: Сборка Spring Boot RESTful Service + Spring Boot Actuator
Мнения, высказанные участниками Java Code Geeks, являются их собственными. |