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

Внедрение зависимостей между бинами

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

Java
@Configuration
public class AppConfig {
    @Bean
    public BeanOne beanOne() {
        return new BeanOne(beanTwo());
    }
    @Bean
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}
Kotlin
@Configuration
class AppConfig {
    @Bean
    fun beanOne() = BeanOne(beanTwo())
    @Bean
    fun beanTwo() = BeanTwo()
}

В предыдущем примере beanOne получает ссылку на beanTwo через внедрение на основе конструктора.

Этот способ объявления межбиновых зависимостей работает только в том случае, если метод, помеченный аннотацией @Bean, объявлен внутри класса, аннотированного @Configuration. Нельзя объявлять межбиновые зависимости, используя обычные классы с аннотацией @Component.

Внедрение зависимости через метод поиска

Как отмечалось ранее, внедрение зависимости через метод – это продвинутая функция, которую не следует слишком часто использовать. Она полезна в случаях, если бин, находящийся в области видимости на уровне экземпляра-одиночки, имеет зависимость от бина, находящегося в области видимости на уровне прототипа. Использование Java для такого типа конфигурации обеспечивает естественные средства для реализации такого паттерна. В следующем примере показано, как использовать внедрение зависимости через метод поиска:

Java
public abstract class CommandManager {
    public Object process(Object commandState) {
        // создаем новый экземпляр соответствующего интерфейса Command
        Command command = createCommand();
        // устанавливаем состояние для (как ожидается, совершенно нового) экземпляра Command
        command.setState(commandState);
        return command.execute();
    }
    // хорошо... но где реализация этого метода?
    protected abstract Command createCommand();
}
Kotlin
abstract class CommandManager {
    fun process(commandState: Any): Any {
        // создаем новый экземпляр соответствующего интерфейса Command
        val command = createCommand()
        // устанавливаем состояние для (как ожидается, совершенно нового) экземпляра Command
        command.setState(commandState)
        return command.execute()
    }
    // хорошо... но где реализация этого метода?
    protected abstract fun createCommand(): Command
}

Используя конфигурацию Java, вы можете создать подкласс CommandManager, в котором абстрактный метод createCommand() переопределяется таким образом, что он ищет новый (прототип) объект команды. В следующем примере показано, как это сделать:

Java
@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
    AsyncCommand command = new AsyncCommand();
    // вводим зависимости здесь по мере необходимости
    return command;
}
@Bean
public CommandManager commandManager() {
    // возвращаем новую анонимную реализацию CommandManager с помощью createCommand()
    // переопределяется для возврата нового прототипа объекта Command
    return new CommandManager() {
        protected Command createCommand() {
            return asyncCommand();
        }
    }
}
Kotlin
@Bean
@Scope("prototype")
fun asyncCommand(): AsyncCommand {
    val command = AsyncCommand()
    // вводим зависимости здесь по мере необходимости
    return command
}
@Bean
fun commandManager(): CommandManager {
    // возвращаем новую анонимную реализацию CommandManager с помощью createCommand()
    // переопределяется для возврата нового прототипа объекта Command
    return object : CommandManager() {
        override fun createCommand(): Command {
            return asyncCommand()
        }
    }
}

Дополнительная информация о внутренней работе конфигурации на основе Java

Рассмотрим следующий пример, в котором метод, помеченный аннотацией @Bean, вызывается дважды:

Java
@Configuration
public class AppConfig {
    @Bean
    public ClientService clientService1() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }
    @Bean
    public ClientService clientService2() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }
    @Bean
    public ClientDao clientDao() {
        return new ClientDaoImpl();
    }
}
Kotlin
@Configuration
class AppConfig {
    @Bean
    fun clientService1(): ClientService {
        return ClientServiceImpl().apply {
            clientDao = clientDao()
        }
    }
    @Bean
    fun clientService2(): ClientService {
        return ClientServiceImpl().apply {
            clientDao = clientDao()
        }
    }
    @Bean
    fun clientDao(): ClientDao {
        return ClientDaoImpl()
    }
}

clientDao()был вызван один раз в clientService1() и один раз в clientService2(). Поскольку этот метод создает новый экземпляр ClientDaoImpl и возвращает его, обычно ожидается, что получится два экземпляра (по одному для каждой службы). Это определенно было создало проблемы: В Spring созданные бины по умолчанию имеют область видимости singleton. Вот здесь-то и свершается магия: Все классы, аннотированные @Configuration, подклассифицируются при запуске с помощью CGLIB. В подклассе дочерний метод сначала проверяет контейнер на наличие кэшированных (входящих в область видимости) бинов, прежде чем вызвать родительский метод и создать новый экземпляр.

Логика работы может различаться в зависимости от области видимости вашего бина. В данном случае речь идёт об объектах-одиночках.

Начиная со Spring 3.2, отпала необходимость добавлять CGLIB в ваш classpath, поскольку классы CGLIB были перепакованы в org.springframework.cglib и включены непосредственно в модуль spring-core JAR.

Существует несколько ограничений, связанных с тем, что CGLIB динамически добавляет функции во время запуска. В частности, конфигурационные классы не должны быть конечными. Однако, начиная с версии 4.3, для конфигурационных классов разрешены любые конструкторы, включая использование аннотации @Autowired или единственное объявление конструктора не по умолчанию для внедрения по умолчанию.

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

Составление конфигураций на основе Java

Функция конфигурирования Spring на основе Java позволяет составлять аннотации, что может облегчить конфигурацию.

Использование аннотации @Import

Подобно элементу <import/>, используемому в XML-файлах Spring, аннотация @Import позволяет загружать определения, помеченные аннотацией @Bean, из другого класса конфигурации для облегчения модульной организации конфигураций, как показано в следующем примере:

Java
@Configuration
public class ConfigA {
    @Bean
    public A a() {
        return new A();
    }
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {
    @Bean
    public B b() {
        return new B();
    }
}
Kotlin
@Configuration
class ConfigA {
    @Bean
    fun a() = A()
}
@Configuration
@Import(ConfigA::class)
class ConfigB {
    @Bean
    fun b() = B()
}

Теперь, вместо того чтобы указывать ConfigA.class и ConfigB.class при создании экземпляра контекста, необходимо явно указывать только ConfigB, как показано в следующем примере:

Java
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
    // теперь оба бина A и B будут доступны...
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}
Kotlin
import org.springframework.beans.factory.getBean
fun main() {
    val ctx = AnnotationConfigApplicationContext(ConfigB::class.java)
    // теперь оба бина A и B будут доступны...
    val a = ctx.getBean<A>()
    val b = ctx.getBean<B>()
}

Такой подход упрощает создание экземпляра контейнера, поскольку приходится работать только с одним классом, а не запоминать потенциально большое количество классов @Configuration во время построения.

Начиная со Spring Framework 4.2, аннотация @Import также поддерживает ссылки на обычные классы компонентов, аналогично методу AnnotationConfigApplicationContext.register. Это особенно полезно, если нужно избежать сканирования компонентов, используя несколько классов конфигурации в качестве точек входа для явного определения всех ваших компонентов.

Внедрение зависимостей для импортированных определений, аннотированных @Bean

Предыдущий пример рабочий, но является упрощением. В большинстве практических сценариев бины имеют зависимости друг от друга в разных конфигурационных классах. При использовании XML это не проблема, так как не задействован компилятор, и вы можете объявлять ref="someBean", доверив Spring решать эту проблему во время инициализации контейнера. При использовании классов, помеченных аннотацией @Configuration, компилятор Java накладывает ограничения на конфигурационную модель, заключающиеся в том, что ссылки на другие бины должны иметь допустимый языком Java синтаксис.

К счастью, решить эту проблему просто. Метод, аннотированный @Bean, может иметь произвольное количество параметров, которые описывают зависимости бина. Рассмотрим следующий более реальный сценарий с несколькими классами, помеченными аннотацией @Configuration, каждый из которых зависит от бинов, объявленных в других:

Java
@Configuration
public class ServiceConfig {
    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}
@Configuration
public class RepositoryConfig {
    @Bean
    public AccountRepository accountRepository(DataSource dataSource) {
        return new JdbcAccountRepository(dataSource);
    }
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
    @Bean
    public DataSource dataSource() {
        // возвращаем новый DataSource
    }
}
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // все связывается по конфигурационным классам...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}
Kotlin
import org.springframework.beans.factory.getBean
@Configuration
class ServiceConfig {
    @Bean
    fun transferService(accountRepository: AccountRepository): TransferService {
        return TransferServiceImpl(accountRepository)
    }
}
@Configuration
class RepositoryConfig {
    @Bean
    fun accountRepository(dataSource: DataSource): AccountRepository {
        return JdbcAccountRepository(dataSource)
    }
}
@Configuration
@Import(ServiceConfig::class, RepositoryConfig::class)
class SystemTestConfig {
    @Bean
    fun dataSource(): DataSource {
        // возвращаем новый DataSource
    }
}
fun main() {
    val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
    // все связывается по конфигурационным классам...
    val transferService = ctx.getBean<TransferService>()
    transferService.transfer(100.00, "A123", "C456")
}

Есть и другой способ получить тот же результат. Помните, что классы, аннотированные @Configuration, в конечном итоге являются лишь еще одним бином в контейнере: Это означает, что они могут использовать преимущества внедрения @Autowired и @Value и другие возможности так же, как и любой другой бин.

Убедитесь, что зависимости, которые вы внедряете таким способом, принадлежат исключительно к самому простому типу. Классы, аннотированные @Configuration, обрабатываются на довольно раннем этапе во время инициализации контекста, и принудительное внедрение зависимости таким способом может привести к непредвиденной ранней инициализации. По возможности используйте внедрение на основе параметров, как в предыдущем примере.

Кроме того, будьте особенно осторожны с определениями BeanPostProcessor и BeanFactoryPostProcessor через @Bean. Обычно их следует объявлять как static методы, аннотированные @Bean, не вызывающие создание экземпляра содержащего их конфигурационного класса. В противном случае аннотации @Autowired и @Value могут не сработать для самого конфигурационного класса, поскольку его можно создать как экземпляр бина раньше, чем AutowiredAnnotationBeanPostProcessor.

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

Java
@Configuration
public class ServiceConfig {
    @Autowired
    private AccountRepository accountRepository;
    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(accountRepository);
    }
}
@Configuration
public class RepositoryConfig {
    private final DataSource dataSource;
    public RepositoryConfig(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
    @Bean
    public DataSource dataSource() {
        // возвращаем новый DataSource
    }
}
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // все связывается по конфигурационным классам...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}
Kotlin
import org.springframework.beans.factory.getBean
@Configuration
class ServiceConfig {
    @Autowired
    lateinit var accountRepository: AccountRepository
    @Bean
    fun transferService(): TransferService {
        return TransferServiceImpl(accountRepository)
    }
}
@Configuration
class RepositoryConfig(private val dataSource: DataSource) {
    @Bean
    fun accountRepository(): AccountRepository {
        return JdbcAccountRepository(dataSource)
    }
}
@Configuration
@Import(ServiceConfig::class, RepositoryConfig::class)
class SystemTestConfig {
    @Bean
    fun dataSource(): DataSource {
        // возвращаем новый DataSource
    }
}
fun main() {
    val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
    // все связывается по конфигурационным классам...
    val transferService = ctx.getBean<TransferService>()
    transferService.transfer(100.00, "A123", "C456")
}
Внедрение на основе конструктора в классах, помеченных аннотацией @Configuration, поддерживается только начиная со Spring Framework 4.3. Обратите внимание, что нет необходимости указывать @Autowired, если целевой бин определяет только один конструктор.
Полностью уточненные импортированные бины для удобства навигации

В предыдущем сценарии использование аннотации @Autowired работает отлично и обеспечивает желаемую модульность, но определение того, где именно объявляются определения автоматически обнаруженных и связанных бинов, остается несколько неоднозначным. Например, если разработчик смотрит на ServiceConfig, как узнать, где именно объявлен бин @Autowired AccountRepository? В коде это не указано явно, и это может быть вполне нормальным явлением. Помните, что Spring Tools for Eclipse предоставляет инструментарий, который может отображать графы, показывающие, каким образом все связано, и это может быть достаточно. Кроме того, ваша интегрированная среда разработки Java может легко осуществлять поиск всех объявлений и использований типа AccountRepository и быстро отобразить вам расположение методов, помеченных аннотацией @Bean, которые возвращают этот тип.

В случаях, если такая двусмысленность неприемлема и вам нужна прямая навигация из вашей IDE от одного класса, аннотированного @Configuration, к другому, рассмотрите возможность автоматического обнаружения и связывания самих классов конфигурации. В следующем примере показано, как это сделать:

Java
@Configuration
public class ServiceConfig {
    @Autowired
    private RepositoryConfig repositoryConfig;
    @Bean
    public TransferService transferService() {
        // выполняем навигацию "через" конфигурационный класс к методу, аннотированному @Bean!
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }
}
Kotlin
@Configuration
class ServiceConfig {
    @Autowired
    private lateinit var repositoryConfig: RepositoryConfig
    @Bean
    fun transferService(): TransferService {
        // выполняем навигацию "через" конфигурационный класс к методу, аннотированному @Bean!
        return TransferServiceImpl(repositoryConfig.accountRepository())
    }
}

В предыдущей ситуации определение AccountRepository является полностью явным. Однако ServiceConfig теперь тесно связан с RepositoryConfig. Таков компромисс. Эта тесная связь может быть несколько ослаблена за счет использования классов с аннотацией @Configuration, основанных на интерфейсах или абстрактных классах. Рассмотрим следующий пример:

Java
@Configuration
public class ServiceConfig {
    @Autowired
    private RepositoryConfig repositoryConfig;
    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }
}
@Configuration
public interface RepositoryConfig {
    @Bean
    AccountRepository accountRepository();
}
@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {
    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(...);
    }
}
@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class})  // импортируем конкретную конфигурацию!
public class SystemTestConfig {
    @Bean
    public DataSource dataSource() {
        // возвращаем DataSource
    }
}
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}
Kotlin
import org.springframework.beans.factory.getBean
@Configuration
class ServiceConfig {
    @Autowired
    private lateinit var repositoryConfig: RepositoryConfig
    @Bean
    fun transferService(): TransferService {
        return TransferServiceImpl(repositoryConfig.accountRepository())
    }
}
@Configuration
interface RepositoryConfig {
    @Bean
    fun accountRepository(): AccountRepository
}
@Configuration
class DefaultRepositoryConfig : RepositoryConfig {
    @Bean
    fun accountRepository(): AccountRepository {
        return JdbcAccountRepository(...)
    }
}
@Configuration
@Import(ServiceConfig::class, DefaultRepositoryConfig::class)  // импортируем конкретную конфигурацию!
class SystemTestConfig {
    @Bean
    fun dataSource(): DataSource {
        // возвращаем DataSource
    }
}
fun main() {
    val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
    val transferService = ctx.getBean<TransferService>()
    transferService.transfer(100.00, "A123", "C456")
}

Теперь ServiceConfig слабо связан с конкретным DefaultRepositoryConfig, а встроенные инструменты IDE по-прежнему полезны: Вы можете легко получить иерархию типов реализаций RepositoryConfig. Таким образом, навигация по классам, помеченным аннотацией @Configuration, и их зависимостям ничем не отличается от обычного процесса навигации по коду, основанному на интерфейсе.

Если вы хотите повлиять на порядок создания определенных бинов при запуске, объявите некоторые из них как @Lazy (для создания при первом доступе, а не при запуске) или как @DependsOn других определенных бинов (чтобы другие определенные бины создавались перед текущим бином, помимо тех действий, которые подразумеваются прямыми зависимостями последнего).

Условное включение классов @Configuration или методов @Bean

Часто бывает полезно условно активировать или дезактивировать весь класс, аннотированный @Configuration, или даже отдельные методы, аннотированные @Bean, основываясь на некотором произвольном состоянии системы. Одним из распространенных примеров этого подхода является использование аннотации @Profile для активации бинов только в том случае, если определенный профиль был включен в EnvironmentSpring.

Аннотация @Profile фактически реализуема с помощью гораздо более гибкой аннотации под названием @Conditional. Аннотация @Conditional указывает на конкретные реализации org.springframework.context.annotation.Condition, с которыми следует ознакомиться до регистрации @Bean.

Реализации интерфейса Condition обеспечивают matches(….) метод, который возвращает true или false. Например, в следующем листинге приведена фактическая реализация Condition, используемая для аннотации @Profile:

Java
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    // Считываем атрибуты аннотации @Profile
    MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
    if (attrs != null) {
        for (Object value : attrs.get("value")) {
            if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
                return true;
            }
        }
        return false;
    }
    return true;
}
Kotlin
override fun matches(context: ConditionContext, metadata: AnnotatedTypeMetadata): Boolean {
    // Считываем атрибуты аннотации @Profile
    val attrs = metadata.getAllAnnotationAttributes(Profile::class.java.name)
    if (attrs != null) {
        for (value in attrs["value"]!!) {
            if (context.environment.acceptsProfiles(Profiles.of(*value as Array<String>))) {
                return true
            }
        }
        return false
    }
    return true
}

Более подробную информацию см. в javadoc по аннотации @Conditional.

Объединение Java и XML-конфигурации

Поддержка класса, помеченного аннотацией @Configuration в Spring, не стремится быть 100% полной заменой Spring XML. Некоторые средства, такие как пространства имен в Spring XML, остаются идеальным способом конфигурирования контейнера. В случаях, если XML является удобным или необходимым инструментом, вам дает выбор: либо создавать экземпляр контейнера "XML-ориентированным" способом, используя, например, ClassPathXmlApplicationContext, либо создать его экземпляр "Java-ориентированным" способом, используя AnnotationConfigApplicationContext и аннотацию @ImportResource для импорта XML по мере необходимости.

XML-ориентированное использование классов c аннотацией @Configuration

Более предпочтительным вариантом может быть начальная загрузка контейнера Spring из XML и включение в него классов, аннотированных @Configuration, в индивидуальном порядке. Например, в случае существующей обширной кодовой базы, использующей Spring XML, проще создавать классы с аннотацией @Configuration по мере необходимости и осуществлять их включение из существующих XML-файлов. Позже в этом разделе мы рассмотрим варианты использования классов, помеченных аннотацией @Configuration, в подобном "XML-ориентированном" случае.

Объявление классов @Configuration как обычных элементов <bean/> в Spring

Помните, что классы с аннотацией @Configuration в конечном итоге являются определениями бинов в контейнере. В этой серии примеров мы создадим класс, аннотированный @Configuration, с именем AppConfig и включим его в файл system-test-config.xml в качестве определения <bean/>. Поскольку <context:annotation-config/> был включен, контейнер распознает аннотацию @Configuration и надлежащим образом обрабатывает методы, помеченные аннотацией @Bean и объявленные в AppConfig.

В следующем примере показан обычный класс конфигурации на языке Java:

Java
@Configuration
public class AppConfig {
    @Autowired
    private DataSource dataSource;
    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }
    @Bean
    public TransferService transferService() {
        return new TransferService(accountRepository());
    }
}
Kotlin
@Configuration
class AppConfig {
    @Autowired
    private lateinit var dataSource: DataSource
    @Bean
    fun accountRepository(): AccountRepository {
        return JdbcAccountRepository(dataSource)
    }
    @Bean
    fun transferService() = TransferService(accountRepository())
}

В следующем примере показана часть образца файла system-test-config.xml:

<beans>
    <!-- активируем обработку аннотаций, таких как @Autowired и @Configuration  -->
    <context:annotation-config/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
    <bean class="com.acme.AppConfig"/>
    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

В следующем примере показан возможный файл jdbc.properties:

jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
Java
public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}
Kotlin
fun main() {
    val ctx = ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml")
    val transferService = ctx.getBean<TransferService>()
    // ...
}
В файле system-test-config.xml класс <bean/> AppConfig не объявляет элемент id. Хотя это был бы приемлемый исход, но он излишний, учитывая, что ни один другой бин ни в какой момент не ссылается на него, и он вряд ли будет явно получен из контейнера по имени. Аналогичным образом бин DataSource автоматически обнаруживается и связывается только по типу, поэтому явный id бина не является строго обязательным.
Использование <context:component-scan/> для перехвата классов, аннотированных @Configuration

Поскольку @Configuration мета-аннотируется с помощью аннотации @Component, классы, помеченные аннотацией @Configuration, автоматически становятся кандидатами на сканирование компонентов. Используя тот же сценарий, что описан в предыдущем примере, мы можем переопределить system-test-config.xml, чтобы воспользоваться преимуществами сканирования компонентов. Обратите внимание, что в этом случае нам не нужно явно объявлять <context:annotation-config/>, потому что <context:component-scan/> обеспечивает ту же функциональность.

В следующем примере показан измененный файл system-test-config.xml:

<beans>
    <!-- определяет и регистрирует AppConfig как определение бина -->
    <context:component-scan base-package="com.acme"/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

Использование XML с аннотацией@ImportResource, ориентированное на класс, аннотированный @Configuration

В приложениях, где классы, аннотированные @Configuration, являются основным механизмом для конфигурирования контейнера, все же, вероятно, приходится использовать XML хотя бы в некоторой степени. В таких сценариях можно использовать аннотацию @ImportResource и определить только тот объем XML, который необходим. Таким образом получаем "Java-ориентированный" подход к конфигурированию контейнера, а использование XML сводится к минимуму. Следующий пример (который предусматривает конфигурационный класс, XML-файл, определяющий бин, файл свойств, а также main класс) демонстрирует, как использовать аннотацию @ImportResource для получения "Java-ориентированной" конфигурации, в которой XML используется по мере необходимости:

Java
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
    @Bean
    public DataSource dataSource() {
        return new DriverManagerDataSource(url, username, password);
    }
}
Kotlin
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
class AppConfig {
    @Value("\${jdbc.url}")
    private lateinit var url: String
    @Value("\${jdbc.username}")
    private lateinit var username: String
    @Value("\${jdbc.password}")
    private lateinit var password: String
    @Bean
    fun dataSource(): DataSource {
        return DriverManagerDataSource(url, username, password)
    }
}
properties-config.xml
<beans>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
Java
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}
Kotlin
import org.springframework.beans.factory.getBean
fun main() {
    val ctx = AnnotationConfigApplicationContext(AppConfig::class.java)
    val transferService = ctx.getBean<TransferService>()
    // ...
}