En este post vamos a hablar sobre el uso de @Primary, cuando es necesario y donde se debe usar.

¿Cuándo es necesario?

Cuando tenemos más de una implementación de un bean de tipo ‘x’, y tenemos un @Autowired de ese tipo ‘x’ sin un @Qualifier, Spring lanzará el siguiente error en el arranque del contexto:

Field service in com.devutil.examples.spring.primary.HelloComponent required a single bean, but 2 were found:
	- defaultServiceImpl: defined in file [/dev-util-examples/java/spring-basics/target/classes/com/devutil/examples/spring/primary/DefaultServiceImpl.class]
	- spanishServiceImpl: defined in file [/dev-util-examples/java/spring-basics/target/classes/com/devutil/examples/spring/primary/SpanishServiceImpl.class]

Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

Este error se produce porque Spring no es capaz de decidir cual de las implementaciones del tipo ‘x’ inyectar en el @Autowired. Para solucionar este problema tenemos 2 opciones:

  • Definir un @Qualifier indicando el nombre del bean que queremos fijar para ese @Autowired
  • Añadir un @Primary al bean que queremos fijar como bean principal para ese tipo de bean

Hay que recalcar que no son excluyentes ambas opciones. Podemos establecer un @Primary y a su vez, indicar un @Qualifier en el @Autowired.

¿Como indicar que un bean sea @Primary?

Tenemos varias opciones en función de la tipología del proyecto.

Si la propia clase del bean se encuentra en nuestro proyecto, podemos añadir directamente la anotación en la clase concreta:

@Service
@Primary
public class DefaultServiceImpl implements HelloService {

	....

}

Si no queremos fijar el @Primary en la propia clase, y tenemos la definición del bean en una clase de configuración propia del proyecto, bastará con añadir @Primary junto a la anotación @Bean.

@Configuration
public class MyConfiguration {

	@Bean
	@Primary
	public HelloService spanishServiceImpl() {
		return new SpanishServiceImpl();
	}

}

Si nuestra configuración de Spring es en formato XML, podemos indicarlo mediante el atributo primary:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xsi:schemaLocation="
    http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-4.3.xsd">

	<bean primary="true" class="com.devutil.examples.spring.primary.SpanishServiceImpl" /> 

</beans>

Por otro lado, vamos a mostrar un caso más complicado pero que puede ser de utilidad. Hay veces que cargamos librerías externas que definen un bean o tienen sus propias clases de autoconfiguración. Si estos beans entran en conflicto con nuestra configuración o simplemente, nos gustaría poder customizarla, podemos hacer uso de BeanFactoryPostProcessor:

@Configuration
public class MyConfiguration {

	@Bean
	public BeanFactoryPostProcessor setPrimaryExternalBean() {
		return (beanFactory) -> beanFactory.getBeanDefinition("spanishServiceImpl").setPrimary(true);
	}

}

Esta clase nos permite que, antes de empezar a crear ningún bean en el contexto de Spring, obtener la definición de un bean concreto y modificarla en runtime.

Como has podido ver, existen todas estas opciones para resolver el problema de múltiples instancias de un mismo tipo y la inyección de dependencias (@Autowired).

Si quieres un ejemplo completo para probar las distintas aproximaciones, tienes el código fuente en Github bajo el package com.devutil.examples.spring.primary.