Какие типы конфигурации поддерживает spring
Перейти к содержимому

Какие типы конфигурации поддерживает spring

  • автор:

Основы работы Spring: аннотация @Configuration

Аннотация @Configuration является частью основной структуры фреймворка Spring. Она указывает, что класс содержит методы определения @Bean. Таким образом, контейнер Spring может обрабатывать класс и генерировать Spring Beans, которые можно использовать в приложении.

Как работает @Configuration

@Configuration позволяет нам использовать аннотации для внедрения зависимостей. Давайте разберемся, как создавать классы конфигурации Spring.

Давайте создадим простой класс java bean.

package com.journaldev.spring; public class MyBean < public MyBean() < System.out.println("MyBean instance created"); >>

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

 org.springframework spring-context 5.0.6.RELEASE 

Теперь давайте создадим класс Configuration.

package com.journaldev.spring; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MyConfiguration < @Bean public MyBean myBean() < return new MyBean(); >>

Затем мы напишем простой класс и настроим наш configuration класс Spring.

package com.journaldev.spring; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class MySpringApp < public static void main(String[] args) < AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(MyConfiguration.class); ctx.refresh(); // MyBean mb1 = ctx.getBean(MyBean.class); // MyBean mb2 = ctx.getBean(MyBean.class); ctx.close(); >>

Если вы запустите приложение, которое мы создали выше, оно выдаст следующий результат:

May 23, 2018 12:34:54 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh INFO: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@ff5b51f: startup date [Wed May 23 12:34:54 IST 2018]; root of context hierarchy MyBean instance created May 23, 2018 12:34:54 PM org.springframework.context.support.AbstractApplicationContext doClose INFO: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@ff5b51f: startup date [Wed May 23 12:34:54 IST 2018]; root of context hierarchy

Обратите внимание: Spring загружает bean-компоненты в свой контекст еще до того, как мы его запросили. Это делается для того, чтобы убедиться, что все bean-компоненты правильно настроены, а приложение будет работать быстро, если что-то пойдет не так. Также необходимо вызвать ctx.refresh(), иначе мы получим следующую ошибку, если попытаемся извлечь из контекста любой компонент.

Exception in thread "main" java.lang.IllegalStateException: org.springframework.context.annotation.AnnotationConfigApplicationContext@f0f2775 has not been refreshed yet at org.springframework.context.support.AbstractApplicationContext.assertBeanFactoryActive(AbstractApplicationContext.java:1076) at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1106) at com.journaldev.spring.MySpringApp.main(MySpringApp.java:11)

Если вы раскомментируете операторы, в которых мы извлекаем экземпляры MyBean, вы заметите, что он не вызывает конструктор MyBean. Это связано с тем, что областью действия bean-компонентов в Spring по умолчанию является Singleton. Мы можем изменить эту область с помощью аннотации @Scope.

Что, если мы удалим аннотацию @Configuration?

Давайте посмотрим, что произойдет, если мы удалим аннотацию @Configuration из класса MyConfiguration.

Класс будет по-прежнему работать так, как ожидалось, а bean-компоненты будут регистрироваться и извлекаться как одноэлементные классы. Но если в этом случае мы вызовем метод myBean(),то у нас получится простой вызов метода Java, в результате чего мы извлечем новый экземпляр MyBean, который не будет одноэлементным. Чтобы доказать это, давайте определим другой компонент, который будет использовать экземпляр MyBean.

Руководство по Spring. Конфигурирование с помощью Java.

В предыдущих постах мы уже рассмотрели конфигурацию в Spring с помощью XML-файлов.

Но стоит упомянуть, что в Spring Framework поддерживается конфигурация с помощью Java, что временами бывает удобно. Это позволяет нам настроить большую часть Spring-приложения без использования конфигурационного файла XML, используя специальные аннотации.

В конфигурации с помощью аннотаций Java, ключевыми являются @Configuration и @Bean

@Configuration

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

@Bean

Аннотация @Bean, прописанная перед методом, информирует Spring о том, что возвращаемый данным методом объект должен быть зарегистрирован, как бин.

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

Исходный код проекта можно скачать по ЭТОЙ ССЫЛКЕ.

javaConfigStructure

javaConfigMessage

javaConfigMessageConfig

javaConfigMessageRunner

Результат работы программы

javaConfigResult

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

Исходный код проекта можно скачать по ЭТОЙ ССЫЛКЕ.

javaConfigExamStructure

javaConfigExam

javaConfigAnswerChecker

javaConfigExamConfig

javaConfigExamRunner

Результат работы программы

javaConfigExamResult

И в заключение приведём простые пример того, как должны выглядеть классы, если мы хотим настроить область видимости (scope) класса с помощью Java-аннотаций.

Настройка области видимости (scope) бина:

javaConfigScope

В этой статье мы ознакомились с основами конфигурации Spring-приложения с помощью Java-аннотаций.

Конфигурация Spring / Spring Boot или «Создаем ментальный фреймворк для Spring»

С архитектурой приложений часто возникают вопросы. Это касается как приложений пакетной обработки (batch job), веб-приложений, так и приложений с обменом сообщениями (messaging application) и других. Фреймворки, такие как Spring Batch, Spring Webflux и Spring Integration служат ориентиром в процессе принятия решения. Кроме того, существует множество специализированных фреймворков, предназначенных для определенной предметной области. Но в этом посте мы не будем о них говорить, а рассмотрим варианты конфигурации Spring.

Помните, что Spring — это большой мешок с объектами. Чтобы предоставлять сервисы, Spring должен знать, как организованы объекты: как они связаны между собой и как взаимодействуют. Например, Spring может стартовать транзакцию при входе в метод и завершить ее при выходе из метода. Или создать HTTP-эндпоинты, которые вызывают методы контроллеров при поступлении запросов. А также обрабатывать сообщения от брокеров, таких как Apache Kafka, AWS SQS, RabbitMQ и других. Список возможностей Spring можно продолжать и продолжать, но все это предполагает первоначальную регистрацию объектов в Spring.

Spring хранит метамодель ваших объектов — это что-то вроде Java Reflection API. Он знает, какие у классов есть аннотации и конструкторы. Также ему известно о зависимостях конкретного объекта: от каких бинов и типов зависит объект. Ваша задача — помочь ему построить эту метамодель для управления всеми вашими объектами. Например, при возможности управления созданием объектов, можно изменить процесс создания объектов до того, как они будут созданы.

Spring предоставит вам все эти сервисы только в случае, если будет знать, как объекты связаны между собой. Таким образом, идея состоит в том, что вы используете POJO (Plain Old Java Objects), а Spring ищет в них аннотации и использует их для настройки поведения ваших сервисов. Но, конечно, это не сделать без контроля создания объектов.

Spring за кулисами создает новый класс, который расширяет ваш. Делается это либо через создание Java InvocationHandler (JDK-прокси), либо, что бывает чаще, с помощью чего-то вроде CGLIB. Создаваемый класс является подклассом вашего класса. Итак, допустим, у вас есть следующий класс:

class CustomerService < private final JdbcTemplate template; CustomerService (JdbcTemplate jt) < this.JdbcTemplate = jt; >@Transactional public void updateCustomer ( long customerId, String name) < // .. . >>

Например, вам нужен автоматический запуск и завершение транзакции при каждом вызове метода updateCustomer . Чтобы это работало, Spring должен вставить свой код до и после вызова этого метода. За кулисами происходит примерно следующее:

class SpringEnhancedCustomerService extends CustomerService < // Spring provides a reference from the applicationContext of type JdbcTemplate SpringEnhancedCustomerService (JdbcTemplate jt) < super(JdbcTemplate ) ; >@Override public void updateCustomer (long customerId, String name) < // call Java code to start a JDBC transaction super.updateCustomer(customerId, name); // call Java code to stop a JDBC transaction >>

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

Итак, Spring должен знать о ваших объектах.

До появления Spring Boot у вас было два стандартных варианта конфигурации: XML и Java. Однако это было давно. В настоящее время XML не приветствуется, и остается Java-конфигурация. Вот пример класса конфигурации:

@Configuration class ServiceConfiguration < @Bean DataSource h2DataSource ()< return . ; >@Bean JdbcTemplate JdbcTemplate (DataSource ds) < return new JdbcTemplate(ds); >@Bean CustomerService customerService (JdbcTemplate jdbcTemplate) < return new CustomerService (jdbcTemplate); >> 

Здесь создается три объекта и они явно связываются между собой. Spring при старте ищет классы с аннотацией @Configuration , вызывает все методы, аннотированные @Bean , сохраняет все возвращаемые значения в контекст и делает их доступными для инъекции. Если метод принимает параметры, то он ищет другой метод, который возвращает значение нужного типа, и сначала вызывает его. Затем это значение инжектируется в метод через параметр. Если метод ранее уже вызывался для какой-то другой инъекции, то используется уже созданный экземпляр.

Преимущество такого подхода в его явности: вся информация о том, как ваши объекты связаны между собой, находится в одном месте — в классах конфигурации. Как вы заметили, информация о ваших классах присутствует в двух разных местах: в самом классе и в классе конфигурации.

Поэтому можно использовать другой, менее явный подход: сканирование компонентов (component-scanning). В этом случае Spring ищет классы в classpath с аннотациями стереотипов, таких как @Component или @Controller . Все аннотации стереотипов в конечном счете аннотируются @Component . Аннотация @Component — самая общая. Если вы посмотрите на @Controller , он аннотирован @Component . Если вы посмотрите на @RestController , то он тоже будет аннотирован @Controller . Класс, аннотированный @RestController , по-прежнему обрабатывается как минимум как класс, аннотированный @Component . Специализированные аннотации уточняют функциональность, но они по-прежнему остаются наследниками аннотации @Component , а не альтернативой ей.

Итак, кажется, описывать класс CustomerService в классе конфигурации будет лишним. В конце концов, если Spring знает только об этом классе, он дальше сам может разобраться в его связях, не так ли? Можно взглянуть на конструктор и увидеть, что для создания экземпляра CustomerService потребуется ссылка на JdbcTemplate , который определен в другом месте.

Сканирование компонентов это и делает. Вы можете добавить на класс аннотацию @Service — еще одну стереотипную аннотацию, помеченную @Component , — а затем удалить @Bean -метод в классе конфигурации. Spring автоматически создаст сервис и предоставит необходимые зависимости. Он также создаст подкласс для реализации необходимых сервисов.

Мы делаем успехи, удаляя все больше бойлерплейта. Но как насчет DataSource и JdbcTemplate? Они нам нужны, но не описывать же их каждый раз заново? В этом нам поможет Spring Boot. Для принятия решения о создании класса или вызова @Bean -метода Spring Boot использует аннотацию @Condition для декорирования классов с @Component или @Configuration . Решение может приниматься и на основе окружения. Например, у вас в classpath есть H2 (встраиваемая SQL-база данных) и библиотека spring-jdbc, которая содержит класс JdbcTemplate . При наличии этих классов в classpath можно сделать вывод о том, что вам нужен встроенный SQL DataSource и что вы хотите связать экземпляр JdbcTemplate с этим источником данных. Теперь можно полностью отказаться от класса @Configuration !

Мы рассмотрели основы конфигурации Spring IoC-контейнера. Можно было бы пойти намного дальше и поговорить об аспектно-ориентированном программировании (АОП), автоконфигурации и многом другом. Но об этом не сегодня. Цель этого поста — объяснить, когда применять тот или иной вид конфигурации.

Материал подготовлен в рамках курса «Разработчик на Spring Framework». Если вам интересно узнать подробнее о формате обучения и программе, познакомиться с преподавателем курса — приглашаем на день открытых дверей онлайн. Регистрация здесь.

  • spring
  • spring boot
  • Spring Framework
  • ментальный фреймворк
  • Конфигурация Spring

Spring: в поисках контекста

Пару месяцев назад в моем профиле был опубликован подробный пост по загрузке классов на JVM. После этого доклада мои коллеги задались хорошим вопросом: а какой механизм использует Spring для разбора конфигураций и как он загружает классы из контекста?

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

Немного теории

Сразу определим, что ApplicationContext — это главный интерфейс в Spring-приложении, который предоставляет информацию о конфигурации приложения.

Перед тем, как перейти непосредственно к демонстрации, взглянем на этапы формирования ApplicationContext:

В этом посте разберем первый этап, так как нас интересует именно чтение конфигураций и создание BeanDefinition.

BeanDefinition — это интерфейс, который описывает бин, его свойства, аргументы конструктора и другую метаинформацию.

Что касается конфигурации самих бинов, у Spring есть 4 способа конфигурации:

  1. Xml конфигурация — ClassPathXmlApplicationContext(”context.xml”);
  2. Groovy конфигурация — GenericGroovyApplicationContext(”context.groovy”);
  3. Конфигурация через аннотации с указанием пакета для сканирования — AnnotationConfigApplicationContext(”package.name”);
  4. JavaConfig — конфигурация через аннотации с указанием класса (или массива классов) помеченного аннотацией @Configuration — AnnotationConfigApplicationContext(JavaConfig.class).

Xml конфигурация

За основу берем простой проект:

public class SpringContextTest< private static String classFilter = "film."; public static void main(String[] args)< printLoadedClasses(classFilter); /* Classloader: sun.misc.Launcher$AppClassLoader@18b4aac2 All - 5 : 0 - Filtered /* doSomething(MainCharacter.num); doSomething(FilmMaker.class); printLoadedClasses(classFilter); /* Classloader: sun.misc.Launcher$AppClassLoader@18b4aac2 class film.MainCharacter class film.FilmMaker All - 7 : 2 - Filtered /*

Здесь следует немного пояснить, какие методы и для чего используются:

  • printLoadedClasses(String… filters) — метод выводит в консоль название загрузчика и загруженных JVM классов из пакета, переданного как параметр. Дополнительно есть информация о количестве всех загруженных классов;
  • doSomething(Object o) — метод, который делает примитивную работу, но не позволяет исключить упомянутые классы в процессе оптимизации при компиляции.
11 public class SpringContextTest< 12 private static String calssFilter = "film."; 13 14 public static void main(String[] args)< 15 16 printLoadedClasses(classFilter); 17 /* Classloader: sun.misc.Launcher$AppClassLoader@18b4aac2 18 All - 5 : 0 - Filtered /* 19 doSomething(MainCharacter.num); doSomething(FilmMaker.class); 20 printLoadedClasses(classFilter); 21 /* Classloader: sun.misc.Launcher$AppClassLoader@18b4aac2 22 class film.MainCharacter 23 class film.FilmMaker 24 All - 7 : 2 - Filtered /* 25 ApplicationContext context = new ClassPathXmlApplicationContext( 26 configLocation: "applicationContext.xml"); 27 printLoadedClasses(classFilter);

В 25 строке идет объявление и инициализация ApplicationContext через конфигурацию Xml.

Конфигурационный Xml-файл выглядит следующим образом:

При конфигурации бина указываем реально существующий class. Обратите внимание на заданное свойство lazy-init=”true”: в этом случае бин будет создаваться только после запроса его из контекста.

Смотрим, как Spring при поднятии контекста разрулит ситуацию с классами, объявленными в конфигурационном файле:

public class SpringContextTest < private static String classFilter = "film."; public static void main(String[] args) < printLoadedClasses(classFilter); /* Classloader: sun.misc.Launcher$AppClassLoader@18b4aac2 All - 5 : 0 - Filtered /* doSomething(MainCharacther.num); doSomething(FilmMaker.class); printLoadedClasses(classFilter); /* Classloader: sun.misc.Launcher$AppClassLoader@18b4aac2 class film.MainCharacter class film.FilmMaker All - 7 : 2 - Filtered /* ApplicationContext context = new ClassPathXmlApplicationContext( configLocation: "applicationContext.xml"); printLoadedClasses(classFilter); /* Classloader: sun.misc.Launcher$AppClassLoader@18b4aac2 class film.MainCharacter class film.FilmMaker class film.Villain All - 343 : 3- Filtered /*

Разберемся с деталями Xml конфигурации:

— Чтением файла конфигурации занимается класс XmlBeanDefinitionReader, который реализует интерфейс BeanDefinitionReader;

XmlBeanDefinitionReader на входе получает InputStream и загружает Document через DefaultDocumentLoader:

Document doc = doLoadDocument(inputSource, resource); return registerBeanDefinitions(doc, resource);

— После этого каждый элемент этого документа обрабатывается и, если он является бином, создается BeanDefinition на основе заполненных данных (id, name, class, alias, init- method, destroy-method и др.):

> else if (delegate.nodeNameEquals(ele, "bean")) < this.processBeanDefinition(ele, delegate);

— Каждый BeanDefinition помещается в Map, который хранится в классе DefaultListableBeanFactory:

this.beanDefinitionMap.put(beanName, beanDefinition); this.beanDefinitionNames.add(beanName);

В коде Map выглядит следующим образом:

/** Map of bean definition objects, keyed by bean name */ private final Map beanDefinitionMap = new ConcurrentHashMap(64); 

Теперь в том же конфигурационном файле добавим еще одно объявление бина с классом film.BadVillain:

Смотрим, что получится, если распечатать список созданных BeanDefenitionNames и загруженные классы:

ApplicationContext context = new ClassPathXmlApplicationContext( configLocation: "applicationContext.xml"); System.out.println(Arrays.asList(context.getBeanDefinitionNames())); printLoadedClasses(calssFilter);

Несмотря на то, что класса film.BadVillain, указанного в конфигурационном файле, не существует, Spring отрабатывает без ошибок:

ApplicationContext context = new ClassPathXmlApplicationContext( configLocation: "applicationContext.xml"); System.out.println(Arrays.asList(context.getBeanDefinitionNames())); // [goodVillain, badVillain] printLoadedClasses(calssFilter); /* Classloader: sun.misc.Launcher$AppClassLoader@18b4aac2 class film.MainCharacter class film.FilmMaker class film.Villain All - 343 : 3- Filtered /*

Cписок BeanDefenitionNames содержит 2 элемента; то есть, те 2
BeanDefinition, сконфигурированные в нашем файле, были созданы.

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

Попытаемся получить еще и сами бины по их именам:

ApplicationContext context = new ClassPathXmlApplicationContext( configLocation: "applicationContext.xml"); System.out.println(Arrays.asList(context.getBeanDefinitionNames())); // [goodVillain, badVillain] System.out.println(context.getBean( name: "goodVillain")); System.out.println(context.getBean( name: "badVillain")); 

Если в первом случае был получен валидный бин, то во втором случае прилетел exception.

Обратите внимание на stack trace: сработала отложенная загрузка классов. Выполняется обход всех загрузчиков классов в попытке найти искомый класс среди загруженных ранее. И после того, как нужный класс не был найден, с помощью вызова метода Utils.forName, происходит попытка найти несуществующий класс по имени, что привело к получению закономерной ошибки.

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

Всё потому, что мы прописали lazy-init:true и запретили Spring создавать экземпляр бина, где и генерируется полученный ранее exception. Если убрать это свойство из конфигурации либо изменить его значение lazy-init:false, то описанная выше ошибка также вылетит, но не будет проигнорирована и приложение остановиться. В нашем случае контекст был проинициализирован, но мы не смогли создать экземпляр бина, т.к. указанный класс не был найден.

Groovy конфигурация

При конфигурации контекста с помощью Groovy-файла, необходимо сформировать GenericGroovyApplicationContext, который принимает на вход строку с конфигурацией контекста. Чтением контекста в данном случае занимается класс GroovyBeanDefinitionReader. Эта конфигурация работает по сути так же, как и Xml, только с Groovy-файлами. К тому же, GroovyApplicationContext нормально работает и с Xml-файлом.

Пример простого конфигурационного Groovy-файла:

beans < goodOperator(film.Operator)bean.lazyInit = 'true' > name = 'Good Oleg' > badOperator(film.BadOperator) bean.lazyInit = 'true' > name = 'Bad Oleg' / > > >

Пробуем проделать то же самое, что и с Xml:

Ошибка вылетает сразу: Groovy так же, как и Xml, создает BeanDefenition'ы, но в данном случае постпроцессор сразу выдаёт ошибку.

Конфигурация через аннотации с указанием пакета для сканирования или JavaConfig

Данная конфигурация отличается от двух предыдущих. В конфигурация через аннотации используется 2 варианта: JavaConfig и аннотация над классами.

Здесь используется один и тот же контекст: AnnotationConfigApplicationContext(“package”/JavaConfig.class). Работает он в зависимости от того, что было передано в конструктор.

В контексте AnnotationConfigApplicationContext есть 2 приватных поля:

  • private final AnnotatedBeanDefinitionReader reader (работает с JavaConfig);
  • private final ClassPathBeanDefinitionScanner scanner (сканирует пакет).
  1. Регистрация всех @Configuration-файлов для дальнейшего парсинга;
  2. Регистрация специального BeanFactoryPostProcessor, а именно BeanDefinitionRegistryPostProcessor, который при помощи класса ConfigurationClassParser парсит JavaConfig и создает BeanDefinition.
@Configuration public class JavaConfig < @Bean @Lazy public MainCharacter mainCharacter()< MainCharacter mainCharacter = new MainCharacter(); mainCharacter.name = "Patric"; return mainCharacter; >>
public static void main(String[] args) < ApplicationContext javaConfigContext = new AnnotationConfigApplicationContext(JavaConfig.class); for (String str : javaConfigContext.getBeanDefinitionNames())< System.out.println(str); >printLoadedClasses(classFilter); 

Создаем конфигурационный файл с максимально простым бином. Смотрим, что загрузится:

Если в случае с Xml и Groovy загрузилось столько BeanDefinition, сколько было объявлено, то в данном случае в процессе поднятия контекста загружаются как объявленные, так и дополнительные BeanDefinition. В случае реализации через JavaConfig все классы загружаются сразу, в том числе и класс самого JavaConfig, так как он сам является бином.

Еще один момент: в случае с Xml и Groovy конфигурациями загрузилось 343 файла, здесь же произошла более “тяжелая” загрузка количеством 631 доп файл.

Этапы работы ClassPathBeanDefinitionScanner:

  • По указанному пакету определяется список файлов для сканирования. Все файлы попадают в директории;
  • Сканер проходит по каждому файлу, получает InputStream и сканирует при помощи класса org.springframework.asm.ClassReader.class;
  • На 3-ем этапе сканер проверяет, проходят ли найденные объекты по фильтрам аннотации org.springframework.core.type.filter.AnnotationTypeFilter. По умолчанию Spring ищет классы, которые помечены аннотацией Component либо любой другой аннотацией, которая включает в себя Component;
  • Если проверка проходит успешно, создаются и регистрируются новые BeanDefinition.

Рассмотрим работу сканера на простом примере.

Создаем собственную аннотацию для поиска соответствующих классов:

import org.springframework.stereotype.Component import java.lang.annotation.*; @Target() @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface MyBeanLoader< String value() default "";

Создаем 2 класса: один со стандартной аннотацией Component, второй — с кастомной аннотацией:

@Component public class MainCharacter
MyBeanLoader("makerFilm") @Lazy public class FilmMaker

В результате получаем сформированные BeanDefinition для этих классов и успешно загруженные классы.

ApplicationContext annotationConfigContext = new AnnotationConfigApplicationContext(. basePackages: "film"); for (String str : annotationConfigContext.getBeanDefinitionNames()) < System.out.println(str); >printLoadedClasses(classFilter);

Вывод

Из всего вышесказанного на поставленные вопросы можно ответить следующим образом:

    Какой механизм использует Spring для разбора конфигураций?

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *