HTML Diff
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&lt;BlockedSite&gt;<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&lt;BlockedSite&gt;<strong>blockedSites</strong>() { logger.info("Request has been performed");<strong>return</strong>repository.findAll(); } }<p>Также обратите внимание на вложенную БД в pom.xml:</p>
24 &lt;?xml version="1.0" encoding="UTF-8"?&gt; &lt;<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"&gt; &lt;<strong>modelVersion</strong>&gt;4.0.0&lt;/<strong>modelVersion</strong>&gt; &lt;<strong>parent</strong>&gt; &lt;<strong>groupId</strong>&gt;org.springframework.boot&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;spring-boot-starter-parent&lt;/<strong>artifactId</strong>&gt; &lt;<strong>version</strong>&gt;2.1.2.RELEASE&lt;/<strong>version</strong>&gt; &lt;<strong>relativePath</strong>/&gt; &lt;!-- lookup parent from repository --&gt; &lt;/<strong>parent</strong>&gt; &lt;<strong>groupId</strong>&gt;ru.otus&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;demo-server&lt;/<strong>artifactId</strong>&gt; &lt;<strong>version</strong>&gt;0.0.1-SNAPSHOT&lt;/<strong>version</strong>&gt; &lt;<strong>url</strong>&gt;demo-server&lt;/<strong>url</strong>&gt; &lt;<strong>description</strong>&gt;Demo project for Spring Boot&lt;/<strong>description</strong>&gt; &lt;<strong>properties</strong>&gt; &lt;<strong>java.version</strong>&gt;1.8&lt;/<strong>java.version</strong>&gt; &lt;/<strong>properties</strong>&gt; &lt;<strong>dependencies</strong>&gt; &lt;<strong>dependency</strong>&gt; &lt;<strong>groupId</strong>&gt;org.springframework.boot&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;spring-boot-starter-web&lt;/<strong>artifactId</strong>&gt; &lt;/<strong>dependency</strong>&gt; &lt;<strong>dependency</strong>&gt; &lt;<strong>groupId</strong>&gt;org.springframework.boot&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;spring-boot-starter-data-jpa&lt;/<strong>artifactId</strong>&gt; &lt;/<strong>dependency</strong>&gt; &lt;<strong>dependency</strong>&gt; &lt;<strong>groupId</strong>&gt;com.h2database&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;h2&lt;/<strong>artifactId</strong>&gt; &lt;/<strong>dependency</strong>&gt; &lt;<strong>dependency</strong>&gt; &lt;<strong>groupId</strong>&gt;org.springframework.boot&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;spring-boot-starter-test&lt;/<strong>artifactId</strong>&gt; &lt;<strong>scope</strong>&gt;test&lt;/<strong>scope</strong>&gt; &lt;/<strong>dependency</strong>&gt; &lt;/<strong>dependencies</strong>&gt; &lt;<strong>build</strong>&gt; &lt;<strong>plugins</strong>&gt; &lt;<strong>plugin</strong>&gt; &lt;<strong>groupId</strong>&gt;org.springframework.boot&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;spring-boot-maven-plugin&lt;/<strong>artifactId</strong>&gt; &lt;/<strong>plugin</strong>&gt; &lt;/<strong>plugins</strong>&gt; &lt;/<strong>build</strong>&gt; &lt;/<strong>project</strong>&gt;<p>Теперь простым и незатейливым образом сохраняем в нашу БД через репозиторий два заблокированных сайта (DemoServerApplication.java):</p>
24 &lt;?xml version="1.0" encoding="UTF-8"?&gt; &lt;<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"&gt; &lt;<strong>modelVersion</strong>&gt;4.0.0&lt;/<strong>modelVersion</strong>&gt; &lt;<strong>parent</strong>&gt; &lt;<strong>groupId</strong>&gt;org.springframework.boot&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;spring-boot-starter-parent&lt;/<strong>artifactId</strong>&gt; &lt;<strong>version</strong>&gt;2.1.2.RELEASE&lt;/<strong>version</strong>&gt; &lt;<strong>relativePath</strong>/&gt; &lt;!-- lookup parent from repository --&gt; &lt;/<strong>parent</strong>&gt; &lt;<strong>groupId</strong>&gt;ru.otus&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;demo-server&lt;/<strong>artifactId</strong>&gt; &lt;<strong>version</strong>&gt;0.0.1-SNAPSHOT&lt;/<strong>version</strong>&gt; &lt;<strong>url</strong>&gt;demo-server&lt;/<strong>url</strong>&gt; &lt;<strong>description</strong>&gt;Demo project for Spring Boot&lt;/<strong>description</strong>&gt; &lt;<strong>properties</strong>&gt; &lt;<strong>java.version</strong>&gt;1.8&lt;/<strong>java.version</strong>&gt; &lt;/<strong>properties</strong>&gt; &lt;<strong>dependencies</strong>&gt; &lt;<strong>dependency</strong>&gt; &lt;<strong>groupId</strong>&gt;org.springframework.boot&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;spring-boot-starter-web&lt;/<strong>artifactId</strong>&gt; &lt;/<strong>dependency</strong>&gt; &lt;<strong>dependency</strong>&gt; &lt;<strong>groupId</strong>&gt;org.springframework.boot&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;spring-boot-starter-data-jpa&lt;/<strong>artifactId</strong>&gt; &lt;/<strong>dependency</strong>&gt; &lt;<strong>dependency</strong>&gt; &lt;<strong>groupId</strong>&gt;com.h2database&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;h2&lt;/<strong>artifactId</strong>&gt; &lt;/<strong>dependency</strong>&gt; &lt;<strong>dependency</strong>&gt; &lt;<strong>groupId</strong>&gt;org.springframework.boot&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;spring-boot-starter-test&lt;/<strong>artifactId</strong>&gt; &lt;<strong>scope</strong>&gt;test&lt;/<strong>scope</strong>&gt; &lt;/<strong>dependency</strong>&gt; &lt;/<strong>dependencies</strong>&gt; &lt;<strong>build</strong>&gt; &lt;<strong>plugins</strong>&gt; &lt;<strong>plugin</strong>&gt; &lt;<strong>groupId</strong>&gt;org.springframework.boot&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;spring-boot-maven-plugin&lt;/<strong>artifactId</strong>&gt; &lt;/<strong>plugin</strong>&gt; &lt;/<strong>plugins</strong>&gt; &lt;/<strong>build</strong>&gt; &lt;/<strong>project</strong>&gt;<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 &lt;?xml version="1.0" encoding="UTF-8"?&gt; &lt;<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"&gt; &lt;<strong>modelVersion</strong>&gt;4.0.0&lt;/<strong>modelVersion</strong>&gt; &lt;<strong>parent</strong>&gt; &lt;<strong>groupId</strong>&gt;org.springframework.boot&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;spring-boot-starter-parent&lt;/<strong>artifactId</strong>&gt; &lt;<strong>version</strong>&gt;2.1.2.RELEASE&lt;/<strong>version</strong>&gt; &lt;<strong>relativePath</strong>/&gt; &lt;!-- lookup parent from repository --&gt; &lt;/<strong>parent</strong>&gt; &lt;<strong>groupId</strong>&gt;ru.otus&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;demo-client&lt;/<strong>artifactId</strong>&gt; &lt;<strong>version</strong>&gt;0.0.1-SNAPSHOT&lt;/<strong>version</strong>&gt; &lt;<strong>url</strong>&gt;demo-client&lt;/<strong>url</strong>&gt; &lt;<strong>description</strong>&gt;Demo project for Spring Boot&lt;/<strong>description</strong>&gt; &lt;<strong>properties</strong>&gt; &lt;<strong>java.version</strong>&gt;1.8&lt;/<strong>java.version</strong>&gt; &lt;/<strong>properties</strong>&gt; &lt;<strong>dependencies</strong>&gt; &lt;<strong>dependency</strong>&gt; &lt;<strong>groupId</strong>&gt;org.springframework.boot&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;spring-boot-starter&lt;/<strong>artifactId</strong>&gt; &lt;/<strong>dependency</strong>&gt; &lt;!-- Это для RestTemplate, это ещё не веб-приложение --&gt; &lt;<strong>dependency</strong>&gt; &lt;<strong>groupId</strong>&gt;org.springframework&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;spring-web&lt;/<strong>artifactId</strong>&gt; &lt;<strong>version</strong>&gt;5.1.4.RELEASE&lt;/<strong>version</strong>&gt; &lt;/<strong>dependency</strong>&gt; &lt;<strong>dependency</strong>&gt; &lt;<strong>groupId</strong>&gt;com.fasterxml.jackson.core&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;jackson-annotations&lt;/<strong>artifactId</strong>&gt; &lt;<strong>version</strong>&gt;2.9.8&lt;/<strong>version</strong>&gt; &lt;/<strong>dependency</strong>&gt; &lt;<strong>dependency</strong>&gt; &lt;<strong>groupId</strong>&gt;com.fasterxml.jackson.core&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;jackson-core&lt;/<strong>artifactId</strong>&gt; &lt;<strong>version</strong>&gt;2.9.8&lt;/<strong>version</strong>&gt; &lt;/<strong>dependency</strong>&gt; &lt;<strong>dependency</strong>&gt; &lt;<strong>groupId</strong>&gt;com.fasterxml.jackson.core&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;jackson-databind&lt;/<strong>artifactId</strong>&gt; &lt;<strong>version</strong>&gt;2.9.8&lt;/<strong>version</strong>&gt; &lt;/<strong>dependency</strong>&gt; &lt;!-- Cache --&gt; &lt;<strong>dependency</strong>&gt; &lt;<strong>groupId</strong>&gt;org.springframework.boot&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;spring-boot-starter-cache&lt;/<strong>artifactId</strong>&gt; &lt;/<strong>dependency</strong>&gt; &lt;!-- Retry --&gt; &lt;<strong>dependency</strong>&gt; &lt;<strong>groupId</strong>&gt;org.springframework.retry&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;spring-retry&lt;/<strong>artifactId</strong>&gt; &lt;/<strong>dependency</strong>&gt; &lt;<strong>dependency</strong>&gt; &lt;<strong>groupId</strong>&gt;org.aspectj&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;aspectjweaver&lt;/<strong>artifactId</strong>&gt; &lt;/<strong>dependency</strong>&gt; &lt;!-- Hystrix --&gt; &lt;<strong>dependency</strong>&gt; &lt;<strong>groupId</strong>&gt;org.springframework.cloud&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;spring-cloud-starter-netflix-hystrix&lt;/<strong>artifactId</strong>&gt; &lt;<strong>version</strong>&gt;2.0.2.RELEASE&lt;/<strong>version</strong>&gt; &lt;/<strong>dependency</strong>&gt; &lt;<strong>dependency</strong>&gt; &lt;<strong>groupId</strong>&gt;com.netflix.hystrix&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;hystrix-javanica&lt;/<strong>artifactId</strong>&gt; &lt;<strong>version</strong>&gt;1.5.12&lt;/<strong>version</strong>&gt; &lt;/<strong>dependency</strong>&gt; &lt;/<strong>dependencies</strong>&gt; &lt;<strong>build</strong>&gt; &lt;<strong>plugins</strong>&gt; &lt;<strong>plugin</strong>&gt; &lt;<strong>groupId</strong>&gt;org.springframework.boot&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;spring-boot-maven-plugin&lt;/<strong>artifactId</strong>&gt; &lt;/<strong>plugin</strong>&gt; &lt;/<strong>plugins</strong>&gt; &lt;/<strong>build</strong>&gt; &lt;/<strong>project</strong>&gt;<p>У клиента есть конфигурация:</p>
52 &lt;?xml version="1.0" encoding="UTF-8"?&gt; &lt;<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"&gt; &lt;<strong>modelVersion</strong>&gt;4.0.0&lt;/<strong>modelVersion</strong>&gt; &lt;<strong>parent</strong>&gt; &lt;<strong>groupId</strong>&gt;org.springframework.boot&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;spring-boot-starter-parent&lt;/<strong>artifactId</strong>&gt; &lt;<strong>version</strong>&gt;2.1.2.RELEASE&lt;/<strong>version</strong>&gt; &lt;<strong>relativePath</strong>/&gt; &lt;!-- lookup parent from repository --&gt; &lt;/<strong>parent</strong>&gt; &lt;<strong>groupId</strong>&gt;ru.otus&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;demo-client&lt;/<strong>artifactId</strong>&gt; &lt;<strong>version</strong>&gt;0.0.1-SNAPSHOT&lt;/<strong>version</strong>&gt; &lt;<strong>url</strong>&gt;demo-client&lt;/<strong>url</strong>&gt; &lt;<strong>description</strong>&gt;Demo project for Spring Boot&lt;/<strong>description</strong>&gt; &lt;<strong>properties</strong>&gt; &lt;<strong>java.version</strong>&gt;1.8&lt;/<strong>java.version</strong>&gt; &lt;/<strong>properties</strong>&gt; &lt;<strong>dependencies</strong>&gt; &lt;<strong>dependency</strong>&gt; &lt;<strong>groupId</strong>&gt;org.springframework.boot&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;spring-boot-starter&lt;/<strong>artifactId</strong>&gt; &lt;/<strong>dependency</strong>&gt; &lt;!-- Это для RestTemplate, это ещё не веб-приложение --&gt; &lt;<strong>dependency</strong>&gt; &lt;<strong>groupId</strong>&gt;org.springframework&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;spring-web&lt;/<strong>artifactId</strong>&gt; &lt;<strong>version</strong>&gt;5.1.4.RELEASE&lt;/<strong>version</strong>&gt; &lt;/<strong>dependency</strong>&gt; &lt;<strong>dependency</strong>&gt; &lt;<strong>groupId</strong>&gt;com.fasterxml.jackson.core&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;jackson-annotations&lt;/<strong>artifactId</strong>&gt; &lt;<strong>version</strong>&gt;2.9.8&lt;/<strong>version</strong>&gt; &lt;/<strong>dependency</strong>&gt; &lt;<strong>dependency</strong>&gt; &lt;<strong>groupId</strong>&gt;com.fasterxml.jackson.core&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;jackson-core&lt;/<strong>artifactId</strong>&gt; &lt;<strong>version</strong>&gt;2.9.8&lt;/<strong>version</strong>&gt; &lt;/<strong>dependency</strong>&gt; &lt;<strong>dependency</strong>&gt; &lt;<strong>groupId</strong>&gt;com.fasterxml.jackson.core&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;jackson-databind&lt;/<strong>artifactId</strong>&gt; &lt;<strong>version</strong>&gt;2.9.8&lt;/<strong>version</strong>&gt; &lt;/<strong>dependency</strong>&gt; &lt;!-- Cache --&gt; &lt;<strong>dependency</strong>&gt; &lt;<strong>groupId</strong>&gt;org.springframework.boot&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;spring-boot-starter-cache&lt;/<strong>artifactId</strong>&gt; &lt;/<strong>dependency</strong>&gt; &lt;!-- Retry --&gt; &lt;<strong>dependency</strong>&gt; &lt;<strong>groupId</strong>&gt;org.springframework.retry&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;spring-retry&lt;/<strong>artifactId</strong>&gt; &lt;/<strong>dependency</strong>&gt; &lt;<strong>dependency</strong>&gt; &lt;<strong>groupId</strong>&gt;org.aspectj&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;aspectjweaver&lt;/<strong>artifactId</strong>&gt; &lt;/<strong>dependency</strong>&gt; &lt;!-- Hystrix --&gt; &lt;<strong>dependency</strong>&gt; &lt;<strong>groupId</strong>&gt;org.springframework.cloud&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;spring-cloud-starter-netflix-hystrix&lt;/<strong>artifactId</strong>&gt; &lt;<strong>version</strong>&gt;2.0.2.RELEASE&lt;/<strong>version</strong>&gt; &lt;/<strong>dependency</strong>&gt; &lt;<strong>dependency</strong>&gt; &lt;<strong>groupId</strong>&gt;com.netflix.hystrix&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;hystrix-javanica&lt;/<strong>artifactId</strong>&gt; &lt;<strong>version</strong>&gt;1.5.12&lt;/<strong>version</strong>&gt; &lt;/<strong>dependency</strong>&gt; &lt;/<strong>dependencies</strong>&gt; &lt;<strong>build</strong>&gt; &lt;<strong>plugins</strong>&gt; &lt;<strong>plugin</strong>&gt; &lt;<strong>groupId</strong>&gt;org.springframework.boot&lt;/<strong>groupId</strong>&gt; &lt;<strong>artifactId</strong>&gt;spring-boot-maven-plugin&lt;/<strong>artifactId</strong>&gt; &lt;/<strong>plugin</strong>&gt; &lt;/<strong>plugins</strong>&gt; &lt;/<strong>build</strong>&gt; &lt;/<strong>project</strong>&gt;<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&lt;SiteInfo&gt;<strong>findAllBlockedSites</strong>() {<strong>return</strong>restTemplate.exchange( serverUrl + "/blocked-sites", HttpMethod.GET, null,<strong>new</strong>ParameterizedTypeReference&lt;List&lt;SiteInfo&gt;&gt;() { } ).getBody(); } public List&lt;SiteInfo&gt;<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&lt;SiteInfo&gt;<strong>findAllBlockedSites</strong>() {<strong>return</strong>restTemplate.exchange( serverUrl + "/blocked-sites", HttpMethod.GET, null,<strong>new</strong>ParameterizedTypeReference&lt;List&lt;SiteInfo&gt;&gt;() { } ).getBody(); } public List&lt;SiteInfo&gt;<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