📜 ⬆️ ⬇️

Open lesson "Creating REST-clients on Spring"

And again, good day! Very soon we will start training the next group “Developer on the Spring Framework” , in connection with which we conducted an open lesson, which has become a tradition on the eve of the launch. This webinar talked about developing REST clients using Spring, and also learned in detail about technologies such as Spring Cache, Spring Retry, and Hystrix.

Lecturer: Yuri Dvorzhetsky - a trainer at Luxoft Training Center, a leading developer, Candidate of Physical and Mathematical Sciences.

The webinar was visited by a completely different audience, assessing their knowledge of Spring within 0-6 points on a 10-point scale, however, judging by the reviews, the open lesson seemed useful even to experienced users.



A few words about Spring 5

As you know, the Spring Framework is a universal and quite popular framework for the Java platform. Spring consists of a mass of subprojects or modules that allows you to solve many problems. In fact, this is a large collection of frameworks in the framework, for example, just a few of them:



Spring replaces the programming of some tasks with configuration, however, configuration sometimes turns into just a nightmare. To quickly create production-grade applications, use Spring Boot . This is a special framework that contains a set of starters ('starter') that simplify the configuration of Spring frameworks and other technologies.

To show some features of the work of Spring, the theme of blocking sites is perfect, as it is now fashionable)). If you want to actively participate in the lesson and practice, it is recommended to download the repository with the server code offered by the teacher. Use the following command:

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

Then just run, for example, like this:

mvnw spring-boot:run

The biggest achievement of Spring Boot is the ability to start the server by simply launching the Main class in IntelliJ IDEA.

The file BlockedSite.java contains our source code:

 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; // ... } 


But the contents of the BlockedSitesController.java controller:

 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(); } } 



Also note the nested database in 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> 

Now, in a simple and straightforward way, we save two blocked sites (DemoServerApplication.java) to our database via the repository:

 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/")); } } 

It remains to start the server using Spring Boot and open the corresponding URL on the local host (localhost:8080/blocked-sites) . At the same time, our server will return to us a list of sites blocked by us, that is, those sites that we added through the database.

Well, it's time to write a client to this server. But before you go to this, you need to remember something.

Theoretical retreat

Let's list some HTTP methods (verbs):


It is impossible not to recall such an important property as idempotency . In simple terms, no matter how many times you use an operation, its result will be the same, as if you applied it only once. For example, you greeted the person in the morning, saying “Hello!” As a result, your friend goes into the “greeted” state :-). And if you say hello several times during the day, nothing will change, it will remain in the same state.

And now, let's think about which of the above HTTP methods are idempotent? Of course, it is implied that you observe semantics. If you do not know, then the teacher tells more about this, starting with the 26th minute of the video.

REST

In order to write a REST controller, you need to remember what REST is:


First, if we talk about interaction in the form of a client-server, then it needs to be built in the form of a request-response. Yes, the interaction is not always built that way, but now this interaction is extremely common, and for web applications, something else looks quite strange. And here, for example, web sockets is just not REST.

Secondly, the most important restriction in REST is the lack of client status on the server. It is assumed that the client server always sends all the necessary state with each request, that is, the state is saved on the client side, and there are no sessions on the server.

How to write a client in Spring

To continue work, we will consider and launch the client (use the link to the repository):

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

 mvnw spring-boot:run 

This is a client and console application already written, not a web server.

See dependencies:

 <?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> 

The client has the configuration:

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"); } } 

But the contents of the SiteServiceRest.java file:

 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/"); }}); } } 

Slightly summarize:

  1. Requests are made via RestTemplate.
  2. RestTemplate can be customized, and this is a regular bean.
  3. Jackson is used for mapping JSON into objects.
  4. Further - only your flight of fancy (details about the launch of the client are in the video).

Colleagues, the webinar turned out to be very informative, therefore, in order not to miss anything, it’s better to watch it completely. You will try the real API "in combat conditions", add @Cacheable to the service, work with Spring Retry, learn about Hystrix and a lot more. We also invite you to the Spring Open Day , which will take place very soon.

And, as usual, we are waiting for your comments on the past open lesson!

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