Using The Bean Annotation

Uso de la anotación @Bean

@Bean es una anotación de nivel de método y el equivalente directo del elemento XML <bean/>. La anotación admite varios de los atributos disponibles en <bean/>, como:

  • name
  • init-method
  • destroy-method
  • autoconexión

Puedes usar la anotación @Bean en una clase anotada con @Configuration o con @Component.

Declarar un bean

Para declarar un bean, anota un método con @Bean. Este método registra una definición de bean dentro de un ApplicationContext, con el tipo indicado por el tipo de retorno del método.

De forma predeterminada, el nombre del bean es el mismo que el nombre del método, salvo que se haya configurado otro generador de nombres de beans.

El siguiente ejemplo muestra una declaración de método @Bean:

@Configuration
public class AppConfig {

	@Bean
	public TransferServiceImpl transferService() {
		return new TransferServiceImpl();
	}
}

La configuración anterior equivale exactamente a la siguiente configuración XML de Spring:

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

Ambas declaraciones ponen a disposición del ApplicationContext un bean denominado transferService, asociado a una instancia de TransferServiceImpl:

transferService -> com.acme.TransferServiceImpl

También puedes utilizar métodos default para definir beans. Esto permite componer configuraciones de beans implementando interfaces que declaran definiciones de beans en métodos default.

public interface BaseConfig {

	@Bean
	default TransferServiceImpl transferService() {
		return new TransferServiceImpl();
	}
}

@Configuration
public class AppConfig implements BaseConfig {
}

También puedes declarar el método @Bean con un tipo de retorno de interfaz —o clase base—:

@Configuration
public class AppConfig {

	@Bean
	public TransferService transferService() {
		return new TransferServiceImpl();
	}
}

Sin embargo, esto limita la visibilidad para la predicción avanzada de tipos al tipo de interfaz especificado, TransferService.

El tipo completo, TransferServiceImpl, solo será conocido por el contenedor después de que se haya creado el bean singleton correspondiente.

Los beans singleton sin inicialización diferida se crean según el orden de su declaración. Por ello, puedes observar resultados distintos al buscar coincidencias por un tipo no declarado; por ejemplo, @Autowired TransferServiceImpl solo se resuelve después de que se haya creado el bean transferService.

Si utilizas tus tipos de forma coherente a través de una interfaz de servicio declarada, los tipos de retorno de tus métodos @Bean pueden seguir sin problemas esa decisión de diseño.

Sin embargo, para componentes que implementan varias interfaces o que pueden ser referenciados por su tipo de implementación, es más seguro declarar el tipo de retorno más específico posible, al menos tan específico como exijan los puntos de inyección que hagan referencia al bean.

Dependencias de beans

Un método anotado con @Bean puede tener cualquier número de parámetros que describan las dependencias necesarias para construir ese bean.

Por ejemplo, si TransferService requiere un AccountRepository, puedes expresar esa dependencia mediante un parámetro de método:

@Configuration
public class AppConfig {

	@Bean
	public TransferService transferService(AccountRepository accountRepository) {
		return new TransferServiceImpl(accountRepository);
	}
}

El mecanismo de resolución es prácticamente idéntico a la inyección de dependencias basada en constructores.

Recibir devoluciones de llamada del ciclo de vida

Las clases definidas mediante @Bean admiten las devoluciones de llamada habituales del ciclo de vida y pueden usar las anotaciones JSR-250 @PostConstruct y @PreDestroy.

Las devoluciones de llamada estándar de Spring también se admiten por completo. Si un bean implementa InitializingBean, DisposableBean o Lifecycle, el contenedor invoca sus respectivos métodos.

También se admiten por completo las interfaces *Aware, como BeanFactoryAware, BeanNameAware, MessageSourceAware y ApplicationContextAware.

La anotación @Bean permite especificar métodos arbitrarios de inicialización y destrucción, de manera equivalente a los atributos XML init-method y destroy-method del elemento bean:

public class BeanOne {

	public void init() {
		// Lógica de inicialización.
	}
}

public class BeanTwo {

	public void cleanup() {
		// Lógica de destrucción.
	}
}

@Configuration
public class AppConfig {

	@Bean(initMethod = "init")
	public BeanOne beanOne() {
		return new BeanOne();
	}

	@Bean(destroyMethod = "cleanup")
	public BeanTwo beanTwo() {
		return new BeanTwo();
	}
}

De forma predeterminada, los beans definidos mediante configuración Java que tengan un método público close o shutdown se registran automáticamente con una devolución de llamada de destrucción.

Si tienes un método público close o shutdown y no quieres que se invoque al cerrar el contenedor, puedes añadir @Bean(destroyMethod = "") a la definición del bean para desactivar el modo predeterminado (inferred).

Esto puede ser conveniente para un recurso obtenido mediante JNDI, porque su ciclo de vida se gestiona fuera de la aplicación. En particular, debes hacerlo siempre para un DataSource, ya que puede causar problemas en servidores de aplicaciones Jakarta EE.

El siguiente ejemplo evita una devolución de llamada automática de destrucción para un DataSource:

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

Con métodos @Bean, normalmente debes realizar búsquedas JNDI mediante programación, usando los asistentes JndiTemplate o JndiLocatorDelegate de Spring, o directamente InitialContext de JNDI.

No se recomienda utilizar JndiObjectFactoryBean en este caso, porque obligaría a declarar el tipo de retorno como FactoryBean, en lugar del tipo real de destino. Esto dificulta las llamadas de referencia cruzada desde otros métodos @Bean que necesiten usar ese recurso.

En el caso de BeanOne, también sería válido llamar directamente al método init() durante la construcción:

@Configuration
public class AppConfig {

	@Bean
	public BeanOne beanOne() {
		BeanOne beanOne = new BeanOne();
		beanOne.init();
		return beanOne;
	}

	// ...
}

Cuando trabajas directamente en Java, puedes hacer lo que necesites con tus objetos y no siempre tienes que depender del ciclo de vida del contenedor.

Especificar el ámbito de un bean

Spring incluye la anotación @Scope para indicar el ámbito de un bean.

Usar la anotación @Scope

Puedes indicar que los beans definidos con @Bean tengan un ámbito específico. Puedes utilizar cualquiera de los ámbitos estándar descritos en la sección sobre ámbitos de beans.

El ámbito predeterminado es singleton, pero puedes sobrescribirlo con @Scope:

@Configuration
public class MyConfiguration {

	@Bean
	@Scope("prototype")
	public Encryptor encryptor() {
		// ...
	}
}

@Scope y scoped-proxy

Spring ofrece una forma práctica de trabajar con dependencias con ámbito mediante proxies con ámbito.

La forma más sencilla de crear un proxy de este tipo en configuración XML es el elemento <aop:scoped-proxy/>. Al configurar beans con Java y @Scope, dispones de soporte equivalente mediante el atributo proxyMode.

El valor predeterminado es ScopedProxyMode.DEFAULT, que normalmente indica que no se debe crear ningún proxy con ámbito, salvo que se haya configurado otro valor predeterminado en la instrucción de exploración de componentes.

Puedes especificar ScopedProxyMode.TARGET_CLASS, ScopedProxyMode.INTERFACES o ScopedProxyMode.NO.

Si trasladas a Java el ejemplo de proxy con ámbito de la documentación XML, quedaría así:

// Bean con ámbito de sesión HTTP expuesto como proxy.
@Bean
@SessionScope
public UserPreferences userPreferences() {
	return new UserPreferences();
}

@Bean
public Service userService() {
	UserService service = new SimpleUserService();

	// Referencia al bean userPreferences representado por un proxy.
	service.setUserPreferences(userPreferences());

	return service;
}

Personalizar el nombre de un bean

De forma predeterminada, las clases de configuración utilizan el nombre de un método @Bean como nombre del bean resultante.

Sin embargo, desde Spring Framework 7.0 puedes modificar esta estrategia predeterminada configurando un ConfigurationBeanNameGenerator personalizado al iniciar el contexto o configurar la exploración de componentes.

Por ejemplo, FullyQualifiedConfigurationBeanNameGenerator puede generar nombres de bean predeterminados totalmente cualificados para métodos @Bean que no tengan un atributo name explícito.

Para métodos @Bean concretos, puedes sobrescribir el nombre predeterminado o generado mediante el atributo name:

@Configuration
public class AppConfig {

	@Bean("myThing")
	public Thing thing() {
		return new Thing();
	}
}

@Bean("myThing") equivale a @Bean(name = "myThing").

Alias de beans

A veces resulta útil asignar varios nombres a un mismo bean, lo que se conoce como alias de beans.

El atributo name de @Bean acepta un array de String para este propósito:

@Configuration
public class AppConfig {

	@Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
	public DataSource dataSource() {
		// Crea, configura y devuelve el bean DataSource.
	}
}

Descripción de un bean

A veces es útil proporcionar una descripción textual más detallada de un bean. Esto puede ser especialmente útil cuando los beans se exponen, por ejemplo mediante JMX, con fines de supervisión.

Para añadir una descripción a un @Bean, puedes utilizar la anotación @Description:

@Configuration
public class AppConfig {

	@Bean
	@Description("Proporciona un ejemplo básico de un bean")
	public Thing thing() {
		return new Thing();
	}
}