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.