Занятие №6

Spring Core

Михаил

Павлов

О чем мы поговорим сегодня

  • Что такое Spring Core (+ немного истории)
  • Основные составляющие:
    • Dependency Injection (+ Inversion of Control)
    • Жизненный цикл бина
    • Конфигурирование Spring приложения
    • AOP (Aspect Oriented Programming)

Что такое Spring Core?

Нет, погодите.

А что такое Spring?

Шел 2002 год

господствовала Java EE (Enterprise Edition)

*играет гнетущая музыка*

В чем была проблема Java EE?

Это приводило к следующим проблемам:

  • Общая сложность кода

  • Запутанность бизнес-логики

  • Потеря времени на реализацию служебных методов

  • Большое количество ошибок

Много инфраструктурного (служебного) кода.

Нужен легковесный фреймворк!

В октябре 2002 вышла книга Рода Джонсона с попыткой пересмотреть подход Java EE.

 

К книге прилагался репозиторий с первой версией фреймворка Spring

*Легковесность - невмешательство в бизнес-логику

Spring вырос очень быстро..

В чем легкость работы со Spring?

  • Работаем с POJO (plain old java object)
  • Объекты не должны ничего знать о фреймворке
  • Пишем бизнес-логику, а большинство служебных функций предоставляет Spring

Что входит в Spring Core

  • Bean и Spring IoC контейнер

  • Dependency Injection (DI)

  • Жизненный цикл бинов

  • Конфигурирование

  • Aspect Oriented Programming (AOP)

Spring IoC контейнер

Spring IoC контейнер - объект, отвечающий за хранение бинов и внедрение зависимостей (DI).

Spring IoC контейнер

Конфигурация

Бизнес-логика

Готовое приложение

*IoC - Invertion of Control (инверсия управления)

Начнем с бизнес-логики

Где она должна быть для Spring-приложения?

Ответ - бин

Bean (бин)

  • Обычный POJO (с бизнес-логикой)
  • Spring о нем знает и может управлять
  • Через Dependency Injection (DI) можно внедрять только бины

Прежде чем говорить о

Dependency Injection (DI)

надо разобрать

Inversion of Control (IoC)

Inversion of Control (IoC)

(Инверсия управления)

Класс сам не должен создавать нужные ему объекты

Суть принципа - убрать управление зависимостями из бизнес-логики, что позволяет делать более модульные приложения

IoC еще называют

"голливудским" принципом

"Не звоните нам, мы сами позвоним вам!"

Dependency Injection (DI)

(Внедрение зависимостей)

Паттерн, реализующий принцип Invertion of Control

Варианты внедрения:

  • Через Setter
  • Через конструктор

Задание: рассчитать цену на товар

Разберем на примере

Связи между классами для подсчета

Что не так с данным кодом?

private BigDecimal calculateProductPrice(Product product) {
    DataSource dataSource = new DataSource("url to db");
    ProductRepository productRepository =
        new ProductRepository(dataSource);
    ProductPriceRepository productPriceRepository =
        new ProductPriceRepository(dataSource);
    ProductService productService = new ProductServiceImpl();
    
    return productService.calculatePrice(product,
        productPriceRepository, productRepository);
}

Инфраструктурный код

"Полезный" код

Слишком крепкая связь

  • Мы жестко контролируем создание нужных классов
  • Много рефакторинга при изменении сигнатуры вызова расчетов

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

Пусть зависимости подключит кто-нибудь другой

 

Например Spring

(с помощью DI)

Варианты внедрения зависимостей

private ProductService productService;

public void setProductService(ProductService productService) {
    this.productService = productService;
}
private ProductService productService;

public PriceCalculator(ProductService productService) {
    this.productService = productService;
}

Setter

Конструктор

Какой способ лучше?

DI - через конструктор!

  • После создания у вас полностью сконфигурированный объект
  • Вы можете применить final (никто не сможет изменить поле)

Прямое внедрение зависимостей

private BigDecimal calculateProductPrice(Product product) {
    DataSource dataSource = new DataSource("url to db");
    ProductRepository productRepository =
        new ProductRepository(dataSource);
    ProductPriceRepository productPriceRepository =
        new ProductPriceRepository(dataSource);
    ProductService productService = new ProductServiceImpl();
    
    return productService.calculatePrice(product,
        productPriceRepository, productRepository);
}

* Вспомним, как было

Применяем DI через конструктор

Мы будем использовать уже готовый объект

private final ProductService productService;

public PriceCalculator(ProductService productService) {
    this.productService = productService;
}

private BigDecimal calculateProductPrice2(Product product) {
     return productService.calculatePrice(product,
        productPriceRepository, productRepository);
}

Циклические зависимости

APPLICATION FAILED TO START

Description:

The dependencies of some of the beans 
in the application context form a cycle:

   wsConfig defined in file
   [C:\gitlab\citizenfeedback\server\build\classes\
   java\main\pro\sisit\etalon\citizen\feedback\
   config\WsConfig.class]

|  syncServiceImpl defined in file 
  [C:\gitlab\citizenfeedback\server\build\classes
  \java\main\pro\sisit\etalon\citizen\
  feedback\sync\SyncServiceImpl.class]

Пример с циклической зависимостью

private final Class2 class2;

public Class1(Class2 class2) {
  this.class2 = class2;
}

-----------------------------------

private final Class1 class1;

public Class2(Class1 class1) {
  this.class1 = class1;
}

Class1

Class2

Для исправления циклических зависимостей

Class1

Class2

ClassLink

Class1

Class2

Пример с исправлением

public ClassLink() {}

-----------------------------------

private final ClassLink classLink;
private final Class2 class2;

public Class1(ClassLink classLink, 
              Class2 class2) {
  this.classLink = classLink;
  this.class2 = class2;
}

-----------------------------------

private final ClassLink classLink;

public Class2(ClassLink classLink) {
  this.classLink = classLink;
}

Class1

Class2

ClassLink

Пример с исправлением

public EntityRepository() {}
-----------------------------------
private final SynService synService;
private final EntityRepository entityRepository;

public EntityService(SynService synService,
                     EntityRepository entityRepository) {
  this.synService = synService;
  this.entityRepository = entityRepository;
}
-----------------------------------
private final EntityRepository entityRepository;

public SynService(EntityRepository entityRepository) {
  this.entityRepository = entityRepository;
}
private final SynService synService;

public EntityService(SynService synService) {
  this.synService = synService;
}
-----------------------------------
private final EntityService entityService;

public SynService(EntityService entityService) {
  this.entityService = entityService;
}

Что мы разобрали в Spring Core

  • Bean и Spring IoC контейнер

  • Dependency Injection (DI)

  • Жизненный цикл бинов

  • Конфигурирование

  • Aspect Oriented Programming (AOP)

Жизненный цикл бина

Основные стадии:

  • Инициализация
  • Жизнь
  • Уничтожение

* На самом деле стадий больше

Свойства бина

  • Имя (позволяет отличать экземпляры класса)
  • Scope (область действия / время жизни)
  • Хуки (обработчики событий) на инициализацию и уничтожение

Хранится в специальном интерфейсе BeanDefinition и грузится из конфигурации (о ней чуть позже)

Помним, что бин - это экземпляр класса бизнес-логики

*Помним про легковесность - нельзя хранить всё это в POJO

Scope (область действия / время жизни)

Основные виды бинов (по времени жизни):

  • Singleton - всегда один экземпляр
  • Prototype - новый экземпляр на каждый запрос внедрения зависимости

* Подробнее по ссылке

Scope - singleton

"- А мне говорили, что singleton это плохо!

- It depends..."

Потенциальные проблемы:

  • нужно корректно создать
  • центральная точка приложения, на которую все завязаны (риск отказа)
  • хранение состояния

Ограничения singleton

  • Используется для сервисов
  • Сервисы не должны хранить состояние (stateless)
  • Небезопасен при работе с множеством потоков, синхронизация - на вас

*За singleton следит сам Spring

Предпочтительный вариант сервиса - менеджер, который передает задачу другим классам

Сервис singleton-антипаттерн

(многопоточность)

public class ServiceAntiPattern {
    private BigDecimal debtSum;
    public BigDecimal сalculateDebtOnToday(Client client) {
        loadCurrentDebt(client);
        calculateNewDebt(client);
        return debtSum;
    }
    private void loadCurrentDebt(Client client) {
        debtSum = debtService.loadDebt(client);
    }
    private void calculateNewDebt(Client client) {
        debtSum = debtService.calculateDebtForClient(client, debtSum);
    }
}

поток 2

поток 1

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

Если нужно вставлять в класс бины и при этом хранить в классе состояние:

  • Используйте scope prototype
  • Либо создайте сервис - фабрику классов c состоянием, который будет передавать им зависимости

Хуки жизненного цикла бина

Жизненный цикл бина

Основные методы:

  • Внутри нашего класса
    • @PostConstruct
    • @PreDestroy
  • При определении бина
    • @Bean(initMethod = "")
    • @Bean(destroyMethod = "")

*также см. интерфейсы InitializingBean и DisposableBean (ссылка)

Что мы разобрали в Spring Core

  • Bean и Spring IoC контейнер

  • Dependency Injection (DI)

  • Жизненный цикл бинов

  • Конфигурирование

  • Aspect Oriented Programming (AOP)

Spring IoC контейнер

Spring IoC контейнер

Конфигурация

Бизнес-логика

Готовое приложение

  • Создать конфигурацию, описывающую свойства бинов
  • Загрузить в IoC контейнер конфигурацию

Виды конфигурирования

  • XML файлы
  • Java-классы
  • Аннотации

Конфигурация через XML

Сервис - выдающий домашних питомцев с их игрушками

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns="http://www.springframework.org/schema/beans"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
                      http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="PetServiceCat"
      class="com.github.siberianintegrationsystems.xml.CatShop" primary="true" >
        <constructor-arg type="com.github.siberianintegrationsystems.xml.Toy" 
                         ref="toy" />
    </bean>
    <bean id="PetServiceDog" class="com.github.siberianintegrationsystems.xml.DogShop" />
    <bean name="toy" class="com.github.siberianintegrationsystems.xml.Toy">
        <constructor-arg name="name" value="mouse" />
    </bean>
</beans>
public static void main(String[] args) {
        ApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("ApplicationXMLConfig.xml");

Итоги. XML

Плюсы

  • очень мощный инструмент
  • минимум воздействия на код

Минусы

  • XML для машин
  • лавинообразный рост XML для большого приложения
  • ошибки видим в runtime

Конфигурация через Java-классы

Объявляем бины в коде

Классы с конфигурацией помечаем @Configuration

@Configuration
public class JavaClassConfiguration {

    @Bean
    @Primary
    public PetService petServiceCat() {
        return new CatShop(new Toy("mouse"));
    }
}

Итоги. Java-классы

Плюсы

  • Пишем код (а не xml :) )
  • Есть проверка типов
  • Минимальное влияние на прикладной код

Минус

  • Нельзя на ходу поменять конфигурацию (приходится перекомпилировать всё приложение)

Конфигурация через аннотации

Аннотации служат для

  • Объявления компонентов: @Component, @Service
  • Сканирования пакетов для поиска объявленных компонентов @ComponentScan
  • Для конфигурирования @Configuration

Аннотации - демонстрация

Рассмотрим приложение для приветствий

 

Почему оно не работает?

Указание внедрения зависимости

Ранее мы явно определяли зависимости.

Этот код не говорит, что нужно инжектировать бин:

void setGreeterService(GreeterService greeterService) {
   this.greeterService = greeterService;
}

Выход - автопривязка @Autowired

Аннотация @Autowired на поле\сеттере\ конструкторе заставляет IoC искать подходящий бин.

 

На единственный конструктор в объекте спринг всегда вешает аннотацию @Autowired

Итоги. Аннотации

Плюс

  • Самый лаконичный способ

 

Минусы

  • Мы добавляем очень много аннотаций
  • Код зависит от фреймворка

* в Java EE добавили аннотацию @Inject, которую поддерживает и Spring, можно использовать ее вместо @Autowired.

Но на практике смена фреймворка трудна и затратна

Итоги. Конфигурирование

Мы рассмотрели все существующие способы конфигурирования

 

На практике мы используем смесь аннотаций и JavaBased

  • @Autowire для бизнес-логики
  • Явное указание бинов для имплементации интерфейсов внешних библиотек

Мозг

Информация

Что мы разобрали в Spring Core

  • Bean и Spring IoC контейнер

  • Dependency Injection (DI)

  • Жизненный цикл бинов

  • Конфигурирование

  • Aspect Oriented Programming (AOP)

AOP - aspect oriented programming

Позволяет использовать общий (типовой, повторяющийся) функционал в вашем коде с помощью аннотаций

 

Примеры:

  • транзакции
  • логирование
  • кэширование

Зачем нужен?

public void doSomething() {
    final String METHODNAME = "doSomething";
    logger.trace("entering " + CLASSNAME + "." + METHODNAME);
    TransactionStatus tx = transacationManager.getTransaction(
                                 new DefaultTransactionDefenition());
    try {
        // Business logic
        transactionManager.commit(tx);
    } catch (RuntimeException ex) {
        logger.error("exception in " + CLASSNAME  + "." + METHODNAME, ex);
        tx.setRollbackOnly();
    } finally {
        logger.trace("exiting " + CLASSNAME + "." + METHODNAME);
    }
}

AOP - пример

@Transactional
public void placeOrder(Order order) {
    order.statusOrdered();
    shipService.planShipping(order);
    orderRepository.saveOrder(order);
}

Как AOP работает

Ответ - Proxy

Spring создает класс-обертку вокруг вашего класса, с теми же методами.

 

При получении бина вы получаете прокси, который и делает сначала магию, а потом вызывает ваш код.

AOP - что происходит при вызове метода

Опасность AOP

!!!method2 вызовет не прокси!!!

public class MyServiceImpl {

  @Transactional
  public void method1() {
    //do something
    method2();
  }

  @Transactional(propagation=Propagation.REQUIRES_NEW)
  public void method2() {
    //do something
  }
}

Что мы разобрали в Spring Core

  • Bean и Spring IoC контейнер

  • Dependency Injection (DI)

  • Жизненный цикл бинов

  • Конфигурирование

  • Aspect Oriented Programming (AOP)

Полезные ссылки

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

Сегодня задания не будет

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

Занятие 6

By Павлов Михаил