Занятие №7

Spring Boot

Соколов

Никита

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

Чак Норрис настолько крут

Что про него напишут Java-приложение ...

"Шутки"

Грузит шутки про Чака Норриса
Сохраняет их в базу данных

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

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

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

Spring Boot

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

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

В чем отличие от Spring ( Core ) ?

А для чего вообще нужен Spring ?

Меньше шаблонного инфраструктурного кода в сравнении с программой просто на Java

В чем отличие Spring Boot от Spring ?

Меньше инфраструктурного кода Spring !

Концентрация на бизнес-логике, а не на конфигурации приложения!

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

Чак Норрис
настолько крут ...

Хочу шутку!

Случайная
Шутка

Случайная
Шутка

Случайная
Шутка

Получить шутку

Получить шутку

Чак одобряет!
Сохранить!

Сохранить последнюю шутку

Интерфейс

Сервис

API

Шаги

Инициализация проекта
Сервис получения шуток
Интерфейс пользователя
Сервис работы с данными
Логирование

Инициализация проекта

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

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

Создать интерфейс JokeService
Создать имплементацию с помощью RestTemplate
Проверить!

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

public interface JokeService {
  String getJoke();
}

Использовать RestTemplate

@Service
public class JokeServiceRestImpl implements JokeService {

  private final RestTemplate restTemplate;
  
  public JokeServiceRestImpl(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
public class JokesApplication {

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

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

@SpringBootTest
public class JokeServiceRestImplTest {

  @Autowired
  JokeService jokeService;

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

Где смеяться ?

> { "type": "success", "value": { "id": 31, "joke": "Chuck Norris has two speeds: Walk and Kill.", "categories": [] } }

Добавить транспортный объект

public class JokeDTO {
  private ValueDTO value;

  public ValueDTO getValue() {
    return value;
  }
}
public class ValueDTO {
  private String joke;

  public String getJoke() {
    return joke;
  }
}
> { "type": "success", "value": { "id": 31, "joke": "Chuck Norris has two speeds: Walk and Kill.", "categories": [] } }

Использовать DTO

@Service
public class JokeServiceRestImpl implements JokeService {

  ...
  
  @Override
  public String getJoke() {
    ResponseEntity<Joke> responseEntity =
      restTemplate.getForEntity(URL, Joke.class);
      
    return responseEntity.getBody().getValue().getJoke();
  }
}
> Chuck Norris crossed the road. No one has ever dared question his motives.

My name is shell. Spring shell.

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

 

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

!

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

dependencies {
  // ...
  compile 
    group: 'org.springframework.shell',
    name: 'spring-shell-starter',
  // ...
}

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

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

  @ShellMethod("Get joke about Chuck Norris.")
  public String joke() {
    lastJoke = jokeService.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();
  }

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

  • Пользовательские бины важнее автоматики
java -jar jokes.jar --debug

Если ваш бин не создается, то вы можете запустить Spring в режиме debug и получить отчет об автоконфигурации

  • Если есть зависимость в classpath, то Spring Boot попробует автоматически создать бин

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

Для Idea community edition

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

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


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

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

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

Создать интерфейс JokeDataService
Создать имплементацию с помощью JdbcTemplate
Добавить команды в shell

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

@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 IF NOT EXISTS jokes (joke text)");
}

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

Появилось хранение в БД
Есть команды в shell
Все работает
 
Данные стираются при перезапуске

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

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

Добавить хранение в файле
Воспользоваться консолью h2
Добавить логирование

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

# путь к файлу
spring.datasource.url=jdbc:h2:file:./data/jokes
spring.datasource.driverClassName=org.h2.Driver

# логин
spring.datasource.username=sa

# пароль
spring.datasource.password=sa

# веб консоль
spring.h2.console.enabled=true

h2 консоль

Адрес консоли по умолчанию: http://localhost:8080/h2-console

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

Объявить логгер в классе

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

logging.level.root = WARN
logging.level.pro.sisit.jokes = DEBUG
logging.file = jokes.log 
public class RestJokeRetriever {
/// ...
  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);
  return lastJoke;
}

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

JavaSIS#3.20 Занятие 7

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