Занятие №7

Spring Boot

Кернер

Денис

Давайте напишем программу!

Приложение - "Шутки", которое может

  • грузить шутки про Чака Норриса
  • сохранять их в базу

Как много нужно сделать?

  • Указать все нужные зависимости с версиями
  • Настроить подключение к базе данных
  • Создать все нужные бины
  • Настроить библиотеку логирования
  • Разработать интерфейс

А может это сделает кто-нибудь другой?

Spring Boot

Появление микросервисов потребовало ускорить создание проектов на Spring

 

Так появился Spring Boot 1.0 в апреле 2014

 

Схема обмена данными

Схема работы с приложением

Шаги

  • Настройка проекта - скачать архив
  • Добавить JokeRetriever
  • Добавить тест
  • Добавить Shell
  • Добавить JokeDataService
  • Добавить логирование
  • Добавить настройки по файлам
  • Добавить PostConstruct
  • Показать консоль h2

Настройка проекта Spring Initializr

Spring Starters (группы библиотек)

Добавить получение шуток

  • Создаем интерфейс JokeRetriever
  • Создаем имплементацию с помощью RestTemplate
  • Скачиваем результат как строку
  • Потом создаем DTO 
  • Маппинг автоматически делает спринг!

Интерфейс - получение шуток

public interface JokeRetriever {
    String getJoke();
}

Используем RestTemplate

@Service
public class RestJokeRetriever implements 
JokeRetriever {
    private final RestTemplate restTemplate;
    public RestJokeRetriever(RestTemplate restTemplate)
    { this.restTemplate = restTemplate; }

    @Override
    public String getJoke() {
        String url = "http://api.icndb.com/jokes/random";
        ResponseEntity<String> response = 
        restTemplate.getForEntity(url, String.class);
        return response.getBody();
    }
}

Добавить бин RestTemplate (httpClient)

@SpringBootApplication
@Configuration
public class JokesApplication {

   // ...
   @Bean
   public RestTemplate restTemplate() {
		return new RestTemplate();
   }

}

Добавить тест на получение шутки

@RunWith(SpringRunner.class)
@SpringBootTest(classes = JokesApplication.class)
public class RestJokeRetrieverTest {
    @Autowired
    JokeRetriever jokeRetriever;

    @Test
    public void joke() {
        String jokeText = jokeRetriever.getJoke()
        .value.getJoke();
        Assert.assertFalse(jokeText.isEmpty());
        Assert.assertTrue(jokeText.contains(
        				"Chuck Norris"));
    }
}

My name is shell. Spring shell.

Давайте общаться с пользователем через интерфейс командной строки.

 

Проект Spring Shell - позволяет вам уйти от обработки ввода и работать только с командами.

Подключаем зависимость

dependencies {
    // ...
	implementation
       'org.springframework.shell:spring-shell-starter:2.0.0.RELEASE'
    // ...
}

Пишем свои команды

@ShellComponent
public class ShellCommands {
    private final JokeRetriever jokeService;
    private String lastJoke;

    @ShellMethod("Get joke about Chuck Norris.")
    public String joke() {
        lastJoke = jokeService.getJoke().value.getJoke();
        return lastJoke;
    } 
}

Результат первой фазы

shell:>joke
Chuck Norris hosting is 101% uptime guaranteed.
shell:>joke
Product Owners never ask Chuck Norris for more features.
They ask for mercy.

Все отлично? Запустим тесты

Warning

Тест не запускается. Из-за Shell. Как исправить?

@RunWith(SpringRunner.class)
@SpringBootTest(
    classes = JokesApplication.class,
    properties = {
       InteractiveShellApplicationRunner
         .SPRING_SHELL_INTERACTIVE_ENABLED + "=false",
       ScriptShellApplicationRunner
         .SPRING_SHELL_SCRIPT_ENABLED + "=false"
    }
)
public class RestJokeRetrieverTest {

Что дальше?

А как хранить?

Как посмотреть все?

Добавить работу с данными

Добавим интерфейс JokeDataService. 

Как реализовать?

  • Подключить dependency
  • Использовать JdbcTemplate

Все остальное сделает Spring Boot

Как он это делает?

  1. @SpringBootApplication
  2. @EnableAutoConfiguration
  3. @Import(EnableAutoConfigurationImportSelector.class)
  4. spring.factories
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
@Configuration
@ConditionalOnClass(ObjectMapper.class)
public class JacksonAutoConfiguration {
	@Bean
	public JsonComponentModule jsonComponentModule() {
		return new JsonComponentModule();
	}

Правила выбора

  1. Пользовательские бины важнее автоматики
  2. Если есть зависимость в classpath, то Spring Boot попробует автоматически создать бин
  3. Если ваш бин не создается, то вы можете запустить Spring в режиме debug и получить отчет об автоконфигурации
java -jar jokes.jar --debug

Либо указав в настройках запуска

Для Idea community edition

Пример отчета

============================
CONDITIONS EVALUATION REPORT
============================


Positive matches:
-----------------

 CodecsAutoConfiguration.JacksonCodecConfiguration matched:
   - @ConditionalOnClass found required class 
   'com.fasterxml.jackson.databind.ObjectMapper' (OnClassCondition)

Работа с данными

@Override
public void save(String jokeText) {
    jdbcTemplate.update(
    "insert into jokes (joke) values (?)"
    , jokeText);
}

@Override
public List<String> getAll() {
    return jdbcTemplate.query(
     "select joke from jokes",
      (rs, rowNum) -> rs.getString("joke"));
}

А как создать схему?

jdbcTemplate может создать таблицу.

Как нам запустить код создания таблицы до старта приложения?

@PostConstruct
public void onPostConstruct() {
	jdbcTemplate.update(
    "create table jokes (joke text);");
}

Добавляем хранение и команды в shell

Реализуем хранение

Добавляем команды в spring shell

Проверяем, что работает просмотр команд

Данные стираются при перезапуске

Как сохранить?

 

 

Давайте сделаем еще лучше!

  • Добавим хранение в файле
  • Давайте использовать консоль h2!
  • http://localhost:8080/h2-console
  • Добавим логирование

 

Хранение в  файле

# путь к файлу
spring.datasource.url=jdbc:h2:file:./data/jokes
spring.datasource.driverClassName=org.h2.Driver
# логин
spring.datasource.username=sa
# пароль
spring.datasource.password=
# веб консоль
spring.h2.console.enabled=true

h2 консоль

Добавим логирование!

Нужно в классе с логированием объявить логгер

Нужно добавить настройки в приложении

logging.level.root=WARN
logging.level.io.github.bael.jokes=DEBUG
logging.file=jokes.log 
private final Logger logger = 
	LoggerFactory.getLogger(RestJokeRetriever.class);

Пишем в лог!

logger.debug("Result: {}", response.getBody());

Итоги

Как сделать лучше?

  • RestAPI (Spring Web) 
  • JPA и Spring Data (ORM)
  • LiquiBase
  • PostgreSql
  • SpringBoot Actuator

Как вам Spring Boot?

А домашнее задание?

Домашнее задание

  • Сделайте сервис который возвращает погоду по названию города. Командный интерфейс принимает название города как параметр.
  • API URL: https://community-open-weather-map.p.rapidapi.com/weather?units=metric&mode=json&q=Красноярск"; (Ключ для апи вышлем в чат)
  • Сохраняйте в базу результат строкой: дата + город + температура: 27.08.2019 Krasnoyarsk +25

Рекомендации формирования запроса

Конкретные заголовки мы скинем в чат

// используйте метод exchange RestTemplate. 
// на входу ему нужна RequestEntity, которую можно создать
// указав в конструкторе HTTP заголовки, HTTP метод и URI

// Заголовки создайте так:
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.add(HEADER_NAME, HEADER_VALUE);

// URI
String url = "http://example.com";
URI.create(url);

Работа с CLI - пример параметра команды

@ShellMethod("Get joke about Chuck Norris.")
public String joke(
    @ShellOption(defaultValue = "NERDY") 
        String category) {
    lastJoke = jokeService.getJoke(category).value.getJoke();
    return lastJoke;
}

Спасибо за внимание!

Занятие 7

By Сибирские интеграционные системы