JavaRush /Курси /Модуль 5. Spring /Конфігурація контейнерів Java-кодом

Конфігурація контейнерів Java-кодом

Модуль 5. Spring
Рівень 4 , Лекція 1
Відкрита

Основні поняття: @Bean та @Configuration

Центральними артефактами в нових засобах підтримки Java-конфігурації в Spring є класи, позначені анотацією @Configuration, та методи, позначені анотацією @Bean.

Аннотація @Bean використовується для зазначення того, що метод створює екземпляр, конфігурує та ініціалізує новий об'єкт, яким керуватиме IoC-контейнер Spring. Для тих, хто знайомий з XML-конфігурацією <beans/> зі Spring, анотація @Bean відіграє ту ж роль, що й елемент <bean/>. Ти можеш використовувати методи, анотовані @Bean, з будь-якою анотацією @Component зі Spring. Однак найчастіше вони використовуються з бінами, анотованими @Configuration.

Анотування класу за допомогою @Configuration вказує на те, що його основне призначення — бути джерелом визначень біну. Більш того, класи, анотовані @Configuration, дозволяють визначати міжбінові залежності шляхом виклику інших методів @Bean у тому самому класі. Найпростіший можливий клас, анотований @Configuration, виглядає так:

Java

@Configuration
public class AppConfig {
    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}
Kotlin

@Configuration
class AppConfig {
    @Bean
    fun myService(): MyService {
        return MyServiceImpl()
    }
}

Попередній клас AppConfig еквівалентний наступному XML-класу <beans/> зі Spring:


<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
Повна @Configuration проти "полегшеного" режиму @Bean?

Коли методи @Bean оголошуються в класах, не анотованих @Configuration, їх називають такими, що обробляються в "полегшеному" режимі. Методи бінів, оголошені в анотації @Component або навіть у звичайному класі, вважаються "полегшеними", з іншою основною метою об'ємного класу, а метод, позначений анотацією @Bean, є там свого роду бонус. Наприклад, службові компоненти можуть надавати контейнеру подання управління через додатковий метод, анотований @Bean, для кожного класу відповідного компонента. У таких сценаріях методи, анотовані @Bean, є механізмом фабричних методів загального призначення.

На відміну від повної @Configuration, огорнені методи @Bean не можуть оголошувати міжбінові залежності. Натомість вони працюють із внутрішнім станом компонента, що містить їх, і в разі необхідності — з аргументами, які вони можуть оголосити. Тому такий метод, позначений анотацією @Bean, не повинен викликати інші методи, позначені анотацією @Bean. Кожен такий метод буквально є лише фабричним методом для конкретного посилання на бін, без будь-якої спеціальної семантики під час виконання. Позитивним побічним ефектом тут є те, що немає необхідності застосовувати підкласифікацію CGLIB під час виконання, тому немає жодних обмежень у плані проєктування класів (отже, охоплюючий клас може бути final і так далі).

У звичайних сценаріях методи, позначені анотацією @Bean, мають бути оголошені в класах, позначених анотацією @Configuration, що забезпечить постійне використання "повного" режиму та перенаправлення перехресних посилань на керування життєвим циклом контейнера. Це запобігає випадковому виклику одного і того ж методу, позначеного анотацією @Bean, через звичайний виклик Java, що допомагає зменшити кількість помилок, які важко знайти, які важко відстежити при роботі в "полегшеному" режимі.

Анотації @Bean та @Configuration докладно розглянуті в наступних розділах. Однак спочатку ми розглянемо різні способи створення контейнера Spring за допомогою конфігурації на основі Java.

Створення екземпляра контейнера Spring за допомогою AnnotationConfigApplicationContext

Наступні розділи описують AnnotationConfigApplicationContext, представлений у Spring 3.0. Ця універсальна реалізація інтерфейсу ApplicationContext здатна приймати на введення не тільки класи, позначені анотацією @Configuration, а й звичайні класи, анотовані @Component, а також класи, анотовані метаданими з JSR-330.

Якщо класи, позначені анотацією @Configuration, вказані як вхідні дані, сам клас, анотований @Configuration, реєструється як визначення біна, а всі оголошені в класі методи, позначені анотацією @Bean, також реєструються як визначення біна.

Якщо згадані класи, позначені анотацією @Component, або з JSR-330, вони реєструються як визначення бінів, і передбачається, що метадані впровадження залежностей, такі як @Autowired або @Inject, використовуються в цих класах, де це необхідно.

Проста конструкція

Так само, як XML-файли Spring використовуються як вхідні дані при створенні ClassPathXmlApplicationContext, можна використовувати класи, анотовані @Configuration, як вхідні дані при створенні AnnotationConfigApplicationContext. Це дозволяє використовувати контейнер Spring повністю без XML, як показано в наступному прикладі:

Java

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}
Kotlin

import org.springframework.beans.factory.getBean
fun main() {
    val ctx = AnnotationConfigApplicationContext(AppConfig::class.java)
    val myService = ctx.getBean<MyService>()
    myService.doStuff()
}

Як згадувалося раніше, AnnotationConfigApplicationContext не обмежується роботою лише з класами, анотованими @Configuration. Будь-який анотований @Component або JSR-330 клас можна надати як вхід до конструктора, як показано в наступному прикладі:

Java

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}
Kotlin

import org.springframework.beans.factory.getBean
fun main() {
    val ctx = AnnotationConfigApplicationContext(MyServiceImpl::class.java, Dependency1::class.java, Dependency2::class.java)
    val myService = ctx.getBean<MyService>()
    myService.doStuff()
}

У попередньому прикладі передбачається, що MyServiceImpl, Dependency1 та Dependency2 використовують анотації впровадження залежностей Spring, такі як анотація @Autowired.

Побудова контейнера програмно за допомогою register(Class<?>… )

Ти можеш створити AnnotationConfigApplicationContext за допомогою конструктора без аргументів, а потім налаштувати його за допомогою методу register(). Цей підхід є особливо корисним у програмній побудові AnnotationConfigApplicationContext. У цьому прикладі показано, як це зробити:

Java

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class, OtherConfig.class);
    ctx.register(AdditionalConfig.class);
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}
Kotlin

import org.springframework.beans.factory.getBean
fun main() {
    val ctx = AnnotationConfigApplicationContext()
    ctx.register(AppConfig::class.java, OtherConfig::class.java)
    ctx.register(AdditionalConfig::class.java)
    ctx.refresh()
    val myService = ctx.getBean <MyService>()
    myService.doStuff()
}

Увімкнення сканування компонентів за допомогою scan(String…)

Щоб увімкнути сканування компонентів,можна анотувати свій клас за допомогою @Configuration так:

Java

@Configuration
@ComponentScan(basePackages = "com. acme") 
public class AppConfig {
    // ...
}
  1. Ця анотація дозволяє сканувати компоненти.
Kotlin

@Configuration
@ComponentScan(basePackages = ["com.acme"]) 
class AppConfig {
    // ...
}
  1. Ця анотація дозволяє сканувати компоненти.

Досвідчені користувачі Spring можуть бути знайомі з еквівалентом XML-оголошення з простору імен context: у Spring, показаному в наступному прикладі:


<beans>
    <context:component-scan base-package="com.acme"/>
</beans>

У попередньому прикладі пакет com.acme сканується на пошук будь-яких класів, анотованих @Component, а ці класи реєструються як визначення біна Spring у контейнері. AnnotationConfigApplicationContext відкриває метод scan(String…) для надання такої ж функціональності сканування компонентів, як показано в наступному прикладі:

Java

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.acme");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}
Kotlin

fun main() {
    val ctx = AnnotationConfigApplicationContext()
    ctx.scan("com.acme")
    ctx.refresh()
    val myService = ctx.getBean<MyService>()
}
Пам'ятай, що класи @Configuration мета-анотовані за допомогою @Component, тому є кандидатами на сканування компонентів. У попередньому прикладі, якщо припустити, що AppConfig оголошено в пакеті com.acme (або в будь-якому іншому пакеті під ним), він перехоплюється під час виклику scan() . Після refresh() всі його методи, анотовані @Bean, обробляються та реєструються як визначення бінів у контейнері.

Підтримка вебдодатків за допомогою AnnotationConfigWebApplicationContext

До варіанта WebApplicationContext класу AnnotationConfigApplicationContext можна отримати доступ за допомогою AnnotationConfigWebApplicationContext. Можеш використовувати цю реалізацію у налаштуванні слухача сервлета ContextLoaderListener зі Spring, DispatcherServlet зі Spring MVC тощо. Наступний фрагмент web.xml конфігурує типовий вебдодаток у Spring MVC (зверни увагу на використання context-параметрів contextClass та init-параметрів):


<web-app>
    <!-- Налаштування ContextLoaderListener для використання AnnotationConfigWebApplicationContext
        замість стандартного XmlWebApplicationContext -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>
    <!-- Розташування конфігурації має складатися з одного або декількох розділених комами або пробілами
        повних імен класів, анотованих @Configuration. Повні імена пакетів можна також -->
    <context-param>
        <param-name>contextConfigLocation>/param-name>
        <param-value>com.acme.AppConfig</param-value>
    </context-param>
    <!-- Здійснюємо початкове завантаження контексту кореневої програми, як завжди, використовуючи ContextLoaderListener -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-classм
    </listener>
    <!-- Оголошуємо DispatcherServlet у Spring MVC, як завжди -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- Конфігуруємо DispatcherServlet на використання AnnotationConfigWebApplicationContext
            замість стандартного XmlWebApplicationContext -->
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            </param-value>
        </init-param>
            <!-- Знову ж таки, розташування конфігурації повинні складатися з одного або декількох розділених комами або пробілами
                і повних імен класів, анотованих @Configuration -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.acme.web.MvcConfig</param-value>
        </init-param>
    </servlet>
    <!-- зіставте всі запити для /app/* із сервлетом диспетчера -->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>
</web-app>
Для програмних випадків використання GenericWebApplicationContext може використовуватися як альтернатива AnnotationConfigWebApplicationContext. Подробиці див. у javadoc за GenericWebApplicationContext.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ