Доброго времени суток, Хабр.


Сегодня я решил представить вам перевод цикла статей для подготовки к Spring Professional Certification.


Это перевод только первой статьи, если он зайдет аудитории, я продолжу выпуск переводов.  


Зачем я это делаю, ведь уже есть куча профильных материалов?
  1. Часто в них информация не структурирована, не собрана, или не актуальна
  2. Молодые разработчики могут не знать английский. Этот цикл можно использовать не только для сертификации, но и для самообучения/повторения материалов.
  3. Этими материалами можно пользоваться при подготовке к собеседованию, т.к. они выстроены в виде вопросов и ответов.
  4. Важное, и самое главное преимущество — этот QA сделан из вопросов из  официального Study Guide от Pivotal. 

  • Некоторые вопросы, которые казались мне лишними или которых не было в гиде я осознанно упустил.

Оглавление
  1. Внедрение зависимостей, контейнер, IoC, бины
  2. AOP (аспектно-ориентированное программирование)
  3. JDBC, транзакции, JPA, Spring Data
  4. Spring Boot
  5. Spring MVC
  6. Spring Security
  7. REST
  8. Тестирование


Сразу напишу список источников, из которых автор брал материалы
  • Spring 5 Design Patterns
  • Spring in Action 4th edition
  • Spring Security — Third Edition
  • Core Spring 5 Certification in Detail by Ivan Krizsan
  • Spring Documentation and Spring API javadocs

И так, начнем.


Что такое внедрение зависимостей(DI) и в чем его преимущества?

Внедрение зависимостей — это специальный паттерн, который уменьшает связь между Spring компонентами. Таким образом, при применении DI, ваш код становится чище, проще, его становится легче понять и тестировать. 
Согласно паттерну DI, создание объектов для зависимостей переходит на фабрику или отдается третьей стороне. Это означает, что мы можем сосредоточиться на использовании этих объектов вместо их создания.


Преимущества DI
  • Уменьшенная связь между частями приложения
  • Улучшенное тестирование
  • Улучшенная архитектура приложения
  • Уменьшает шаблонный код
  • Стандартизирует разработку приложения

Почему для создания Spring beans рекомендуются интерфейсы?
  • Улучшенное тестирование. В тестах бин может быть заменен специальным объектом(mock или stub), который реализует интерфейс бина.
  • Позволяет использовать механизм динамических прокси из JDK(например, при создании репозитория через Spring Data)
  • Позволяет скрывать реализацию

Что такое application context?

В Spring Framework интерфейс org.springframework.factory.BeanFactory предоставляет фабрику для бинов, которая в то же время является IoC контейнером приложения. Управление бинами основано на конфигурации(java или xml).


Интерфейс org.springframework.context.ApplicationContext — это обертка над bean factory, предоставляющая некоторые дополнительные возможности, например AOP, транзакции, безопасность, i18n, и т.п.


Что такое контейнер и какой у него жизненный цикл?

Основа Spring Framework — контейнер, и наши объекты "живут" в этом контейнере.
Контейнер обычно создает множество объектов на основе их конфигураций и управляет их жизненным циклом от создания объекта до уничтожения.


Контейнер — это объект, реализующий интерфейс ApplicationContext.


Жизненный цикл контейнера
  1. Контейнер создается при запуске приложения
  2. Контейнер считывает конфигурационные данные
  3. Из конфигурационных данных создается описание бинов
  4. BeanFactoryPostProcessors обрабатывают описание бина
  5. Контейнер создает бины используя их описание
  6. Бины инициализируются — значения свойств и зависимости внедряются в бин
  7. BeanPostProcessor запускают методы обратного вызова(callback methods)
  8. Приложение запущено и работает
  9. Инициализируется закрытие приложения
  10. Контейнер закрывается
  11. Вызываются callback methods

Как создать экземпляр ApplicationContext?

Spring обеспечивает несколько разновидностей контекста. 


Есть несколько основных реализаций интерфейса ApplicationContext:


  • FileSystemXmlApplicationContext
  • ClassPathXmlApplicationContext
  • AnnotationConfigApplicationContext
  • XmlWebApplicationContext
  • AnnotationConfigWebApplicationContext

Примеры создания контекста:


ApplicationContext ctx = new FileSystemXmlApplicationContext(
                                     "c:/bean_properties.xml");

ApplicationContext ctx = new AnnotationConfigApplicationContext(
                            "com.springdemoapp.JavaConfig.class");

Можете ли вы описать жизненный цикл бина в контейнере?
  1. Загрузка описаний бинов, создание графа зависимостей(между бинами)
  2. Создание и запуск BeanFactoryPostProcessors
  3. Создание бинов
  4. Spring внедряет значения и зависимости в свойства бина
  5. Если бин реализует метод setBeanName() из интерфейса NameBeanAware, то ID бина передается в метод
  6. Если бин реализует BeanFactoryAware, то Spring устанавливает ссылку на bean factory через setBeanFactory() из этого интерфейса.
  7. Если бин реализует интерфейс ApplicationContextAware, то Spring устанавливает ссылку на ApplicationContext через setApplicationContext().
  8. BeanPostProcessor это специальный интерфейс(о нем ниже), и Spring позволяет бинам имплементировать этот интерфейс. Реализуя метод postProcessBeforeInitialization(), можно изменить экземпляр бина перед его(бина) инициализацией(установка свойств и т.п.)
  9. Если определены методы обратного вызова, то Spring вызывает их. Например, это метод, аннотированный @PostConstruct или метод initMethod из аннотации @Bean.
  10. Теперь бин готов к использованию. Его можно получить с помощью метода ApplicationContext#getBean().
  11. После того как контекст будет закрыт(метод close() из ApplicationContext), бин уничтожается.
  12. Если в бине есть метод, аннотированный @PreDestroy, то перед уничтожением вызовется этот метод. Если бин имплементирует DisposibleBean, то Spring вызовет метод destroy(), чтобы очистить ресурсы или убить процессы в приложении. Если в аннотации @Bean определен метод destroyMethod, то вызовется и он.

Как получить ApplicationContext в интеграционном тесте?

Если вы используете JUnit 5, то вам нужно указать 2 аннотации:


  • ```@ExtendWith(TestClass.class)``` — используется для указания тестового класса
  • ```@ContextConfoguration(classes = JavaConfig.class)``` — загружает java/xml конфигурацию для создания контекста в тесте

Можно использовать аннотацию @SpringJUnitConfig, которая сочетает обе эти аннотации.
Для теста веб-слоя можно использовать аннотацию @SpringJUnitWebConfig.


Как завершить работу контекста в приложении?

Если это не веб-приложение, то есть 2 способа:


  • Регистрация shutdown-hook с помощью вызова метода registerShutdownHook(), он также реализован в классе AbstractApplicationContext. Это предпочтительный способ.
  • Можно вызвать метод close() из класса AbstractApplicationContext.

В Spring Boot приложении:


  • Spring Boot самостоятельно зарегистрирует shutdown-hook за вас.

Что такое Java-конфигурация? Как она применяется?

Чтобы создать класс с конфигурацией на основе Java-кода, нужно аннотировать его с помощью
@Configuration
Этот класс будет содержать фабричные методы для создания бинов в контейнере.
Эти методы должны быть аннотированы аннотацией @Bean.


Пример:


@Configuration
public class DSConfig {
  @Bean
  public DataSource dataSource() {
      return DataSourceBuilder
          .create()
          .username("")
          .password("")
          .url("")
          .driverClassName("")
          .build();
  }
}

Этот класс поместит в контейнер экземпляр класса DataSource. Позднее его можно будет использовать при доступе к базе данных.


DI используя аннотации, сканирование классов

Component scanning(сканирование компонентов) — Spring автоматически обнаруживает бины, которые будут находиться в контейнере. Это бины с аннотациями-стереотипами.


Однако сканирование компонентов не включено по умолчанию.
Чтобы включить сканирование, аннотируйте @Configuration-класс аннотацией @ComponentScanning. Spring будет автоматически сканировать тот пакет, в котором находится этот класс и все его подпакеты.
Можно указать и другие пакеты для сканирования, и даже классы:


//сканирует 2 пакета
@Configuration(<i>basePackages</i> = {"soundsystem", "video"})

//сканирует класс
@Configuration(<i>basePackageClasses</i> = "MyClass.class")

Autowiring(внедрение) — Spring автоматически внедрит зависимости во время сканирования или помещения бина в контейнер.
Для внедрения зависимостей используется аннотация @Autowire.


Что такое stereotypes(аннотации-стереотипы)?

Стереотипы — это аннотации, обозначающие специальную функциональность.
Все стереотипы включают в себя аннотацию @Component.


Component Корневая аннотация, которая помечает класс как кандидат для автовнедрения
Controller Указывает, что класс является контроллером для отправления данных на фронт.
@RestController Указывает, что класс является контроллером для REST. 
Содержит аннотации Controller и @ResponseBody
Service Указывает, что класс является сервисом для выполнения бизнес-логики
Repository Указывает, что класс является репозиторием для работы с бд
@Configuration Указывает, что класс содержит Java-конфигурацию(@Bean-методы)

Какие существуют области видимости у бинов? Какая у них видимость по умолчанию?

Область видимости — scope, скоуп. Существует 2 области видимости по умолчанию.


Singleton
Область видимости по умолчанию. В контейнере находится всего 1 экземпляр бина
Prototype
В контейнере может находится любое количество экземпляров бина

И 4 области видимости в веб-приложении.


Request
Область видимости — 1 HTTP запрос. На каждый запрос создается новый бин
Session
Область видимости — 1 сессия. На каждую сессию создается новый бин
Application
Область видимости — жизненный цикл ServletContext
WebSocket
Область видимости — жизненный цикл WebSocket

Как создаются бины: сразу или лениво? Как изменить это поведение?

Singleton-бины обычно создаются сразу при сканировании.
Prototype-бины обычно создаются только после запроса.


Чтобы указать способ инициализации, можно использовать аннотацию @Lazy
Она ставится на @Bean-методы, на @Configuration-классы, или на @Component-классы.
В зависимости от параметра(true или false), который принимает аннотация, инициализация будет или ленивая, или произойдет сразу. По умолчанию(т.е. без указания параметра) используется true.


Что такое BeanFactoryPostProcessor и когда он используется?
  • BeanFactoryPostProcessor работает над описаниями бинов или конфигурационными метаданными перед тем, как бин будет создан.
  • Spring поставляет несколько полезных реализаций BeanFactoryPostProcessor, например, читающий property-файлы и получающий из них свойства бинов.
  • Вы можете написать собственную реализацию BFPP.

Зачем вам может понадобится static @Bean-метод?

Для того чтобы использовать кастомный BFPP. Вы можете переопределить механизм получения данных из метафайлов.


@Bean
public static PropertySourcesPlaceholderConfigurer pspc() {
    //создать, сконфигурировать и вернуть pspc
}

Опишите свойства аннотации @Bean
  • destroyMethod — указывает на метод обратного вызова. Метод находится в бине.
  • initMethod — указывает на метод обратного вызова. Метод находится в бине.
  • name — имя бина. По умолчанию именем бина является имя метода.
  • value — алиас для name()

Что такое BeanPostProcessor и чем он отличается от BeanFactoryPostProcessor?

Spring использует несколько BeanPostProcessor’ов. 
Например, CommonAnnotationPostProcessor или AutowiredAnnotationBeanPostProcessor.
BPP работает с экземплярами бинов, т.е. контейнер создает бин, а затем начинает работать BPP.



Что такое callback methods и как их использовать?

Есть 3 варианта для создания таких методов:


  • @PreDestroy и @PostConstruct аннотации
  • Параметры initMethod и destroyMethod в аннотации @Bean, указывающие на методы в классе бина
  • Переопределенные InitializingBean#afterPropertiesSet() и DisposableBean#destroy(). Для переопределения этих методов нужно имплементировать соответствующие интерфейсы.


Как можно использовать аннотацию @Autowire и в чем отличие между способами?

Ниже перечислены типы DI, которые могут быть использованы в вашем приложении:


  • Constructor DI
  • Setter DI
  • Field DI

DI через конструктор считается самым лучшим способом, т.к. для него не надо использовать рефлексию, а также он не имеет недостатков DI через сеттер.
DI через поле не рекомендуется использовать, т.к. для этого применяется рефлексия, снижающая производительность.
DI через конструктор может приводить к циклическим зависимостям. Чтобы этого избежать, можно использовать ленивую инициализацию бинов или DI через сеттер.


Опишите поведение аннотации @Autowired
  1. Контейнер определяет тип объекта для внедрения
  2. Контейнер ищет бины в контексте(он же контейнер), которые соответствуют нужному типу
  3. Если есть несколько кандидатов, и один из них помечен как @Primary, то внедряется он
  4. Если используется аннотации @Autowire + Qualifier, то контейнер будет использовать информацию из @Qualifier, чтобы понять, какой компонент внедрять
  5. В противном случае контейнер попытается внедрить компонент, основываясь на его имени или ID
  6. Если ни один из способов не сработал, то будет выброшено исключение

Контейнер обрабатывает DI с помощью AutowiredAnnotationBeanPostProcessor. В связи с этим, аннотация не может быть использована ни в одном BeanFactoryPP или BeanPP.


Если внедряемый объект массив, коллекция, или map с дженериком, то Spring внедрит все бины подходящие по типу в этот массив(или другую структуру данных). В случае с map ключом будет имя бина.


//параметр указывает, требуется ли DI
@Authowired(required = true/false)

Как произвести DI в private поле?

Вы можете использовать разные типы внедрения:


  • Конструктор
  • Сеттер
  • Field-injection
  • Value

Как использование @Qualifier дополняет @Autowired?

Spring предоставляет аннотацию Qualifier, чтобы преодолеть проблему неоднозначности при DI.


@Bean
@Qualifier("SomeClass1")
public SomeClass getField() {...}

//…

@Autowire
@Qualifier("SomeField1")
public SomeClass someField;

Если в контейнере есть несколько бинов одного типа(SomeClass), то контейнер внедрит именно тот бин, над @Bean-методом которого стоит соответствующий квалификатор. Также можно не ставить квалификатор на метод, а использовать имя бина в качестве параметра квалификатора.
Имя бина можно можно указать через параметр аннотации Bean, а по умолчанию это имя фабричного метода.


Что такое прокси-объекты и какие типы прокси-объектов может создавать Spring?

Прокси это специальный объект, который имеет такие же публичные методы как и бин, но у которого есть дополнительная функциональность. 
Два вида прокси:


  • JDK-proxy — динамическое прокси. API встроены в JDK. Для него необходим интерфейс
  • CGLib proxy — не встроен в JDK. Используется когда интерфейс объекта недоступен

Плюсы прокси-объектов:


  • Позволяют добавлять доп. логику — управление транзакциями, безопасность, логирование
  • Отделяет некоторый код(логирование и т.п.) от основной логики

Как внедряется singleton-бин?

Если в контейнере нет экземпляра бина, то вызывается @Bean-метод. Если экземпляр бина есть, то возвращается уже созданный бин.


Что такое профили? Какие у них причины использования?

При использовании Java-конфигурации вы можете использовать аннотацию @Profile.
Она позволяет использовать разные настройки для Spring в зависимости от указанного профиля.
Ее можно ставить на @Configuration и Component классы, а также на Bean методы.


Profile("!test")
//загружать со всеми прифилями, кроме теста

@Bean("dataSource")
@Profile("production")
public DataSource jndiDataSource() {...}

@Bean("dataSource")
@Profile("development")
public DataSource standaloneDataSource() {...}

Как внедрить простые значения в свойства в Spring?

Для этого можно использовать аннотацию @Value.
Такие значения можно получать из property файлов, из бинов, и т.п.


@Value("$some.key")
public String stringWithDefaultValue;

В эту переменную будет внедрена строка, например из property или из view.


Как обычно, просьба присылать правки или найденные ошибки в личку.

Комментарии (51)


  1. SeApps
    06.10.2019 16:18

    Вот прям то что надо. Автору респект


  1. shalomman
    06.10.2019 18:06

    Внедрение зависимостей — это специальный паттерн, который уменьшает связь между Spring компонентами (то есть между различными POJO).

    Я не думаю, что Spring компоненты являются POJO. И насколько мне известно, Spring IOC работает только между бинами, иначе говоря нельзя заинжектить что-то в обычный POJO если он не является бином.


    1. Anton23 Автор
      06.10.2019 18:27

      Ну, по факту это обычный класс с аннотациями. Я думаю тут написано это для того чтобы показать что бины — это почти обычные классы. Но возможно лучше удалить эту строку.


      1. mad_nazgul
        07.10.2019 08:06

        Бины могут быть обычными классами.
        Т.е. можно создать бин без аннотаций.


    1. mad_nazgul
      07.10.2019 08:06

      Любой POJO может быть бином, но не любой POJO является бином.
      Можно создать бин из любого POJO класса, потом его передавать (инжектить) в любой бин.
      Для того чтобы инжекция была «прозрачной», то рекомендуется делать её через конструктор.
      На крайний случай через метод, но никогда не через поле.


      1. TerraV
        07.10.2019 17:12

        Любой неабстрактный класс с конструктором, который Spring может зарезолвить из контекста можно сделать бином. Другое дело что далеко не всегда так поступать можно, равно как не стоит из POJO делать бин. Это антипаттерн. POJO это контейнер данных. Бин это stateful/stateless объект представляющий наружу некоторую функциональность. Какую функциональность может предоставить POJO? Насчет инъекций через конструктор тоже спорное утверждение. Да, так проще мокать в тестах но цена за это — усложнение графа инициализации бинов. И сидеть решать циклическую зависимость может быть очень невесело. На практике инъекции через поля могут быть допустимы, тем более что наружу не будут торчать кишки сеттеров, вообще не относящихся к бизнес-логике бина.


        1. mad_nazgul
          09.10.2019 08:43

          Как минимум бин настроек может быть POJO :-)

          Насчет инжекции через поля
          1) Нельзя написать unit-test
          2) Сильная привязка к контейнеру бинов

          Т.о. наша бизнес-логика может быть приколочена к фреймворку который мы используем.

          Это как бы моветон.


          1. TerraV
            09.10.2019 11:10

            Не согласен с обоими высказываниями. Начну со второго. Спринг тем и хорош что у него одна реализация, которая одинакова что в разработке что в продакшне. В отличии от JavaEE где как бы есть спецификация а потом ловишь глюки реализации, Спринг везде один. Нет второго фреймворка на который можно мигрировать Спринг-приложение. Не может быть слабой или сильной привязки к Спрингу. Она или есть или ее нет.

            Отсюда вытекает контраргумент к первому пункту. Инъекции всегда надо отдавать на откуп Спрингу. Начиная со Spring Boot 2.1 ввели очень правильные ограничения на переопределение бинов и т.п. Сейчас юнит-тесты делаются через @ContextConfiguration/@SpringBootTest (в зависимости от желаемого скоупа) и nested static @TestConfiguration класс. Где внутри определяются реализации бинов на замену. Таким образом прекрасно тестируются классы с инъекцией через поля.

            Более того, подобное тестирование покрывает сценарий поднятия версии Spring.


            1. mad_nazgul
              09.10.2019 11:42
              +1

              Я говорил о unit-тестировании.
              В большинстве тестов не нужно поднимать какой-либо контекст.
              Достаточно задать какие входные данные и что должны получить на выходе.
              Поднятие контекста достаточно тяжелая вещь.
              А мокирование некоторых бинов не совсем тривиальная задача.

              К чему приводит привязка к фреймворку я вижу сейчас.
              Когда то было принято решение использовать «стильно-модно-молодежный» jBoss Seam, теперь чтобы «спрыгнуть» лет 5 уже умершего фреймворка, надо будет затратить не понятно сколько времени.


              1. AstarothAst
                09.10.2019 11:57

                Я вообще избавился от тестов со спринговым контекстом, и тестирую свои бины так, словно это обычные классы. Вроде пока все идет хорошо :)


                1. TerraV
                  09.10.2019 13:13

                  Рано или поздно вы столкнетесь с ситуацией что все тесты зеленые а приложение не стартует (к примеру из-за циклических зависимостей). Или еще хуже, стартует на девелопменте а на продакшене нет (реальный случай между прочим)


                  1. mad_nazgul
                    09.10.2019 14:38

                    Ну интеграционные тесты никто не отменял, они тоже нужны.
                    Как минимум простой тест на поднятие контекста.
                    Грубо говоря unit-тестами мы проверяем, что логика(алгоритм) правильный.
                    И если на входе корректные данные, то на выходе получаем корректные данные.
                    Плюс можем оттестировать поведение при не корректных данных.

                    Интеграционные тесты показывают как «кубики» работают в связке.

                    Скажем так разные инструменты для разных задач.


                  1. AstarothAst
                    09.10.2019 14:40

                    Рано или поздно вы столкнетесь с ситуацией что все тесты зеленые а приложение не стартует (к примеру из-за циклических зависимостей).

                    Если приложение не стартует из-за циклических зависимостей, то приложение об этом и скажет, и даже граф нарисует — тестирование для этого не нужно. По сути дела каждый запуск приложения будет являться таким тестом, так какой смысл еще и самостоятельно писать эмуляцию этого поведения?

                    Или еще хуже, стартует на девелопменте а на продакшене нет (реальный случай между прочим)

                    А какие гарантии тут даст тестирование? Оно только добавит вариантов — на девеломенте и в тестах зеленое, в проде — красное. Или в проде и на девелопменте зеленое, но тесты красные. Тоже реальный случай, если что.


              1. TerraV
                09.10.2019 13:07

                Отлично, только в этом случае вы тестируете не пойми что. Жизненный цикл объекта в JUnit тестах у вас принципиально отличается от того что потом крутится в продакшене. А если еще усугубить это моками то получаются две параллельные вселенные. Если у вас есть бин ну и тестируйте его как бин. Если это не бин (нет никакого Autowiring) то и спорить не о чем.


                1. AstarothAst
                  09.10.2019 14:42

                  Не соглашусь. Если в ваш бин внедряется некий интерфейс, то никакой параллельной вселенной не будет — вы тестируете поведение своего бина, а не внедренных зависимостей, а ваш бин создался корректно, получив инжект зависимостей в конструктор, с той лишь разницей, что экземпляры этих зависимостей создал не спринг, а макита.


                  1. TerraV
                    09.10.2019 14:50

                    Отлично, в вашем бине есть @PostConstruct. Ваши действия? Эмулировать поведение Спринга? Или @PostConstruct от лукавого и его на равне с @PreDestroy лучше не использовать? И Value аннотации вешать на методы а не на поля. Ну чтоб уж вообще по-хардкору.


                    1. AstarothAst
                      09.10.2019 15:00
                      -1

                      Отлично, в вашем бине есть @PostConstruct. Ваши действия? Эмулировать поведение Спринга?

                      Вариант дернуть метод помеченный @PostConstruct прямо в ходе теста вами не рассматривается? Это все такой же метод, который можно и нужно протестировать, а не рассчитывать, что Спринг его просто вызовет. Ну, то есть @PostConstruct помогает вам вызвать метод в определенный момент времени, но никак не гарантирует, что он отработает правильно, так что тестировать его все равно придется, а раз так то в чем проблема его вызвать? Разве что эстетические чувства страдают.

                      на равне с @PreDestroy лучше не использовать?

                      Ни разу не был нужен — признаюсь, как на духу! В условиях, когда сервис могут снести по kill -9 как-то не пригодился.

                      И Value аннотации вешать на методы а не на поля

                      Value аннотации у меня не уходят дальше конфигурации. А в последнее время я от них и вовсе отказался, слишком не гибкий инструмент.


                      1. TerraV
                        09.10.2019 15:03

                        То есть вы за эмуляцию жизненного цикла спринга «в меру своего понимания». Ну ок, ваш выбор.


                        1. AstarothAst
                          09.10.2019 15:09
                          -1

                          Вообще-то по-моему очевидно, что я за тестирование своего кода, а не того, как спринг работает.


                          1. TerraV
                            09.10.2019 15:10

                            Вариант дернуть метод помеченный @PostConstruct прямо в ходе теста вами не рассматривается?

                            Ваше же предложение


                            1. AstarothAst
                              09.10.2019 15:17

                              Это тоже мое предложение:

                              Это все такой же метод, который можно и нужно протестировать


                1. mad_nazgul
                  09.10.2019 14:42
                  +2

                  Э-э-э зачем мне тестировать ВСЕ приложение, когда мне нужно протестировать только логику работы какого-то класса/метода?!
                  Для этого unit-тесты само то.
                  Понятно, что они не покрывают всей функциональности.
                  Но их писать легче и они дают определенную уверенность, что при внесении изменений что-то где-то не отвалиться.

                  Интеграционные тесты это совсем другая история.
                  Причем в основном грустная, т.к. при любом изменении они будут «падать».
                  Т.к. тестируется не маленькая часть чего-то, а практически все приложение.


                  1. TerraV
                    09.10.2019 14:44

                    А кто писал про ВСЕ приложение? То что можно управлять скопом это новость?

                    Мы уже вроде договорились что инъекция через конструктор небезопасна из-за циклических зависимостей. Сейчас обсуждаем инъекцию через сеттеры или через переопределение бинов в контексте. Специально для этого есть @TestConfiguration которая конечно несколько противоречива из-за поведения на паблик классах и на нестед статик классах.


                    1. AstarothAst
                      09.10.2019 14:52

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

                      Эту «опасность» выявляет первый же запуск приложения — контекст просто не взлетит. Пути решения, вроде, тоже хорошо известны — либо Lazy, либо инжектить как-то иначе. Преимущества работы тестов через Спринг все еще не очевидны.


                      1. TerraV
                        09.10.2019 15:02

                        Основное преимущество — стандартизация подхода. Если это бин, тестим через Спринг контекст. Если не бин — создаем вручну. Логика проста и понятна даже джуниору. Этот подход позволяет сократить стоимость владение, когда два внешне одинаковых класса тестируются по-разному. Спринг контекс отработает все постпроцессоры и даст наглядную картину как оно будет выглядеть в продакшене. Если вы создаете бин вручную и вручную же эмулируете жизненный цикл Спринга то у меня для вас плохие новости…


                        1. mad_nazgul
                          09.10.2019 15:11

                          Как раз основная идея бинов в Spring'е было то, что они ничего не знают о Spring'е.
                          Как минимум, когда вводили конфигурацию на основе xml.
                          Потом ввели конфигурацию на основе аннотации и граница была стерта. Бины узнали, что есть Spring.

                          ...
                          @Autowired
                          private ApplicationContext applicationContext;
                          ...
                          

                          Как вершина этого подхода.
                          Потом сделали конфигурацию на основе классов.
                          Что позволило опять скрыть от бина существование Spring'а.


                          1. TerraV
                            09.10.2019 15:22

                            В момент когда у вас появляется lifecycle вы уже не можете его игнорировать. Бины в Спринге подчиняются жизненному циклу хотите вы этого или нет. С этого момента у вас по-сути три варианта — 1. писать бин так чтобы жизненный цикл было можно игнорировать (тем самым отказываясь от использования части функциональности спринга), 2. тестировать используя Спринг контекст и делегируя ему нативное управление жизненным циклом либо 3. эмулировать жизненный цикл вручную


                            1. mad_nazgul
                              09.10.2019 15:29
                              -1

                              Э-э-э мухи отдельно, котлеты отдельно. Жизненный цикл бина это игрушки Spring'а и они меня мало интересуют в рамках бизнес-логики.
                              Основная задача unit-тестов бинов протестировать, что логика в классе правильная.
                              Сам бин о своем жизненном цикле знать ничего не должен.
                              Очень желательно, чтобы он и о Spring ничего не знал.

                              По хорошему, все описание жизненного цикла бина должно быть в кнофигурационном/ых классе/ах.


                        1. AstarothAst
                          09.10.2019 15:14

                          Спасибо за «плохие новости», однако все работает — и с постпроцессорами, и с тестированием через «ручное создание». Можете даже еще один минус поставить, возможно вам станет от этого легче.

                          Кстати, у меня вопрос — нынче тестовый контекст взлетает, когда собираешь не толстый готовый к деплою джарник, а всего-то библиотеку? Пару лет назад, когда выделял общую логику в отдельный джар этот самый тестовый контекст напрочь отказывался взлетать, то ли нервничал, что @SpringBootApplication найти не мог, то ли еще что… А ведь основная масса постпроцессоров именно туда и ушла. Так я от тестового контекста и отказался.


                    1. mad_nazgul
                      09.10.2019 15:04

                      Можно управлять и скопом.
                      Например скоуп работы с БД, это уже больше 80% приложения.
                      Остатки, это на http-контроллеры и интеграции со внешними системами.
                      А если поднимать веб-контекст — то это просто все приложение.

                      Насчет «циклических зависимостей».
                      Так в конструктор инжектится интерфейс.
                      И если создавать бины типа интерфейса, то Spring их замечательно проксирует и никаких проблем с циклическими зависимостями.


                      1. TerraV
                        09.10.2019 15:10

                        Если не секрет то как вы пишете JUnit тесты для БД?


                        1. mad_nazgul
                          09.10.2019 15:16

                          Э-э-э зачем?

                          Для БД приходиться писать Spring-овые тесты с поднятием контекста БД.

                          Т.к. в основном используется Spring Data Jpa.
                          То они нужны, чтобы правильно создать интерфейс для Repository. Ну или протестить Query и/или Query(native=true)


                        1. AstarothAst
                          09.10.2019 15:18

                          Юнит-тесты — для БД? Простите, а что они должны тестировать???


                          1. PqDn
                            09.10.2019 10:58
                            +1

                            вот ты придумал сложный запрос с кучей джойнами и агрегаторными функциями. Вот такое и желательно затестировать. Так в впринципе можно разработку запроса вести

                            Ведь рано или поздно в твой код придет Антон, и ченить попробует в запросе поменять


                            1. Anton23 Автор
                              09.10.2019 10:58
                              +1

                              А вот это обидно было :/


                            1. mad_nazgul
                              09.10.2019 15:30

                              Э-э-э как бы тестирование запросов это не задача unit-тестирования. Это немного другая задача.
                              Если действительно «сложный запрос», то для его тестирования нужно
                              1) Соответствующая БД (заменители типа H2 или Derby не подойдут)
                              2) Соответствующая структура БД (таблицы, ключи, внешние ключи, триггеры, ограничения и пр)
                              3) Соответствующие данные (Т.к. в зависимости от данных запрос легко может выдавать все что угодно)

                              Т.о. для тестирования запроса надо
                              1) Поднять инстанс БД
                              2) Накатить структуру БД
                              3) Накатить данные в БД

                              Чтобы это сделать быстро и безболезненно, скорее всего, нужен Docker.
                              В котором поднимается БД и на нее накатывается соответствующий дамп.
                              И уже к БД в докере делается запрос и проверяются результаты.

                              Вот это все как-то на unit-тест не похоже ни разу. :-)


                            1. AstarothAst
                              09.10.2019 17:38

                              Все так, просто этому не место в юнит-тестах. Это вообще слой БД, так что есть вариант запихнуть это все в хранимую процедуру, а из приложения дергать уже ее. С другой стороны в чем-то я понимаю причины возникновения юнит-тестов, которые тестируют запросы — народ просто не знает где и как их еще писать, сами БД это дело никак не поддерживают и не поощряют, вот и оседает тестирование БД в юниттестах.


                      1. AstarothAst
                        09.10.2019 15:16

                        Насчет «циклических зависимостей».
                        Так в конструктор инжектится интерфейс.
                        И если создавать бины типа интерфейса, то Spring их замечательно проксирует и никаких проблем с циклическими зависимостями.

                        Это либо не так, либо не совсем так — намедни мне удалось создать такую закольцованную структуру. Почесал голову и начал думать, где у меня, собственно, дырка в логике, если получилась такая петля и кто на ком стоял.


                        1. mad_nazgul
                          09.10.2019 15:20

                          Вообще странно.
                          Насколько я учил.
                          Проблема с циклическая зависимостью может быть, только если инжектяться классы.
                          Когда работают через инжектиться все через интерфейсы, то в начале создается прокси, потом он разруливает по конкретным реализациям.

                          Сам недавно на такое натолкнулся.
                          Вместо интерфейса указал класс.


                          1. AstarothAst
                            09.10.2019 15:44

                            Ну, вот набросал, получается.

                            interface IA {
                            
                            }
                            
                            interface IB {
                            
                            }
                            
                            @Component
                            public class IAImpl implements IA {
                            
                                public IAImpl(@Autowired IB ib) {
                                }
                            }

                            
                            @Component
                            public class IBImpl implements IB {
                            
                                public IBImpl(@Autowired IA ia) {
                                }
                            }


                            При запуске выдает:
                            ***************************
                            APPLICATION FAILED TO START
                            ***************************

                            Description:

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

                            ------¬
                            | IAImpl defined in file [G:\.....\IAImpl.class]
                            ^ v
                            | IBImpl defined in file [G:\.....\IBImpl.class]
                            L------


                            1. mad_nazgul
                              09.10.2019 05:47

                              А можно и так
                              A:

                              public interface IA {
                                  public void testA();
                              }
                              ...
                              public class IAImpl implements IA {
                                  private IB ib;
                                  IAImpl(IB ib) {
                                      this.ib = ib;
                                  }
                                  public void testA() {
                                      System.out.println("TEST A");
                                      ib.testB();
                                  }
                              }
                              

                              B:
                              public interface IB {
                                  public void testB();
                              }
                              public class IBImpl implements IB {
                                  private IA ia;
                                  IBImpl(IA ia) {
                                      this.ia = ia;
                                  }
                                  public void testB() {
                                      Random random = new Random();
                                      if(random.nextBoolean()) {
                                          System.out.println("TEST B");
                                      } else {
                                          ia.testA();
                                      }
                                  }
                              }
                              


                              А вся «грязь» будет здесь:
                              @SpringBootApplication
                              public class Main {
                                  @Bean
                                  public IA iaBean(@Lazy IB ibBean) {
                                      return new IAImpl(ibBean);
                                  }
                                  @Bean
                                  public IB ibBean(IA iaBean) {
                                      return new IBImpl(iaBean);
                                  }
                                  @Bean
                                  public CommandLineRunner commandLineRunner() {
                                      return new CommandLineRunner() {
                                          @Autowired
                                          private IA ia;
                                          @Autowired
                                          private IB ib;
                                          @Override
                                          public void run(String... args) throws Exception {
                                              ia.testA();
                                              System.out.println("--------------");
                                              ib.testB();
                                          }
                                      };
                                  }
                                  public static void main(String[] args) {
                                      SpringApplication.run(Main.class, args);
                                  }
                              }
                              


                              Т.о. ни IAImpl не знает о реализации IB, ни наоборот IBImpl не знает о реализации IA. Ни оба вместе не знают об Spring'е.
                              Все знание о поднятие контекста и разрешения зависимостей находиться в конфигурационном файле. Где проблемы «циклических» зависимостей решаются средствами Spring'а


                              1. AstarothAst
                                09.10.2019 09:19

                                Фактически ваше решение — добавление Lazy в конструктор, а оно сработает и в моем примере:

                                public IAImpl(@Lazy @Autowired IB ib) {
                                    }


                                Выносить это в конфигурацию или нет — вопрос открытый, тут от предпочтений. В данном случае тут по-моему вообще стоит подумать почему возникла кольцевая зависимость? Это ж где-то дырка в логике построения самого приложения, а Lazy просто костыль, который позволяет как-то ехать и не падать на старте.


                                1. mad_nazgul
                                  09.10.2019 15:23

                                  Скажем так. Проблемы поднятия контекста, когда он локализован только в конфигурационных файлах (классы или xml) видны более явно, чем если бы конфигурация была бы размазана тонким слоем по всему приложению.
                                  И тут уже в зависимости от задачи каждый сам решает как решать данную проблему.
                                  Либо меняя имплементацию сервисов. Либо изменяя правила поднятия контекста.


  1. AstarothAst
    06.10.2019 19:42
    +1

    Вроде все знаю, а повторить все равно приятно.


  1. kacetal
    07.10.2019 09:21

    А на сколько ценится такая сертификация не в контексте саморазвития, но у работадателей?


    1. Anton23 Автор
      07.10.2019 09:26

      Однозначно ценится, хоть и не является важным фактором. Некоторые госкомпании требуют сертификаты. Сертификация может сильно помочь при устройстве на работу когда опыта мало или нет совсем. Людям, уже достигшим чего-то в разработке сертификаты не нужны и не помогают (потому что их знания уже выше тех, которые проверяются при сертификации), просто для галочки. Сертификат даёт интервьюеру понять что человек точно знает определенные темы.


    1. PqDn
      07.10.2019 13:08

      в России такой сертификат не ценится (прежде всего из-за того, что нельзя его сдать в России)
      если идти в аутсорсинговую компанию, где тебя будут перепродавать другую компанию, то любой сертификат будет в плюс.

      По факту при собеседовании если показываешь свои знания, то работодателю глубоко наплевать на твое образование/сертификаты


      1. EjikVTumane
        09.10.2019 14:23

        Ну, справедливости ради, — до 2017 года их можно было сдавать в России. Через любого партнера Pearson VUE, коих хватает.

        Pearson VUE is no longer delivering exams for Pivotal. Find more information about Pivotal certification.
        Last updated 2017-07-13


  1. alladuh
    07.10.2019 11:21
    +1

    Спасибо за перевод!
    Весьма полезный материал


  1. PqDn
    07.10.2019 13:14

    CGLib proxy — не встроен в JDK. Используется когда интерфейс объекта недоступен

    я бы тут добавил, что если в проекте опираться только на такие прокси, то в проекте можно отказаться от использования интерфейсов для спринг бинов. Но это должно быть прям осознаное решение.

    Почему для создания Spring beans рекомендуются интерфейсы?
    тут анологично бы добавил, что если написать свой бин пост процессор (или при подключении внешней библиотеки), где будет использоваться стандартная прокся jdk, то если инжекция идет на класс, то все нафиг ломается… Решение — перейти на инжекцию по интерфейсу или использовать cglib

    Что такое профили? Какие у них причины использования?
    Profile("!test") — я бы такой пример добавил, загружать со всеми прифилями, кроме теста


    1. Anton23 Автор
      07.10.2019 17:53

      Спасибо, дополню