2 added
2 removed
Original
2026-01-01
Modified
2026-03-10
1
<p>И снова доброго времени суток! Совсем скоро у нас стартует обучение очередной группы <a>"Разработчик на Spring Framework"</a>, в связи с чем мы провели открытый урок, что стало уже традицией в преддверии запуска. На этом вебинаре говорили о разработке REST-клиентов с помощью Spring, а также детально узнали о таких технологиях, как Spring Cache, Spring Retry и Hystrix.</p>
1
<p>И снова доброго времени суток! Совсем скоро у нас стартует обучение очередной группы <a>"Разработчик на Spring Framework"</a>, в связи с чем мы провели открытый урок, что стало уже традицией в преддверии запуска. На этом вебинаре говорили о разработке REST-клиентов с помощью Spring, а также детально узнали о таких технологиях, как Spring Cache, Spring Retry и Hystrix.</p>
2
<p>Преподаватель: <a>Юрий Дворжецкий</a> - тренер в Luxoft Training Center, ведущий разработчик, кандидат физико-математических наук.</p>
2
<p>Преподаватель: <a>Юрий Дворжецкий</a> - тренер в Luxoft Training Center, ведущий разработчик, кандидат физико-математических наук.</p>
3
-
<p>Вебинар посетила совершенно разная аудитория, оценившая свои знания по Spring в пределах 0-6 баллов по 10-бальной шкале, однако, судя по отзывам, открытый урок показался полезным даже опытным пользователям.</p>
3
+
<p>Вебинар ��осетила совершенно разная аудитория, оценившая свои знания по Spring в пределах 0-6 баллов по 10-бальной шкале, однако, судя по отзывам, открытый урок показался полезным даже опытным пользователям.</p>
4
<p><strong>Пару слов о Spring 5</strong></p>
4
<p><strong>Пару слов о Spring 5</strong></p>
5
<p>Как известно, Spring Framework является универсальным и довольно популярным фреймворком для Java-платформы. Spring состоит из массы подпроектов или модулей, что позволяет решать множество задач. По сути, это большая коллекция "фреймворков во фреймворке", вот, например, лишь некоторые из них:</p>
5
<p>Как известно, Spring Framework является универсальным и довольно популярным фреймворком для Java-платформы. Spring состоит из массы подпроектов или модулей, что позволяет решать множество задач. По сути, это большая коллекция "фреймворков во фреймворке", вот, например, лишь некоторые из них:</p>
6
<ul><li>Spring IoC + AOP = Context,</li>
6
<ul><li>Spring IoC + AOP = Context,</li>
7
<li>Spring JDBC,</li>
7
<li>Spring JDBC,</li>
8
<li>Spring ORM,</li>
8
<li>Spring ORM,</li>
9
<li>Spring Data (это целый набор подпроектов),</li>
9
<li>Spring Data (это целый набор подпроектов),</li>
10
<li>Spring MVC, Spring WebFlux,</li>
10
<li>Spring MVC, Spring WebFlux,</li>
11
<li>Spring Security,</li>
11
<li>Spring Security,</li>
12
<li>Spring Cloud (это ещё более огромный набор подпроектов),</li>
12
<li>Spring Cloud (это ещё более огромный набор подпроектов),</li>
13
<li>Spring Batch,</li>
13
<li>Spring Batch,</li>
14
<li>Spring Boot.</li>
14
<li>Spring Boot.</li>
15
</ul><p><a></a>Spring заменяет конфигурированием программирование некоторых задач, однако конфигурирование иногда превращается просто в кошмар. Для быстрого создания production-grade приложений как раз и используют <strong>Spring Boot</strong>. Это специальный фреймворк, который содержит набор стартеров (‘starter’), упрощающих настройку Spring-фреймворков и других технологий.</p>
15
</ul><p><a></a>Spring заменяет конфигурированием программирование некоторых задач, однако конфигурирование иногда превращается просто в кошмар. Для быстрого создания production-grade приложений как раз и используют <strong>Spring Boot</strong>. Это специальный фреймворк, который содержит набор стартеров (‘starter’), упрощающих настройку Spring-фреймворков и других технологий.</p>
16
<p>Чтобы показать некоторые особенности работы Spring, прекрасно подходит тема блокировки сайтов, так как это сейчас модно)). Если хотите активно поучаствовать в уроке и попрактиковаться, рекомендуется скачать <a>репозиторий</a> с кодом сервера, который предложил преподаватель. Используем следующую команду:</p>
16
<p>Чтобы показать некоторые особенности работы Spring, прекрасно подходит тема блокировки сайтов, так как это сейчас модно)). Если хотите активно поучаствовать в уроке и попрактиковаться, рекомендуется скачать <a>репозиторий</a> с кодом сервера, который предложил преподаватель. Используем следующую команду:</p>
17
<p>git clone git@github.com:ydvorzhetskiy/sb-server.git</p>
17
<p>git clone git@github.com:ydvorzhetskiy/sb-server.git</p>
18
<p>Далее просто запускаем, например, так:</p>
18
<p>Далее просто запускаем, например, так:</p>
19
<p>mvnw spring-boot:run</p>
19
<p>mvnw spring-boot:run</p>
20
<p>Самым большим достижением Spring Boot является возможность запустить сервер простым запуском Main-класса в IntelliJ IDEA.</p>
20
<p>Самым большим достижением Spring Boot является возможность запустить сервер простым запуском Main-класса в IntelliJ IDEA.</p>
21
<p>В файле BlockedSite.java находится наш исходный код:</p>
21
<p>В файле BlockedSite.java находится наш исходный код:</p>
22
package ru.otus.demoserver.domain;<strong>import</strong>javax.persistence.Entity;<strong>import</strong>javax.persistence.GeneratedValue;<strong>import</strong>javax.persistence.Id; @Entity public<strong>class</strong><strong>BlockedSite</strong>{ @Id @GeneratedValue private int id; private String url;<p>А вот содержимое контроллера BlockedSitesController.java:</p>
22
package ru.otus.demoserver.domain;<strong>import</strong>javax.persistence.Entity;<strong>import</strong>javax.persistence.GeneratedValue;<strong>import</strong>javax.persistence.Id; @Entity public<strong>class</strong><strong>BlockedSite</strong>{ @Id @GeneratedValue private int id; private String url;<p>А вот содержимое контроллера BlockedSitesController.java:</p>
23
package ru.otus.demoserver.rest; @RestController public<strong>class</strong><strong>BlockedSitesController</strong>{ private final Logger logger = LoggerFactory.getLogger(BlockedSitesController.class); private final BlockedSitesRepository repository; public<strong>BlockedSitesController</strong>(BlockedSitesRepository repository) { this.repository = repository; } @GetMapping("/blocked-sites") public List<BlockedSite><strong>blockedSites</strong>() { logger.info("Request has been performed");<strong>return</strong>repository.findAll(); } }<p>Также обратите внимание на вложенную БД в pom.xml:</p>
23
package ru.otus.demoserver.rest; @RestController public<strong>class</strong><strong>BlockedSitesController</strong>{ private final Logger logger = LoggerFactory.getLogger(BlockedSitesController.class); private final BlockedSitesRepository repository; public<strong>BlockedSitesController</strong>(BlockedSitesRepository repository) { this.repository = repository; } @GetMapping("/blocked-sites") public List<BlockedSite><strong>blockedSites</strong>() { logger.info("Request has been performed");<strong>return</strong>repository.findAll(); } }<p>Также обратите внимание на вложенную БД в pom.xml:</p>
24
<?xml version="1.0" encoding="UTF-8"?> <<strong>project</strong>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"> <<strong>modelVersion</strong>>4.0.0</<strong>modelVersion</strong>> <<strong>parent</strong>> <<strong>groupId</strong>>org.springframework.boot</<strong>groupId</strong>> <<strong>artifactId</strong>>spring-boot-starter-parent</<strong>artifactId</strong>> <<strong>version</strong>>2.1.2.RELEASE</<strong>version</strong>> <<strong>relativePath</strong>/> <!-- lookup parent from repository --> </<strong>parent</strong>> <<strong>groupId</strong>>ru.otus</<strong>groupId</strong>> <<strong>artifactId</strong>>demo-server</<strong>artifactId</strong>> <<strong>version</strong>>0.0.1-SNAPSHOT</<strong>version</strong>> <<strong>url</strong>>demo-server</<strong>url</strong>> <<strong>description</strong>>Demo project for Spring Boot</<strong>description</strong>> <<strong>properties</strong>> <<strong>java.version</strong>>1.8</<strong>java.version</strong>> </<strong>properties</strong>> <<strong>dependencies</strong>> <<strong>dependency</strong>> <<strong>groupId</strong>>org.springframework.boot</<strong>groupId</strong>> <<strong>artifactId</strong>>spring-boot-starter-web</<strong>artifactId</strong>> </<strong>dependency</strong>> <<strong>dependency</strong>> <<strong>groupId</strong>>org.springframework.boot</<strong>groupId</strong>> <<strong>artifactId</strong>>spring-boot-starter-data-jpa</<strong>artifactId</strong>> </<strong>dependency</strong>> <<strong>dependency</strong>> <<strong>groupId</strong>>com.h2database</<strong>groupId</strong>> <<strong>artifactId</strong>>h2</<strong>artifactId</strong>> </<strong>dependency</strong>> <<strong>dependency</strong>> <<strong>groupId</strong>>org.springframework.boot</<strong>groupId</strong>> <<strong>artifactId</strong>>spring-boot-starter-test</<strong>artifactId</strong>> <<strong>scope</strong>>test</<strong>scope</strong>> </<strong>dependency</strong>> </<strong>dependencies</strong>> <<strong>build</strong>> <<strong>plugins</strong>> <<strong>plugin</strong>> <<strong>groupId</strong>>org.springframework.boot</<strong>groupId</strong>> <<strong>artifactId</strong>>spring-boot-maven-plugin</<strong>artifactId</strong>> </<strong>plugin</strong>> </<strong>plugins</strong>> </<strong>build</strong>> </<strong>project</strong>><p>Теперь простым и незатейливым образом сохраняем в нашу БД через репозиторий два заблокированных сайта (DemoServerApplication.java):</p>
24
<?xml version="1.0" encoding="UTF-8"?> <<strong>project</strong>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"> <<strong>modelVersion</strong>>4.0.0</<strong>modelVersion</strong>> <<strong>parent</strong>> <<strong>groupId</strong>>org.springframework.boot</<strong>groupId</strong>> <<strong>artifactId</strong>>spring-boot-starter-parent</<strong>artifactId</strong>> <<strong>version</strong>>2.1.2.RELEASE</<strong>version</strong>> <<strong>relativePath</strong>/> <!-- lookup parent from repository --> </<strong>parent</strong>> <<strong>groupId</strong>>ru.otus</<strong>groupId</strong>> <<strong>artifactId</strong>>demo-server</<strong>artifactId</strong>> <<strong>version</strong>>0.0.1-SNAPSHOT</<strong>version</strong>> <<strong>url</strong>>demo-server</<strong>url</strong>> <<strong>description</strong>>Demo project for Spring Boot</<strong>description</strong>> <<strong>properties</strong>> <<strong>java.version</strong>>1.8</<strong>java.version</strong>> </<strong>properties</strong>> <<strong>dependencies</strong>> <<strong>dependency</strong>> <<strong>groupId</strong>>org.springframework.boot</<strong>groupId</strong>> <<strong>artifactId</strong>>spring-boot-starter-web</<strong>artifactId</strong>> </<strong>dependency</strong>> <<strong>dependency</strong>> <<strong>groupId</strong>>org.springframework.boot</<strong>groupId</strong>> <<strong>artifactId</strong>>spring-boot-starter-data-jpa</<strong>artifactId</strong>> </<strong>dependency</strong>> <<strong>dependency</strong>> <<strong>groupId</strong>>com.h2database</<strong>groupId</strong>> <<strong>artifactId</strong>>h2</<strong>artifactId</strong>> </<strong>dependency</strong>> <<strong>dependency</strong>> <<strong>groupId</strong>>org.springframework.boot</<strong>groupId</strong>> <<strong>artifactId</strong>>spring-boot-starter-test</<strong>artifactId</strong>> <<strong>scope</strong>>test</<strong>scope</strong>> </<strong>dependency</strong>> </<strong>dependencies</strong>> <<strong>build</strong>> <<strong>plugins</strong>> <<strong>plugin</strong>> <<strong>groupId</strong>>org.springframework.boot</<strong>groupId</strong>> <<strong>artifactId</strong>>spring-boot-maven-plugin</<strong>artifactId</strong>> </<strong>plugin</strong>> </<strong>plugins</strong>> </<strong>build</strong>> </<strong>project</strong>><p>Теперь простым и незатейливым образом сохраняем в нашу БД через репозиторий два заблокированных сайта (DemoServerApplication.java):</p>
25
package ru.otus.demoserver; @SpringBootApplication public<strong>class</strong><strong>DemoServerApplication</strong>{ public<strong>static</strong><strong>void</strong><strong>main</strong>(String[] args) { ApplicationContext ctx = SpringApplication.run(DemoServerApplication.class, args); BlockedSitesRepository repository = ctx.getBean(BlockedSitesRepository.class); repository.save(<strong>new</strong>BlockedSite("https://telegram.org/")); repository.save(<strong>new</strong>BlockedSite("https://azure.microsoft.com/")); } }<p>Осталось запустить сервер с помощью Spring Boot и открыть соответствующий урл на локальном хосте (localhost:8080/blocked-sites). При этом наш сервер будет нам возвращать список заблокированных нами сайтов, то есть те сайты, которые мы добавили через БД.</p>
25
package ru.otus.demoserver; @SpringBootApplication public<strong>class</strong><strong>DemoServerApplication</strong>{ public<strong>static</strong><strong>void</strong><strong>main</strong>(String[] args) { ApplicationContext ctx = SpringApplication.run(DemoServerApplication.class, args); BlockedSitesRepository repository = ctx.getBean(BlockedSitesRepository.class); repository.save(<strong>new</strong>BlockedSite("https://telegram.org/")); repository.save(<strong>new</strong>BlockedSite("https://azure.microsoft.com/")); } }<p>Осталось запустить сервер с помощью Spring Boot и открыть соответствующий урл на локальном хосте (localhost:8080/blocked-sites). При этом наш сервер будет нам возвращать список заблокированных нами сайтов, то есть те сайты, которые мы добавили через БД.</p>
26
<p>Что же, пришла пора писать клиента к этому серверу. Но прежде чем к этому перейти, нужно кое-что вспомнить.</p>
26
<p>Что же, пришла пора писать клиента к этому серверу. Но прежде чем к этому перейти, нужно кое-что вспомнить.</p>
27
<p><strong>Теоретическое отступление</strong></p>
27
<p><strong>Теоретическое отступление</strong></p>
28
<p>Давайте перечислим некоторые HTTP-методы (глаголы):</p>
28
<p>Давайте перечислим некоторые HTTP-методы (глаголы):</p>
29
<ul><li>GET - получение entity или списка;</li>
29
<ul><li>GET - получение entity или списка;</li>
30
<li>POST - создание entity;</li>
30
<li>POST - создание entity;</li>
31
<li>PUT - изменение entity;</li>
31
<li>PUT - изменение entity;</li>
32
<li>PATCH - изменение entity (RFC-…);</li>
32
<li>PATCH - изменение entity (RFC-…);</li>
33
<li>DELETE - удаление entity;</li>
33
<li>DELETE - удаление entity;</li>
34
<li>HEAD, OPTIONS - "хитрые" методы для поддержки HTTP-протокола и вообще REST-сервисов;</li>
34
<li>HEAD, OPTIONS - "хитрые" методы для поддержки HTTP-протокола и вообще REST-сервисов;</li>
35
<li>TRACE - устаревший метод, который не используется.</li>
35
<li>TRACE - устаревший метод, который не используется.</li>
36
</ul><p>Нельзя не вспомнить и про такое важное свойство, как <strong>идемпотентность</strong>. Говоря простым языком, сколько бы раз вы не применяли операцию, её результат будет один и тот же, как если бы вы применили её всего один раз. Например, вы поздоровались с утра с человеком, сказав ему "Привет!" В результате ваш знакомый переходит в состояние "поздорованный" :-). И если вы ещё несколько раз в течение дня ему скажете "Привет!", ничего не изменится, он останется в том же состоянии.</p>
36
</ul><p>Нельзя не вспомнить и про такое важное свойство, как <strong>идемпотентность</strong>. Говоря простым языком, сколько бы раз вы не применяли операцию, её результат будет один и тот же, как если бы вы применили её всего один раз. Например, вы поздоровались с утра с человеком, сказав ему "Привет!" В результате ваш знакомый переходит в состояние "поздорованный" :-). И если вы ещё несколько раз в течение дня ему скажете "Привет!", ничего не изменится, он останется в том же состоянии.</p>
37
<p>А теперь, давайте подумаем, какие из вышеперечисленных HTTP-методов идемпотентны? Конечно, подразумевается, что вы соблюдаете семантику. Если не знаете, то подробнее об этом рассказывает преподаватель, начиная с 26-й минуты видео.</p>
37
<p>А теперь, давайте подумаем, какие из вышеперечисленных HTTP-методов идемпотентны? Конечно, подразумевается, что вы соблюдаете семантику. Если не знаете, то подробнее об этом рассказывает преподаватель, начиная с 26-й минуты видео.</p>
38
<p><strong>REST</strong></p>
38
<p><strong>REST</strong></p>
39
<p>Для того чтобы писать REST-контроллер, нужно вспомнить, что такое REST:</p>
39
<p>Для того чтобы писать REST-контроллер, нужно вспомнить, что такое REST:</p>
40
<ul><li>REST - REpresentational State Transfer;</li>
40
<ul><li>REST - REpresentational State Transfer;</li>
41
<li>это архитектурный стиль, а не стандарт;</li>
41
<li>это архитектурный стиль, а не стандарт;</li>
42
<li>это, по сути, набор принципов-ограничений;</li>
42
<li>это, по сути, набор принципов-ограничений;</li>
43
<li>REST был давно, но термин появился сравнительно недавно;</li>
43
<li>REST был давно, но термин появился сравнительно недавно;</li>
44
<li>Web-приложение в стиле REST называется RESTful, его API в таком случае - RESTful API (антоним - Stateful);</li>
44
<li>Web-приложение в стиле REST называется RESTful, его API в таком случае - RESTful API (антоним - Stateful);</li>
45
<li>REST-ом сейчас называют всё что хотят…</li>
45
<li>REST-ом сейчас называют всё что хотят…</li>
46
</ul><p>Во-первых, если говорить о взаимодействии в виде клиент-сервер, то его нужно строить в виде запрос-ответ. Да, не всегда взаимодействие так строится, но сейчас такое взаимодействие крайне распространено, а для веб-приложений что-то другое выглядит совсем странно. А вот, например, веб-сокеты - это как раз не REST.</p>
46
</ul><p>Во-первых, если говорить о взаимодействии в виде клиент-сервер, то его нужно строить в виде запрос-ответ. Да, не всегда взаимодействие так строится, но сейчас такое взаимодействие крайне распространено, а для веб-приложений что-то другое выглядит совсем странно. А вот, например, веб-сокеты - это как раз не REST.</p>
47
<p>Во-вторых, самое важное ограничение в REST - отсутствие состояния клиента на сервере. Предполагается, что серверу клиент всегда передаёт всё необходимое состояние с каждым запросом, то есть состояние сохраняется на стороне клиента, и нет никаких сессий на сервере.</p>
47
<p>Во-вторых, самое важное ограничение в REST - отсутствие состояния клиента на сервере. Предполагается, что серверу клиент всегда передаёт всё необходимое состояние с каждым запросом, то есть состояние сохраняется на стороне клиента, и нет никаких сессий на сервере.</p>
48
<h3>Как писать клиента на Spring</h3>
48
<h3>Как писать клиента на Spring</h3>
49
<p>Для продолжения работы рассмотрим и запустим клиента (используем ссылку на репозиторий):</p>
49
<p>Для продолжения работы рассмотрим и запустим клиента (используем ссылку на репозиторий):</p>
50
git clone git@github.com:ydvorzhetskiy/sb-client.git mvnw spring-boot:run<p>Это уже написанный клиент и консольное приложение, а не веб-сервер.</p>
50
git clone git@github.com:ydvorzhetskiy/sb-client.git mvnw spring-boot:run<p>Это уже написанный клиент и консольное приложение, а не веб-сервер.</p>
51
<p>Смотрим зависимости:</p>
51
<p>Смотрим зависимости:</p>
52
<?xml version="1.0" encoding="UTF-8"?> <<strong>project</strong>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"> <<strong>modelVersion</strong>>4.0.0</<strong>modelVersion</strong>> <<strong>parent</strong>> <<strong>groupId</strong>>org.springframework.boot</<strong>groupId</strong>> <<strong>artifactId</strong>>spring-boot-starter-parent</<strong>artifactId</strong>> <<strong>version</strong>>2.1.2.RELEASE</<strong>version</strong>> <<strong>relativePath</strong>/> <!-- lookup parent from repository --> </<strong>parent</strong>> <<strong>groupId</strong>>ru.otus</<strong>groupId</strong>> <<strong>artifactId</strong>>demo-client</<strong>artifactId</strong>> <<strong>version</strong>>0.0.1-SNAPSHOT</<strong>version</strong>> <<strong>url</strong>>demo-client</<strong>url</strong>> <<strong>description</strong>>Demo project for Spring Boot</<strong>description</strong>> <<strong>properties</strong>> <<strong>java.version</strong>>1.8</<strong>java.version</strong>> </<strong>properties</strong>> <<strong>dependencies</strong>> <<strong>dependency</strong>> <<strong>groupId</strong>>org.springframework.boot</<strong>groupId</strong>> <<strong>artifactId</strong>>spring-boot-starter</<strong>artifactId</strong>> </<strong>dependency</strong>> <!-- Это для RestTemplate, это ещё не веб-приложение --> <<strong>dependency</strong>> <<strong>groupId</strong>>org.springframework</<strong>groupId</strong>> <<strong>artifactId</strong>>spring-web</<strong>artifactId</strong>> <<strong>version</strong>>5.1.4.RELEASE</<strong>version</strong>> </<strong>dependency</strong>> <<strong>dependency</strong>> <<strong>groupId</strong>>com.fasterxml.jackson.core</<strong>groupId</strong>> <<strong>artifactId</strong>>jackson-annotations</<strong>artifactId</strong>> <<strong>version</strong>>2.9.8</<strong>version</strong>> </<strong>dependency</strong>> <<strong>dependency</strong>> <<strong>groupId</strong>>com.fasterxml.jackson.core</<strong>groupId</strong>> <<strong>artifactId</strong>>jackson-core</<strong>artifactId</strong>> <<strong>version</strong>>2.9.8</<strong>version</strong>> </<strong>dependency</strong>> <<strong>dependency</strong>> <<strong>groupId</strong>>com.fasterxml.jackson.core</<strong>groupId</strong>> <<strong>artifactId</strong>>jackson-databind</<strong>artifactId</strong>> <<strong>version</strong>>2.9.8</<strong>version</strong>> </<strong>dependency</strong>> <!-- Cache --> <<strong>dependency</strong>> <<strong>groupId</strong>>org.springframework.boot</<strong>groupId</strong>> <<strong>artifactId</strong>>spring-boot-starter-cache</<strong>artifactId</strong>> </<strong>dependency</strong>> <!-- Retry --> <<strong>dependency</strong>> <<strong>groupId</strong>>org.springframework.retry</<strong>groupId</strong>> <<strong>artifactId</strong>>spring-retry</<strong>artifactId</strong>> </<strong>dependency</strong>> <<strong>dependency</strong>> <<strong>groupId</strong>>org.aspectj</<strong>groupId</strong>> <<strong>artifactId</strong>>aspectjweaver</<strong>artifactId</strong>> </<strong>dependency</strong>> <!-- Hystrix --> <<strong>dependency</strong>> <<strong>groupId</strong>>org.springframework.cloud</<strong>groupId</strong>> <<strong>artifactId</strong>>spring-cloud-starter-netflix-hystrix</<strong>artifactId</strong>> <<strong>version</strong>>2.0.2.RELEASE</<strong>version</strong>> </<strong>dependency</strong>> <<strong>dependency</strong>> <<strong>groupId</strong>>com.netflix.hystrix</<strong>groupId</strong>> <<strong>artifactId</strong>>hystrix-javanica</<strong>artifactId</strong>> <<strong>version</strong>>1.5.12</<strong>version</strong>> </<strong>dependency</strong>> </<strong>dependencies</strong>> <<strong>build</strong>> <<strong>plugins</strong>> <<strong>plugin</strong>> <<strong>groupId</strong>>org.springframework.boot</<strong>groupId</strong>> <<strong>artifactId</strong>>spring-boot-maven-plugin</<strong>artifactId</strong>> </<strong>plugin</strong>> </<strong>plugins</strong>> </<strong>build</strong>> </<strong>project</strong>><p>У клиента есть конфигурация:</p>
52
<?xml version="1.0" encoding="UTF-8"?> <<strong>project</strong>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"> <<strong>modelVersion</strong>>4.0.0</<strong>modelVersion</strong>> <<strong>parent</strong>> <<strong>groupId</strong>>org.springframework.boot</<strong>groupId</strong>> <<strong>artifactId</strong>>spring-boot-starter-parent</<strong>artifactId</strong>> <<strong>version</strong>>2.1.2.RELEASE</<strong>version</strong>> <<strong>relativePath</strong>/> <!-- lookup parent from repository --> </<strong>parent</strong>> <<strong>groupId</strong>>ru.otus</<strong>groupId</strong>> <<strong>artifactId</strong>>demo-client</<strong>artifactId</strong>> <<strong>version</strong>>0.0.1-SNAPSHOT</<strong>version</strong>> <<strong>url</strong>>demo-client</<strong>url</strong>> <<strong>description</strong>>Demo project for Spring Boot</<strong>description</strong>> <<strong>properties</strong>> <<strong>java.version</strong>>1.8</<strong>java.version</strong>> </<strong>properties</strong>> <<strong>dependencies</strong>> <<strong>dependency</strong>> <<strong>groupId</strong>>org.springframework.boot</<strong>groupId</strong>> <<strong>artifactId</strong>>spring-boot-starter</<strong>artifactId</strong>> </<strong>dependency</strong>> <!-- Это для RestTemplate, это ещё не веб-приложение --> <<strong>dependency</strong>> <<strong>groupId</strong>>org.springframework</<strong>groupId</strong>> <<strong>artifactId</strong>>spring-web</<strong>artifactId</strong>> <<strong>version</strong>>5.1.4.RELEASE</<strong>version</strong>> </<strong>dependency</strong>> <<strong>dependency</strong>> <<strong>groupId</strong>>com.fasterxml.jackson.core</<strong>groupId</strong>> <<strong>artifactId</strong>>jackson-annotations</<strong>artifactId</strong>> <<strong>version</strong>>2.9.8</<strong>version</strong>> </<strong>dependency</strong>> <<strong>dependency</strong>> <<strong>groupId</strong>>com.fasterxml.jackson.core</<strong>groupId</strong>> <<strong>artifactId</strong>>jackson-core</<strong>artifactId</strong>> <<strong>version</strong>>2.9.8</<strong>version</strong>> </<strong>dependency</strong>> <<strong>dependency</strong>> <<strong>groupId</strong>>com.fasterxml.jackson.core</<strong>groupId</strong>> <<strong>artifactId</strong>>jackson-databind</<strong>artifactId</strong>> <<strong>version</strong>>2.9.8</<strong>version</strong>> </<strong>dependency</strong>> <!-- Cache --> <<strong>dependency</strong>> <<strong>groupId</strong>>org.springframework.boot</<strong>groupId</strong>> <<strong>artifactId</strong>>spring-boot-starter-cache</<strong>artifactId</strong>> </<strong>dependency</strong>> <!-- Retry --> <<strong>dependency</strong>> <<strong>groupId</strong>>org.springframework.retry</<strong>groupId</strong>> <<strong>artifactId</strong>>spring-retry</<strong>artifactId</strong>> </<strong>dependency</strong>> <<strong>dependency</strong>> <<strong>groupId</strong>>org.aspectj</<strong>groupId</strong>> <<strong>artifactId</strong>>aspectjweaver</<strong>artifactId</strong>> </<strong>dependency</strong>> <!-- Hystrix --> <<strong>dependency</strong>> <<strong>groupId</strong>>org.springframework.cloud</<strong>groupId</strong>> <<strong>artifactId</strong>>spring-cloud-starter-netflix-hystrix</<strong>artifactId</strong>> <<strong>version</strong>>2.0.2.RELEASE</<strong>version</strong>> </<strong>dependency</strong>> <<strong>dependency</strong>> <<strong>groupId</strong>>com.netflix.hystrix</<strong>groupId</strong>> <<strong>artifactId</strong>>hystrix-javanica</<strong>artifactId</strong>> <<strong>version</strong>>1.5.12</<strong>version</strong>> </<strong>dependency</strong>> </<strong>dependencies</strong>> <<strong>build</strong>> <<strong>plugins</strong>> <<strong>plugin</strong>> <<strong>groupId</strong>>org.springframework.boot</<strong>groupId</strong>> <<strong>artifactId</strong>>spring-boot-maven-plugin</<strong>artifactId</strong>> </<strong>plugin</strong>> </<strong>plugins</strong>> </<strong>build</strong>> </<strong>project</strong>><p>У клиента есть конфигурация:</p>
53
<p>1. RestTemplateConfig.java</p>
53
<p>1. RestTemplateConfig.java</p>
54
package ru.otus.democlient.config; @Configuration public<strong>class</strong><strong>RestTemplateConfig</strong>{ @Bean public RestTemplate<strong>restTemplate</strong>(RestTemplateBuilder restTemplateBuilder) {<strong>return</strong>restTemplateBuilder .setConnectTimeout(Duration.ofSeconds(2)) .setReadTimeout(Duration.ofSeconds(3)) .build(); }<p>2. CacheConfig.java</p>
54
package ru.otus.democlient.config; @Configuration public<strong>class</strong><strong>RestTemplateConfig</strong>{ @Bean public RestTemplate<strong>restTemplate</strong>(RestTemplateBuilder restTemplateBuilder) {<strong>return</strong>restTemplateBuilder .setConnectTimeout(Duration.ofSeconds(2)) .setReadTimeout(Duration.ofSeconds(3)) .build(); }<p>2. CacheConfig.java</p>
55
package ru.otus.democlient.config; @Configuration public<strong>class</strong><strong>CacheConfig</strong>{ @Bean public CacheManager<strong>cacheManager</strong>() {<strong>return</strong><strong>new</strong>ConcurrentMapCacheManager("sites"); } }<p>А вот содержимое файла SiteServiceRest.java:</p>
55
package ru.otus.democlient.config; @Configuration public<strong>class</strong><strong>CacheConfig</strong>{ @Bean public CacheManager<strong>cacheManager</strong>() {<strong>return</strong><strong>new</strong>ConcurrentMapCacheManager("sites"); } }<p>А вот содержимое файла SiteServiceRest.java:</p>
56
package ru.otus.democlient.service;<strong>import</strong>com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;<strong>import</strong>org.springframework.beans.factory.annotation.Value;<strong>import</strong>org.springframework.cache.annotation.Cacheable;<strong>import</strong>org.springframework.core.ParameterizedTypeReference;<strong>import</strong>org.springframework.http.HttpMethod;<strong>import</strong>org.springframework.retry.annotation.Backoff;<strong>import</strong>org.springframework.retry.annotation.Retryable;<strong>import</strong>org.springframework.stereotype.Service;<strong>import</strong>org.springframework.web.client.RestTemplate;<strong>import</strong>java.util.Collections;<strong>import</strong>java.util.List; @Service public<strong>class</strong><strong>SiteServiceRest</strong><strong>implements</strong><strong>SiteService</strong>{ private final RestTemplate restTemplate; private final String serverUrl; public<strong>SiteServiceRest</strong>( RestTemplate restTemplate, @Value("${application.server.url}") String serverUrl ) { this.restTemplate = restTemplate; this.serverUrl = serverUrl; } @Override public List<SiteInfo><strong>findAllBlockedSites</strong>() {<strong>return</strong>restTemplate.exchange( serverUrl + "/blocked-sites", HttpMethod.GET, null,<strong>new</strong>ParameterizedTypeReference<List<SiteInfo>>() { } ).getBody(); } public List<SiteInfo><strong>getDefaultSites</strong>() {<strong>return</strong>Collections.singletonList(<strong>new</strong><strong>SiteInfo</strong>() {{ setUrl("http://vk.com/"); }}); } }<p><strong>Слегка подрезюмируем:</strong></p>
56
package ru.otus.democlient.service;<strong>import</strong>com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;<strong>import</strong>org.springframework.beans.factory.annotation.Value;<strong>import</strong>org.springframework.cache.annotation.Cacheable;<strong>import</strong>org.springframework.core.ParameterizedTypeReference;<strong>import</strong>org.springframework.http.HttpMethod;<strong>import</strong>org.springframework.retry.annotation.Backoff;<strong>import</strong>org.springframework.retry.annotation.Retryable;<strong>import</strong>org.springframework.stereotype.Service;<strong>import</strong>org.springframework.web.client.RestTemplate;<strong>import</strong>java.util.Collections;<strong>import</strong>java.util.List; @Service public<strong>class</strong><strong>SiteServiceRest</strong><strong>implements</strong><strong>SiteService</strong>{ private final RestTemplate restTemplate; private final String serverUrl; public<strong>SiteServiceRest</strong>( RestTemplate restTemplate, @Value("${application.server.url}") String serverUrl ) { this.restTemplate = restTemplate; this.serverUrl = serverUrl; } @Override public List<SiteInfo><strong>findAllBlockedSites</strong>() {<strong>return</strong>restTemplate.exchange( serverUrl + "/blocked-sites", HttpMethod.GET, null,<strong>new</strong>ParameterizedTypeReference<List<SiteInfo>>() { } ).getBody(); } public List<SiteInfo><strong>getDefaultSites</strong>() {<strong>return</strong>Collections.singletonList(<strong>new</strong><strong>SiteInfo</strong>() {{ setUrl("http://vk.com/"); }}); } }<p><strong>Слегка подрезюмируем:</strong></p>
57
<ol><li>Запросы делаются через RestTemplate.</li>
57
<ol><li>Запросы делаются через RestTemplate.</li>
58
<li>RestTemplate можно настраивать, и это обычный бин.</li>
58
<li>RestTemplate можно настраивать, и это обычный бин.</li>
59
<li>Jackson используется для маппинга JSON в объекты.</li>
59
<li>Jackson используется для маппинга JSON в объекты.</li>
60
<li>Дальше - только ваш полёт фантазии (подробности о запуске клиента есть в видео).</li>
60
<li>Дальше - только ваш полёт фантазии (подробности о запуске клиента есть в видео).</li>
61
-
</ol><p>Коллеги, вебинар получился очень содержательным, поэтому, чтобы ничего не пропустить, лучше смотрите его полностью. Вы попробуете "в боевых условиях" реальное API, добавите @Cacheable на сервис, поработаете со Spring Retry, узнаете о Hystrix и много чего ещё. Такж�� мы приглашаем вас на <a>очередной вебинар</a> по Spring.</p>
61
+
</ol><p>Коллеги, вебинар получился очень содержательным, поэтому, чтобы ничего не пропустить, лучше смотрите его полностью. Вы попробуете "в боевых условиях" реальное API, добавите @Cacheable на сервис, поработаете со Spring Retry, узнаете о Hystrix и много чего ещё. Также мы приглашаем вас на <a>очередной вебинар</a> по Spring.</p>
62
62