Статьи

Сборка пружинной загрузки RESTful Service + привод пружинной загрузки

обзор

Что такое ОТДЫХ?

REST (REpresentational State Transfer) — это архитектурный стиль, на котором построена сеть, и он стал стандартным шаблоном проектирования программного обеспечения, используемым для веб-приложений. Термин Передача репрезентативного состояния был впервые использован Роем Филдингом, создателем REST и одним из основных авторов спецификации HTTP, в своей докторской диссертации .

Есть много хороших ссылок на 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"?>
    <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;
 
@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(){
         
    }
     
}

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;
 
@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

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;
 
@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.

Мы не настроили никакой безопасности и роли пользователей, так как это всего лишь пример. Поэтому для простоты демонстрации я отключу требование безопасности. В противном случае мы получим «неавторизованную» ошибку на данный момент, как показано ниже.

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, являются их собственными.