W poprzednim wpisie #3 Spring Boot 2 – Actuator poznaliśmy narzędzie Actuator. Jest to mechanizm, który dostarcza metryki oraz dodatkowe informacje na temat aplikacji. Out-of-the-box Actuator daje nam między innymi możliwość sprawdzenia metryk wirtualnej maszyny Javy czy statusu naszej aplikacji. Jednakże, czasem potrzebujemy dodać własny endpoint dostarczający dodatkowe informacje o naszej aplikacji. W tym wpisie opowiemy sobie, jak dodać taki własny endpoint.

Spring Boot 2

Spring Boot 2 nieznacznie zmodyfikował sposób dodawania nowych endpointów. W poprzedniej wersji frameworku należało zaimplementować odpowiedni interfejs. W najnowszej wersji do dodania nowego endpointu wykorzystywana jest adnotacja @Endpoint (lub jej warianty, takie jak @WebEndpoint):

1
2
3
4
5
6
7
@Component
@Endpoint(id = "integrations")
public class IntegrationsEndpoint {

// logic

}

W parametrze id określamy, pod jakim adresem dostępny będzie nowy endpoint. W naszym przypadku jest to adres /actuator/integrations. Dodatkowo, każdy dodany przez nas endpoint jest domyślnie włączony. Możemy to zmienić, używając klucza enableByDefault: @Endpoint(id = "integrations", enableByDefault = false).

Operacje CRUD

Po dodaniu nowego adresu, pora na dodanie funkcjonalności. W Spring Boot 2 oprócz rejestracji nowych adresów, zmienił się też sposób obsługi operacji CRUD. Spring Boot 2 dostarcza nam trzy nowe adnotacje do mapowania operacji HTTP:

  • @ReadOperation - odczyt wartości przy wykorzystaniu metody GET.
  • @WriteOperation - zapis wartości przy wykorzystaniu metody POST.
  • @DeleteOperation - usunięcie wartości przy wykorzystaniu metody DELETE.

Spróbujmy zaimplementować klasę, która pozwala dodawać, usuwać oraz odczytywać wykorzystane integracje w naszej aplikacji:

1
2
3
4
5
6
7
8
9
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
@Component
@WebEndpoint(id = "integrations", enableByDefault = false)
public class IntegrationsEndpoint {

private Map<String, Integration> integrations = new HashMap<>();

@ReadOperation
public Map<String, Integration> getIntegrations() {
return integrations;
}

@ReadOperation
public Integration getIntegration(@Selector String name) {
return integrations.get(name);
}

@WriteOperation
public void addIntegration(@Selector String name, Integration integration) {
integrations.put(name, integration);
}

@DeleteOperation
public void deleteIntegrationBy(@Selector String name) {
integrations.remove(name);
}

}

class Integration {

private String integration;

public String getIntegration() {
return integration;
}

public void setIntegration(final String integration) {
this.integration = integration;
}
}

W powyższej klasie pojawiła się także adnotacja @Selector, która mapuje klucz przekazanej wartości z części ścieżki URL.

Extension

Czasami, oprócz podstawowej funkcjonalności endpointów, chcielibyśmy mieć wpływ na zwracane nagłówki lub kody statusów. Jest to możliwe dzięki adnotacji @EndpointWebExtension(endpoint = IntegrationsEndpoint.class). Adnotacja ta pod kluczem endpoint przyjmuje nazwę klasy endpointu, któremu chcemy dodać dodatkową funkcjonalność:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Component
@EndpointWebExtension(endpoint = IntegrationsEndpoint.class)
public class IntegrationsEndpointExtension {

private final IntegrationsEndpoint endpoint;

public IntegrationsEndpointExtension(final IntegrationsEndpoint endpoint) {
this.endpoint = endpoint;
}

@ReadOperation
WebEndpointResponse<Map<String, Integration>> integrations() {
final Map<String, Integration> integrations = endpoint.getIntegrations();
if (integrations.containsKey("someValue")) {
return new WebEndpointResponse<>(
integrations,
HttpStatus.CONFLICT.value());
}
return new WebEndpointResponse<>(
integrations,
HttpStatus.I_AM_A_TEAPOT.value());
}
}

Github

Całość jak zawsze na GitHubie.