Lección abierta "Crear clientes REST en Spring"

Y de nuevo, buen día! Muy pronto, comenzaremos a entrenar para el próximo grupo, "Desarrollador en el Marco de Primavera" , en relación con el cual llevamos a cabo una lección abierta, que ya se ha convertido en una tradición en previsión del lanzamiento. En este seminario web, hablamos sobre el desarrollo de clientes REST usando Spring, y también aprendimos en detalle sobre tecnologías como Spring Cache, Spring Retry e Hystrix.

Profesor: Yuri Dvorzhetsky - entrenador en el Centro de formación de Luxoft, desarrollador principal, candidato de ciencias físicas y matemáticas.

Al seminario web asistió una audiencia completamente diferente, evaluando su conocimiento de Spring dentro de 0-6 puntos en una escala de 10 puntos, sin embargo, a juzgar por las revisiones, la lección abierta parecía útil incluso para usuarios experimentados.



Algunas palabras sobre la primavera 5

Como saben, Spring Framework es un marco universal y bastante popular para la plataforma Java. Spring consiste en una masa de subproyectos o módulos, lo que le permite resolver muchos problemas. De hecho, esta es una gran colección de "marcos en un marco", por ejemplo, solo algunos de ellos:

  • Spring IoC + AOP = Contexto,
  • Spring JDBC,
  • Spring ORM,
  • Spring Data (este es un conjunto completo de subproyectos),
  • Spring MVC, Spring WebFlux,
  • Seguridad de primavera
  • Spring Cloud (este es un conjunto aún más grande de subproyectos)
  • Lote de primavera,
  • Bota de primavera.


Spring reemplaza la programación con algunas tareas para la configuración, pero la configuración a veces se convierte en una pesadilla. Para crear rápidamente aplicaciones de grado de producción, utilizan Spring Boot . Este es un marco especial que contiene un conjunto de iniciadores ('iniciador'), que simplifican la configuración de los marcos Spring y otras tecnologías.

Para mostrar algunas de las características de Spring, el tema del bloqueo de sitios es perfecto, ya que ahora está de moda)). Si desea participar activamente en la lección y la práctica, se recomienda descargar el repositorio con el código del servidor que sugirió el profesor. Usamos el siguiente comando:

git clone git@github.com:ydvorzhetskiy/sb-server.git

A continuación, simplemente ejecute, por ejemplo, así:

mvnw spring-boot:run

El mayor logro de Spring Boot es la capacidad de iniciar el servidor simplemente ejecutando la clase Main en IntelliJ IDEA.

El archivo BlockedSite.java contiene nuestro código fuente:

 package ru.otus.demoserver.domain; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @Entity public class BlockedSite { @Id @GeneratedValue private int id; private String url; 


Y aquí está el contenido del controlador BlockedSitesController.java:

 package ru.otus.demoserver.rest; @RestController public class BlockedSitesController { private final Logger logger = LoggerFactory.getLogger(BlockedSitesController.class); private final BlockedSitesRepository repository; public BlockedSitesController(BlockedSitesRepository repository) { this.repository = repository; } @GetMapping("/blocked-sites") public List<BlockedSite> blockedSites() { logger.info("Request has been performed"); return repository.findAll(); } } 



También preste atención a la base de datos anidada en pom.xml:

  <?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> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>ru.otus</groupId> <artifactId>demo-server</artifactId> <version>0.0.1-SNAPSHOT</version> <url>demo-server</url> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 

Ahora, de una manera simple y sin pretensiones, guardamos dos sitios bloqueados (DemoServerApplication.java) en nuestra base de datos a través del repositorio:

 package ru.otus.demoserver; @SpringBootApplication public class DemoServerApplication { public static void main(String[] args) { ApplicationContext ctx = SpringApplication.run(DemoServerApplication.class, args); BlockedSitesRepository repository = ctx.getBean(BlockedSitesRepository.class); repository.save(new BlockedSite("https://telegram.org/")); repository.save(new BlockedSite("https://azure.microsoft.com/")); } } 

Queda por iniciar el servidor usando Spring Boot y abrir la URL apropiada en el host local (localhost:8080/blocked-sites) . Al mismo tiempo, nuestro servidor nos devolverá una lista de los sitios que bloqueamos, es decir, aquellos sitios que agregamos a través de la base de datos.

Bueno, es hora de escribir un cliente en este servidor. Pero antes de continuar con esto, debe recordar algo.

Retiro teórico

Enumeremos algunos métodos HTTP (verbos):

  • GET: obtener una entidad o lista;
  • POST - creación de entidad;
  • PUT - entidad de cambio;
  • PARCHE - cambio de entidad (RFC -...);
  • BORRAR - borrar entidad;
  • HEAD, OPTIONS: métodos "difíciles" para admitir el protocolo HTTP y los servicios REST en general;
  • TRACE es un método obsoleto que no se utiliza.

Uno no puede dejar de recordar una propiedad tan importante como la idempotencia . En términos simples, no importa cuántas veces aplique una operación, su resultado será el mismo que si la aplicara solo una vez. Por ejemplo, saludaste a un hombre por la mañana, diciendo "¡Hola!" Como resultado, tu amigo entra en un estado de "hola" :-). Y si le dices "¡Hola!" Varias veces durante el día, nada cambiará, permanecerá en el mismo estado.

Ahora, pensemos cuál de los métodos HTTP anteriores es idempotente. Por supuesto, se entiende que observa la semántica. Si no lo sabe, el profesor le contará más sobre esto, a partir del minuto 26 del video.

DESCANSO

Para escribir un controlador REST, debe recordar qué es REST:

  • RESTO - Transferencia de estado representativa;
  • Es un estilo arquitectónico, no un estándar;
  • es, de hecho, un conjunto de principios-restricciones;
  • REST fue hace mucho tiempo, pero el término apareció relativamente recientemente;
  • La aplicación web de estilo REST se llama RESTful, su API en este caso es la API RESTful (el antónimo es Stateful);
  • REST ahora se llama lo que quieran ...

En primer lugar, si hablamos de interacción en forma de un cliente-servidor, entonces debe construirse en forma de una solicitud-respuesta. Sí, la interacción no siempre se construye de esta manera, pero ahora esta interacción es extremadamente común, y para las aplicaciones web, algo más parece muy extraño. Pero, por ejemplo, los sockets web, esto simplemente no es REST.

En segundo lugar, la limitación más importante en REST es la falta de estado del cliente en el servidor. Se supone que el cliente siempre pasa el estado necesario al servidor con cada solicitud, es decir, el estado se guarda en el lado del cliente y no hay sesiones en el servidor.

Cómo escribir un cliente en primavera

Para continuar, considere y ejecute el cliente (use el enlace al repositorio):

 git clone git@github.com:ydvorzhetskiy/sb-client.git 

 mvnw spring-boot:run 

Esta es una aplicación de consola y cliente ya escrita, no un servidor web.

Nos fijamos en las dependencias:

 <?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> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>ru.otus</groupId> <artifactId>demo-client</artifactId> <version>0.0.1-SNAPSHOT</version> <url>demo-client</url> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!--   RestTemplate,    - --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.1.4.RELEASE</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.9.8</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.8</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.8</version> </dependency> <!-- Cache --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!-- Retry --> <dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </dependency> <!-- Hystrix --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> <version>2.0.2.RELEASE</version> </dependency> <dependency> <groupId>com.netflix.hystrix</groupId> <artifactId>hystrix-javanica</artifactId> <version>1.5.12</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 

El cliente tiene una configuración:

1. RestTemplateConfig.java

 package ru.otus.democlient.config; @Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) { return restTemplateBuilder .setConnectTimeout(Duration.ofSeconds(2)) .setReadTimeout(Duration.ofSeconds(3)) .build(); } 

2. CacheConfig.java

 package ru.otus.democlient.config; @Configuration public class CacheConfig { @Bean public CacheManager cacheManager() { return new ConcurrentMapCacheManager("sites"); } } 

Y aquí está el contenido del archivo SiteServiceRest.java:

 package ru.otus.democlient.service; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.annotation.Cacheable; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpMethod; import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Retryable; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import java.util.Collections; import java.util.List; @Service public class SiteServiceRest implements SiteService { private final RestTemplate restTemplate; private final String serverUrl; public SiteServiceRest( RestTemplate restTemplate, @Value("${application.server.url}") String serverUrl ) { this.restTemplate = restTemplate; this.serverUrl = serverUrl; } @Override public List<SiteInfo> findAllBlockedSites() { return restTemplate.exchange( serverUrl + "/blocked-sites", HttpMethod.GET, null, new ParameterizedTypeReference<List<SiteInfo>>() { } ).getBody(); } public List<SiteInfo> getDefaultSites() { return Collections.singletonList(new SiteInfo() {{ setUrl("http://vk.com/"); }}); } } 

Resumir un poco:

  1. Las solicitudes se realizan a través de RestTemplate.
  2. RestTemplate se puede personalizar, y este es un bean normal.
  3. Jackson se usa para mapear JSON en objetos.
  4. A continuación, solo su vuelo de fantasía (los detalles sobre el lanzamiento del cliente están en el video).

Colegas, el seminario web resultó ser muy informativo, por lo tanto, para no perderse nada, es mejor verlo en su totalidad. @Cacheable la API real "en combate", agregará @Cacheable al servicio, trabajará con Spring Retry, aprenderá sobre Hystrix y mucho más. También te invitamos a la Jornada de Puertas Abiertas de Primavera, que se realizará muy pronto.

Y, como siempre, ¡esperamos sus comentarios sobre la lección abierta del pasado!

Source: https://habr.com/ru/post/440046/


All Articles