@Bean – это аннотация уровня метода и прямой аналог элемента <bean/> на XML. Аннотация поддерживает некоторые атрибуты, предлагаемые <bean/>, такие как:

  • init-method

  • destroy-method

  • autowiring

  • name

Вы можете использовать аннотацию @Bean в классе с аннотацией @Configuration или в классе с аннотацией @Component.

Объявление бина

Чтобы объявить бин, можно аннотировать метод с помощью @Bean. Этот метод используется для регистрации определения бина в ApplicationContext того типа, который задан в качестве возвращаемого значения метода. По умолчанию имя бина совпадает с именем метода. В следующем примере показано объявление метода с аннотацией @Bean:

Java
@Configuration
public class AppConfig {
    @Bean
    public TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}
Kotlin
@Configuration
class AppConfig {
    @Bean
    fun transferService() = TransferServiceImpl()
}

Предшествующая конфигурация в точности эквивалентна следующей на основе XML в Spring:

<beans>
    <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

Оба объявления делают бин с именем transferService доступным в ApplicationContext, привязанном к экземпляру объекта типа TransferServiceImpl, как показано на следующем текстовом изображении:

transferService -> com.acme.TransferServiceImpl

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

Java
public interface BaseConfig {
    @Bean
    default TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}
@Configuration
public class AppConfig implements BaseConfig {
}

Можно также объявить свой метод, аннотированный @Bean, с помощью возвращаемого типа интерфейса (или базового класса), как показано в следующем примере:

Java
@Configuration
public class AppConfig {
    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
}
Kotlin
@Configuration
class AppConfig {
    @Bean
    fun transferService(): TransferService {
        return TransferServiceImpl()
    }
}

Однако это ограничивает видимость для прогнозирования расширенного типа указанным типом интерфейса(TransferService). Затем, когда полный тип (TransferServiceImpl) единожды распознается контейнером, создается экземпляр затронутого бина-одиночки. Экземпляры бинов-одиночек без отложенной инициализации создаются в соответствии с порядком их объявления, поэтому вы можете заметить разные результаты согласования по типам в зависимости от того, когда другой компонент пытается выполнить согласование по необъявленному типу (например, @Autowired TransferServiceImpl, который разрешается только после создания экземпляра бина transferService).

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

Зависимости бинов

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

Java
@Configuration
public class AppConfig {
    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}
Kotlin
@Configuration
class AppConfig {
    @Bean
    fun transferService(accountRepository: AccountRepository): TransferService {
        return TransferServiceImpl(accountRepository)
    }
}

Механизм разрешения практически идентичен внедрению зависимостей на основе конструктора.

Получение обратных вызовов жизненного цикла

Любые классы, определенные с использованием аннотации @Bean, поддерживают обычные обратные вызовы жизненного цикла и могут использовать аннотации @PostConstruct и @PreDestroy из JSR-250.

Обычные обратные вызовы жизненного цикла из Spring также полностью поддерживаются. Если бин реализует InitializingBean, DisposableBean или Lifecycle, их соответствующие методы вызываются контейнером.

Стандартный набор *Aware интерфейсов (таких как BeanFactoryAware, BeanNameAware, MessageSourceAware, ApplicationContextAware и так далее) также полностью поддерживается.

Аннотация @Bean поддерживает указание произвольных методов обратного вызова инициализации и уничтожения, подобно атрибутам init-method и destroy-method в Spring XML для элемента bean, как показано в следующем примере:

Java
public class BeanOne {
    public void init() {
        // логика инициализации
    }
}
public class BeanTwo {
    public void cleanup() {
        // логика уничтожения
    }
}
@Configuration
public class AppConfig {
    @Bean(initMethod = "init")
    public BeanOne beanOne() {
        return new BeanOne();
    }
    @Bean(destroyMethod = "cleanup")
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}
Kotlin
class BeanOne {
    fun init() {
        // логика инициализации
    }
}
class BeanTwo {
    fun cleanup() {
        // логика уничтожения
    }
}
@Configuration
class AppConfig {
    @Bean(initMethod = "init")
    fun beanOne() = BeanOne()
    @Bean(destroyMethod = "cleanup")
    fun beanTwo() = BeanTwo()
}

По умолчанию бины, определенные с помощью Java-конфигурации, которые имеют публичный метод close или shutdown, автоматически включаются в список с помощью обратного вызова уничтожения. Если у вас есть публичный метод close или shutdown, но вам не нужно, чтобы он вызывался при закрытии контейнера, то можете добавить @Bean(destroyMethod="") в определение вашего бина, чтобы отключить режим по умолчанию (inferred).

Вам может потребоваться сделать это по умолчанию для ресурса, который вы получаете с помощью JNDI, поскольку его жизненный цикл управляется вне приложения. В частности, убедитесь, что всегда осуществляете это для DataSource, так как известно, что это является проблемой для серверов приложений на Java EE.

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

Java
@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
    return (DataSource) jndiTemplate.lookup("MyDS");
}
Kotlin
@Bean(destroyMethod = "")
fun dataSource(): DataSource {
    return jndiTemplate.lookup("MyDS") as DataSource
}

Кроме того, в методах, аннотированных @Bean, обычно задействуется программный поиск JNDI либо путем использования вспомогательных классов JndiTemplate или JndiLocatorDelegate из Spring, либо путем прямого использования InitialContext из JNDI, но не варианта JndiObjectFactoryBean (который вынудит вас объявить возвращаемый тип как тип FactoryBean вместо фактического целевого типа, что затруднит его использование для перекрестных ссылок в других методах, помеченных аннотацией @Bean, которые, как предполагается, будут ссылаться на предоставленный ресурс).

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

Java
@Configuration
public class AppConfig {
    @Bean
    public BeanOne beanOne() {
        BeanOne beanOne = new BeanOne();
        beanOne.init();
        return beanOne;
    }
    // ...
}
Kotlin
@Configuration
class AppConfig {
    @Bean
    fun beanOne() = BeanOne().apply {
        init()
    }
    // ...
}
Если вы работаете непосредственно в Java, то можете делать со своими объектами все, что угодно, и вам не всегда необходимо полагаться на жизненный цикл контейнера.

Задание области видимости бинов

Spring включает в себя аннотацию @Scope, чтобы можно было задать область доступности бина.

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

Вы можете задать, чтоб ваши бины, определенные с помощью аннотации @Bean, имели определенную область доступности. Вы можете использовать любую из стандартных областей доступности, указанных в разделе Области доступности бина.

По умолчанию используется область доступности на уровне singleton, но вы можете переопределить ее с помощью аннотации @Scope, как показано в следующем примере:

Java
@Configuration
public class MyConfiguration {
    @Bean
    @Scope("prototype")
    public Encryptor encryptor() {
        // ...
    }
}
Kotlin
@Configuration
class MyConfiguration {
    @Bean
    @Scope("prototype")
    fun encryptor(): Encryptor {
        // ...
    }
}

@Scope и scoped-proxy

Spring предлагает удобный способ работы с зависимостями, находящимися в области доступности, через scoped proxies. Самый простой способ создать такой прокси при использовании XML-конфигурации - элемент <aop:scoped-proxy/>. Настройка ваших бинов в Java с помощью аннотации @Scope обеспечивает эквивалентную поддержку с помощью атрибута proxyMode. По умолчанию используется ScopedProxyMode.DEFAULT, что обычно указывает на то, что не следует создавать прокси, входящий в области доступности, если только на уровне инструкции сканирования компонентов не было сконфигурировано иное значение по умолчанию. Вы можете указать ScopedProxyMode.TARGET_CLASS, ScopedProxyMode.INTERFACES или ScopedProxyMode.NO.

Если переложить пример с прокси, находящимся в области доступности, из справочной документации по XML (см. "прокси, входящие в область доступности") на нашу аннотацию @Bean с использованием Java, то он будет выглядеть следующим образом:

Java
// бин, находящийся в области видимости session HTTP, открытый как прокси.
@Bean
@SessionScope
public UserPreferences userPreferences() {
    return new UserPreferences();
}
@Bean
public Service userService() {
    UserService service = new SimpleUserService();
    // ссылка на проксированный бин userPreferences
    service.setUserPreferences(userPreferences());
    return service;
}
Kotlin
// бин, находящийся в области видимости session HTTP, открытый как прокси.
@Bean
@SessionScope
fun userPreferences() = UserPreferences()
@Bean
fun userService(): Service {
    return SimpleUserService().apply {
        // ссылка на проксированный бин userPreferences
        setUserPreferences(userPreferences())
    }
}

Настройка именования бинов

По умолчанию конфигурационные классы используют имя метода, аннотированного @Bean, в качестве имени результирующего бина. Однако эту функциональность можно переопределить с помощью атрибута name, как показано в следующем примере:

Java
@Configuration
public class AppConfig {
    @Bean("myThing")
    public Thing thing() {
        return new Thing();
    }
}
Kotlin
@Configuration
class AppConfig {
    @Bean("myThing")
    fun thing() = Thing()
}

Присвоение псевдонимов бинам

Как обсуждалось в разделе Именование бинов, иногда желательно давать одному бину несколько имен, что называется присвоением псевдонима бину. Атрибут name аннотации @Bean принимает массив String для этой цели. В следующем примере показано, как задать несколько псевдонимов для бина:

Java
@Configuration
public class AppConfig {
    @Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
    public DataSource dataSource() {
        // создаем экземпляр, конфигурируем и возвращаем бин DataSource...
    }
}
Kotlin
@Configuration
class AppConfig {
    @Bean("dataSource", "subsystemA-dataSource", "subsystemB-dataSource")
    fun dataSource(): DataSource {
        // создаем экземпляр, конфигурируем и возвращаем бин DataSource...
    }
}

Описание бина

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

Чтобы добавить описание к аннотации @Bean, вы можете использовать аннотацию @Description, как показано в следующем примере:

Java
@Configuration
public class AppConfig {
    @Bean
    @Description("Provides a basic example of a bean")
    public Thing thing() {
        return new Thing();
    }
}
Kotlin
@Configuration
class AppConfig {
    @Bean
    @Description("Provides a basic example of a bean")
    fun thing() = Thing()
}