Ызкштп. Оглавление Введение 61. Что такое инверсия контроля (IoC) и внедрение зависимостей (DI) Как они реализованы в
Скачать 1.17 Mb.
|
} @Configuration public class HeroesConfig { @Bean public List () { List ArrayList<>(); result.add( new Terminator()); result.add( new Rambo()); return result; } } 16. Расскажите про аннотацию @Conditional Источники: Spring API - @Conditional Spring API - Interface Condition Spring - Conditionally Include @Configuration Classes or @Bean Methods Baeldung - Custom Auto-Configuration with Spring Boot Habr - Использование Conditional в Spring Часто бывает полезно включить или отключить весь класс @Configuration, @Component или отдельные методы @Bean в зависимости от каких-либо условий. Аннотация @Conditional указывает, что компонент имеет право на регистрацию в контексте только тогда, когда все условия соответствуют. Может применяться: ❖ над классами прямо или косвенно аннотированными @Component, включая классы @Configuration; ❖ над методами @Bean; ❖ как мета-аннотация при создании наших собственных аннотаций-условий. Условия проверяются непосредственно перед тем, как должно быть зарегистрировано BeanDefinition компонента, и они могут помешать регистрации данного BeanDefinition. Поэтому нельзя допускать, чтобы при проверке условий мы взаимодействовали с бинами (которых еще не существует), с их BeanDefinition-ами можно. Условия мы определяем в специально создаваемых нами классах, которые должны имплементировать функциональный интерфейс Condition с одним единственным методом, возвращающим true или false: boolean matches (ConditionContext context, AnnotatedTypeMetadata metadata) Создав свой класс и переопределив в нем метод matches() с нашей логикой, мы должны передать этот класс в аннотацию @Conditional в качестве параметра: @Configuration @Conditional (OurConditionClass.class) class MySQLAutoconfiguration { //... } Для того, чтобы проверить несколько условий, можно передать в @Conditional несколько классов с условиями: @Bean @Conditional (HibernateCondition.class, OurConditionClass.class) Properties additionalProperties () { //... } Если класс @Configuration помечен как @Conditional, то на все методы @Bean, аннотации @Import и аннотации @ComponentScan, связанные с этим классом, также будут распространяться указанные условия. Для более детальной настройки классов, аннотированных @Configuration, предлагается использовать интерфейс ConfigurationCondition. В одном классе - одно условие. Для создания более сложных условий можно использовать классы AnyNestedCondition, AllNestedConditions и NoneNestedConditions. В Spring Framework имеется множество готовых аннотаций (и связанных с ними склассами-условиями, имплементирующими интерфейс Condition), которые можно применять совместно над одним определением бина: Аннотация Описание ConditionalOnBean Условие выполняется, в случае если присутствует нужный бин в BeanFactory. ConditionalOnClass Условие выполняется, если нужный класс есть в classpath. ConditionalOnCloudPlatform Условие выполняется, когда активна определенная платформа. ConditionalOnExpression Условие выполняется, когда SpEL выражение вернуло положительное значение. ConditionalOnJava Условие выполняется, когда приложение запущено с определенной версией JVM. ConditionalOnJndi Условие выполняется, только если через JNDI доступен определенный ресурс. ConditionalOnMissingBean Условие выполняется, в случае если нужный бин отсутствует в контейнере. ConditionalOnMissingClass Условие выполняется, если нужный класс отсутствует в classpath. ConditionalOnNotWebApplication Условие выполняется, если контекст приложения не является веб контекстом. ConditionalOnProperty Условие выполняется, если в файле настроек заданы нужные параметры. ConditionalOnResource Условие выполняется, если присутствует нужный ресурс в classpath. ConditionalOnSingleCandidate Условие выполняется, если bean-компонент указанного класса уже содержится в контейнере и он единственный. ConditionalOnWebApplication Условие выполняется, если контекст приложения является веб контекстом. 17. Расскажите про аннотацию @ComponentScan Источники: Spring - Automatically Detecting Classes and Registering Bean Definitions Baeldung - Spring Component Scanning Baeldung - Spring @ComponentScan – Filter Types Аннотация @ComponentScan используется вместе с аннотацией @Configuration для указания пакетов, которые мы хотим сканировать на наличие компонентов, из которых нужно сделать бины. @ComponentScan без аргументов указывает Spring по умолчанию сканировать текущий пакет и все его подпакеты. Текущий пакет - тот, в котором находится файл конфигурации с этой самой аннотацией @ComponentScan. В данном случае в контейнер попадут: ❖ бин конфигурационного класса; ❖ бины, объявленные в конфигурационном классе с помощью @Bean; ❖ все бины из пакета и его подпакетов. Аннотация @SpringBootApplication включает в себя аннотации @ComponentScan, @SpringBootConfiguration и @EnableAutoConfiguration, но это не мешает разместить её ещё раз отдельно для указания конкретного пакета. Если указать @ComponentScan с атрибутом basePackages, то это изменит пакет по умолчанию на указанный: @ComponentScan (basePackages = "com.baeldung.componentscan.springapp.animals" ) @Configuration public class SpringComponentScanApp { // ... } Если указать @ComponentScan с атрибутом excludeFilters, то это позволит использовать фильтр и исключить ненужные классы из процесса сканирования: @ComponentScan (excludeFilters = @ComponentScan .Filter(type=FilterType.REGEX, pattern= "com\\.baeldung\\.componentscan\\.springapp\\.flowers\\..*" )) 18. Расскажите про аннотацию @Profile Источники: Spring - Bean Definition Profiles Spring API - @Profile Baeldung - Spring Profiles Mkyong - Spring Profiles example Профили — это ключевая особенность Spring Framework, позволяющая нам относить наши бины к разным профилям (логическим группам), например, dev, test, prod. Мы можем активировать разные профили в разных средах, чтобы загрузить только те бины, которые нам нужны. Используя аннотацию @Profile, мы относим бин к конкретному профилю. Её можно применять на уровне класса или метода. Аннотация @Profile принимает в качестве аргумента имя одного или нескольких профилей. Она фактически реализована с помощью гораздо более гибкой аннотации @Conditional. Рассмотрим базовый сценарий - у нас есть бин, который должен быть активным только во время разработки, но не должен использоваться в продакшене. Мы аннотируем этот компонент с профилем «dev», и он будет присутствовать в контейнере только во время разработки - во время продакшена профиль dev просто не будет активен: @Component @Profile ( "dev" ) public class DevDatasourceConfig В качестве быстрого обозначения имена профилей также могут начинаться с оператора NOT, например «!dev», чтобы исключить их из профиля. В приведенном ниже примере компонент активируется, только если профиль «dev» не активен: @Component @Profile ( "!dev" ) public class DevDatasourceConfig Следующим шагом является активация нужного профиля для того, чтобы в контейнере были зарегистрированы только бины, соответствующие данному профилю. Одновременно могут быть активны несколько профилей. По умолчанию, если профиль бина не определен, то он относится к профилю “default”. Spring также предоставляет способ установить профиль по умолчанию, когда другой профиль не активен, используя свойство «spring.profiles.default». В Spring Boot есть возможность иметь один файл настроек application.properties, в котором будут основные настройки для всех профилей, и иметь по файлу настроек для каждого профиля application-dev.properties и application-prod.properties, содержащие свои собственные дополнительные настройки. 19. Расскажите про ApplicationContext и BeanFactory, чем отличаются? В каких случаях что стоит использовать? Источники: Spring - The IoC Container Spring - BeanFactory or ApplicationContext? Baeldung - Difference Between BeanFactory and ApplicationContext BeanFactory BeanFactory — это интерфейс, который предоставляет механизм конфигурации, способный управлять объектами любого типа. В общем, BeanFactory предоставляет инфраструктуру конфигурации и основные функциональные возможности. BeanFactory легче по сравнению с ApplicationContext. ApplicationContext ApplicationContext является наследником BeanFactory и полностью реализует его функционал, добавляя больше специфических enterprise-функций. ApplicationContext vs. BeanFactory Feature BeanFactory ApplicationContext Bean instantiation/wiring Yes Yes Integrated lifecycle management No Yes Automatic BeanPostProcessor registration No Yes Automatic BeanFactoryPostProcessor registration No Yes Convenient MessageSource access (for internalization) No Yes Built-in ApplicationEvent publication mechanism No Yes 1. ApplicationContext загружает все бины при запуске, а BeanFactory - по требованию. 2. ApplicationContext расширяет BeanFactory и предоставляет функции, которые подходят для корпоративных приложений: a. поддержка внедрения зависимостей на основе аннотаций; b. удобный доступ к MessageSource (для использования в интернационализации ); c. публикация ApplicationEvent - для бинов, реализующих интерфейс ApplicationListener, с помощью интерфейса ApplicationEventPublisher; d. простая интеграция с функциями Spring AOP. 3. ApplicationContext поддерживает автоматическую регистрацию BeanPostProcessor и BeanFactoryPostProcessor. Поэтому всегда желательно использовать ApplicationContext, потому что Spring 2.0 (и выше) интенсивно использует BeanPostProcessor. 4. ApplicationContext поддерживает практически все типы scope для бинов, а BeanFactory поддерживает только два - Singleton и Prototype. 5. В BeanFactory не будут работать транзакции и Spring AOP. Это может привести к путанице, потому что конфигурация с виду будет корректной. 20. Расскажите про жизненный цикл бина, аннотации @PostConstruct и @PreDestroy() Источники: Spring - Introduction to the Spring IoC Container and Beans Spring API - BeanPostProcessor Shell26 - Bean Baeldung - PostConstruct and PreDestroy Annotations Spring-projects.ru - Урок 2: Введение в Spring IoC контейнер Habr - Этапы инициализации контекста Medium - Spring под капотом YouTube - Евгений Борисов — Spring-потрошитель, часть 1 YouTube - Евгений Борисов — Spring-потрошитель, часть 2 Жизненный цикл бинов 1. Парсирование конфигурации и создание BeanDefinition Цель первого этапа — это создание всех BeanDefinition. Объекты BeanDefinition — это набор метаданных будущего бина, макет, по которому нужно будет создавать бин в случае необходимости. То есть для каждого бина создается свой объект BeanDefinition, в котором хранится описание того, как создавать и управлять этим конкретным бином. Проще говоря, сколько бинов в программе - столько и объектов BeanDefinition, их описывающих. BeanDefinition содержат (среди прочего) следующие метаданные: 1. Имя класса с указанием пакета: обычно это фактический класс бина. 2. Элементы поведенческой конфигурации бина, которые определяют, как бин должен вести себя в контейнере (scope, обратные вызовы жизненного цикла и т.д.). 3. Ссылки на другие bean-компоненты, которые необходимы для его работы. Эти ссылки также называются зависимостями. 4. Другие параметры конфигурации для установки во вновь созданном объекте - например, ограничение размера пула или количество соединений, используемых в бине, который управляет пулом соединений. Эти метаданные преобразуются в набор свойств, которые составляют каждое BeanDefinition. В следующей таблице описаны эти свойства: Свойство Ссылка с описанием Class Instantiating Beans Name Naming Beans Scope Bean Scopes Constructor arguments Dependency Injection Properties Dependency Injection Autowiring mode Autowiring Collaborators Lazy initialization mode Lazy-initialized Beans Initialization method Initialization Callbacks Destruction method Destruction Callbacks При конфигурации через аннотации с указанием пакета для сканирования или JavaConfig используется класс AnnotationConfigApplicationContext. Регистрируются все классы с @Configuration для дальнейшего парсирования, затем регистрируется специальный BeanFactoryPostProcessor, а именно BeanDefinitionRegistryPostProcessor, который при помощи класса ConfigurationClassParser парсирует JavaConfig, загружает описания бинов (BeanDefinition), создаёт граф зависимостей (между бинами) и создаёт: Map ConcurrentHashMap<>( 256 ); в которой хранятся все описания бинов, обнаруженных в ходе парсинга конфигурации. 2. Настройка созданных BeanDefinition После первого этапа у нас имеется коллекция Map, в которой хранятся BeanDefinition-ы. BeanFactoryPostProcessor-ы на этапе создания BeanDefinition-ов могут их настроить как нам необходимо. BeanFactoryPostProcessor-ы могут даже настроить саму BeanFactory ещё до того, как она начнет работу по созданию бинов. В интерфейсе BeanFactoryPostProcessor всего один метод: public interface BeanFactoryPostProcessor { void postProcessBeanFactory (ConfigurableListableBeanFactory beanFactory) throws BeansException; } 3. Создание кастомных FactoryBean (только для XML-конфигурации) 4. Создание экземпляров бинов Сначала BeanFactory из коллекции Map с объектами BeanDefinition достаёт те из них, из которых создаёт все BeanPostProcessor-ы, необходимые для настройки обычных бинов. Создаются экземпляры бинов через BeanFactory на основе ранее созданных BeanDefinition. 5. Настройка созданных бинов На данном этапе бины уже созданы, мы можем лишь их донастроить. Интерфейс BeanPostProcessor позволяет вклиниться в процесс настройки наших бинов до того, как они попадут в контейнер. ApplicationContext автоматически обнаруживает любые бины с реализацией BeanPostProcessor и помечает их как “post-processors” для того, чтобы создать их определенным способом. Например, в Spring есть реализации BeanPostProcessor-ов, которые обрабатывают аннотации @Autowired, @Inject, @Value и @Resource. Интерфейс несет в себе два метода: postProcessBeforeInitialization(Object bean, String beanName) и postProcessAfterInitialization(Object bean, String beanName). У обоих методов параметры абсолютно одинаковые. Разница только в порядке их вызова. Первый вызывается до init-метода, второй - после. Как правило, BeanPostProcessor-ы, которые заполняют бины через маркерные интерфейсы или тому подобное, реализовывают метод postProcessBeforeInitialization (Object bean, String beanName), тогда как BeanPostProcessor-ы, которые оборачивают бины в прокси, обычно реализуют postProcessAfterInitialization (Object bean, String beanName). Прокси — это класс-декорация над бином. Например, мы хотим добавить логику нашему бину, но джава-код уже скомпилирован, поэтому нам нужно на лету сгенерировать новый класс. Этим классом мы должны заменить оригинальный класс так, чтобы никто не заметил подмены. Есть два варианта создания этого класса: 1. либо он должен наследоваться от оригинального класса (CGLIB) и переопределять его методы, добавляя нужную логику; 2. либо он должен имплементировать те же самые интерфейсы, что и первый класс (Dynamic Proxy). По конвенции спринга, если какой-то из BeanPostProcessor-ов меняет что-то в классе, то он должен это делать на этапе postProcessAfterInitialization(). Таким образом мы уверены, что initMethod у данного бина, работает на оригинальный метод, до того, как на него накрутился прокси. Хронология событий: 1. Сначала сработает метод postProcessBeforeInitialization() всех имеющихся BeanPostProcessor-ов. 2. Затем, при наличии, будет вызван метод, аннотированный @PostConstruct. 3. Если бин имплементирует InitializingBean, то Spring вызовет метод afterPropertiesSet() - не рекомендуется к использованию как устаревший. 4. При наличии, будет вызван метод, указанный в параметре initMethod аннотации @Bean. 5. В конце бины пройдут через postProcessAfterInitialization (Object bean, String beanName). Именно на данном этапе создаются прокси стандартными BeanPostProcessor-ами. Затем отработают наши кастомные BeanPostProcessor-ы и применят нашу логику к прокси-объектам. После чего все бины окажутся в контейнере, который будет обязательно обновлен методом refresh(). 6. Но даже после этого мы можем донастроить наши бины ApplicationListener-ами. 7. Теперь всё. 6. Бины готовы к использованию Их можно получить с помощью метода ApplicationContext#getBean(). 7. Закрытие контекста Когда контекст закрывается (метод close() из ApplicationContext), бин уничтожается. Если в бине есть метод, аннотированный @PreDestroy, то перед уничтожением вызовется этот метод. Если бин имплементирует DisposibleBean, то Spring вызовет метод destroy() - не рекомендуется к использованию как устаревший. Если в аннотации @Bean определен метод destroyMethod, то будет вызван и он. @PostConstruct Spring вызывает методы, аннотированные @PostConstruct, только один раз, сразу после инициализации свойств компонента. За данную аннотацию отвечает один из BeanPostProcessor- ов. Метод, аннотированный @PostConstruct, может иметь любой уровень доступа, может иметь любой тип возвращаемого значения (хотя тип возвращаемого значения игнорируется Spring-ом), метод не должен принимать аргументы. Он также может быть статическим, но преимуществ такого использования метода нет, т.к. доступ у него будет только к статическим полям/методам бина, и в таком случае смысл его использования для настройки бина пропадает. Одним из примеров использования @PostConstruct является заполнение базы данных. Например, во время разработки нам может потребоваться создать пользователей по умолчанию. |