#Primary on Factory Beans - java

The simplified version I have looks like this:
#Configuration
#EnableTransactionManagement
public class DatabaseDefaultConfig {
#Bean
#Primary
public DataSource dataSourceDefault(DatabaseConfigurationHelper databaseConfigurationHelper) {
return ...;
}
#Bean
#Primary
public SqlSessionFactoryBean sqlSessionFactoryBeanDefault(DatabaseConfigurationHelper databaseConfigurationHelper, #Value("${datasource.default.cacheEnabled}") boolean cacheEnabled) throws Exception {
return ...;
}
}
#Configuration
#EnableTransactionManagement
public class DatabaseMaintenanceConfig {
#Bean
public DataSource dataSourceMaintenance(DatabaseConfigurationHelper databaseConfigurationHelper) {
return ...;
}
#Bean
public SqlSessionFactoryBean sqlSessionFactoryBeanMaintenance(DatabaseConfigurationHelper databaseConfigurationHelper, #Value("${datasource.maintenance.cacheEnabled}") boolean cacheEnabled) throws Exception {
return ...;
}
}
The classes are very much the same, one uses #Primary. Now let's create two dummy beans:
#Configuration
public class CommonDatabaseConfig {
#Bean
public AtomicInteger a(SqlSessionFactoryBean sqlSessionFactoryBean) {
return new AtomicInteger();
}
#Bean
public AtomicLong b(DataSource dataSource) {
return new AtomicLong();
}
}
While b works fine, a fails and claims that two beans were found:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of method a in sjngm.CommonDatabaseConfig required a single bean, but 2 were found:
- &sqlSessionFactoryBeanDefault: defined by method 'sqlSessionFactoryBeanDefault' in class path resource [sjngm/DatabaseDefaultConfig.class]
- &sqlSessionFactoryBeanMaintenance: defined by method 'sqlSessionFactoryBeanMaintenance' in class path resource [sjngm/DatabaseMaintenanceConfig.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
Note that both beans start with a &. Reading this question and its answer it becomes clear that this is intended. However, that seems to break applying the #Primary as it fails in this area of Spring's DefaultListableBeanFactory:
protected boolean isPrimary(String beanName, Object beanInstance) {
if (containsBeanDefinition(beanName)) {
return getMergedLocalBeanDefinition(beanName).isPrimary();
}
BeanFactory parent = getParentBeanFactory();
return (parent instanceof DefaultListableBeanFactory &&
((DefaultListableBeanFactory) parent).isPrimary(beanName, beanInstance));
}
containsBeanDefinition() in line 2 returns false because of the ampersand.
Now: Am I doing something wrong here? How can I fix this?
This is Spring 4.3.9 (as part of Spring-Boot 1.5.4)

It's fixed within spring-framework PR 22711.

Related

How do you make a spring bean Primary only if another Primary bean does not exist

I am trying to create multiple beans that implement the same interface. I have a bean that I want to use as the "default" #Primary bean; however, since it acts as a default, I want another bean to be able to use #Primary (or something similar) to make a primary bean. In other words, this default one should be like... "#PrimaryIfNoPrimaryAlreadyExist" kind of thing. For example, I have this:
#Configuration
public class DefaultCustomObjectMapperConfiguration {
#Bean
#Primary
public ICustomObjectMapper defaultCustomObjectMapper(#Value("${objectmapper.serialize.defaultFormat:JSON}") String defaultMapperFormat,
#Qualifier(DEFAULT_XML_MAPPER_KEY) ICustomObjectMapper xmlMapper,
#Qualifier(DEFAULT_JSON_MAPPER_KEY) ICustomObjectMapper jsonMapper) {
return "XML".equals(defaultMapperFormat) ? xmlMapper : jsonMapper;
}
#Bean
#Qualifier(DEFAULT_JSON_MAPPER_KEY)
public ICustomObjectMapper defaultJSONCustomObjectMapper() {
return new DefaultCustomObjectMapper(new ObjectMapper());
}
#Bean
#Qualifier(DEFAULT_XML_MAPPER_KEY)
public ICustomObjectMapper defaultXMLCustomObjectMapper() {
return new DefaultCustomObjectMapper(new XmlMapper());
}
}
I always want to create the defaultJSONCustomObjectMapper() bean and the defaultXMLCustomObjectMapper() bean, but the defaultCustomObjectMapper() should only be created if there is not another bean in the Spring context that is defined as #Primary. For example, it should not be created (or at least not used as Primary) if someone else defines this in the same context:
#Primary
#Bean
ICustomObjectMapper anotherCustomObjectMapper() {
return new AnotherCustomObjectMapper();
}
I believe you can override a bean by calling it the same name, but I don't want to do it that way because then it requires the service pulling this in to know that it has to call the bean something special.
Is this possible? Or is there a way to do it that is better than this?
Edit:
Looking at the Spring annotations, there's the ConditionalOnSingleCandidate. It would be more accurate for me to say I want the opposite of that, i.e. ConditionalOnMultipleCandidates
Adding #ConditionalOnMissingBean(annotation = Primary.class) to your "default" #Primary bean should do the trick. This will only register your default primary bean if another primary bean of the same type is not already registered.
#Bean
#Primary
#ConditionalOnMissingBean(annotation = Primary.class)
public ICustomObjectMapper defaultCustomObjectMapper(#Value("${objectmapper.serialize.defaultFormat:JSON}") String defaultMapperFormat,
#Qualifier(DEFAULT_XML_MAPPER_KEY) ICustomObjectMapper xmlMapper,
#Qualifier(DEFAULT_JSON_MAPPER_KEY) ICustomObjectMapper jsonMapper) {
// ...
}
Note that depending on the potential order of your other bean creation, you may want to ensure that this default bean is processed last. For example, by adding #Order(Integer.MAX_INT).
Update: Sean correctly points out that #ConditionalOnMissingBean doesn't work in this case because it looks for ANY bean with Primary annotation, not just beans of our type.
A somewhat ugly alternative is to programmatically set a bean to primary after bean creation if no other primary bean of that type was found. This can be done by implementing BeanDefinitionRegistryPostProcessor and BeanFactoryAware. Note that bean name is used, not qualifier.
#Configuration
public class DefaultCustomObjectMapperConfiguration
implements BeanDefinitionRegistryPostProcessor, BeanFactoryAware {
private BeanFactory beanFactory;
#Value("${objectmapper.serialize.defaultFormat:JSON}")
private String defaultMapperFormat;
#Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// unused
}
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
String[] beansOfType = BeanFactoryUtils.beanNamesForTypeIncludingAncestors((ListableBeanFactory) beanFactory, ICustomObjectMapper.class);
for(String beanName : beansOfType) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if(beanDef.isPrimary()) {
// found an existing primary bean of same type
return;
}
}
// note that getBeanDefinition retrieves by bean name, which is not necessarily equal to qualifier
BeanDefinition defaultPrimaryBeanDef =
registry.getBeanDefinition("XML".equals(defaultMapperFormat) ? DEFAULT_XML_MAPPER_KEY : DEFAULT_JSON_MAPPER_KEY);
defaultPrimaryBeanDef.setPrimary(true);
}
#Bean(DEFAULT_JSON_MAPPER_KEY)
#Qualifier(DEFAULT_JSON_MAPPER_KEY)
public ICustomObjectMapper defaultJSONCustomObjectMapper() {
return new DefaultCustomObjectMapper(new ObjectMapper());
}
#Bean(DEFAULT_XML_MAPPER_KEY)
#Qualifier(DEFAULT_XML_MAPPER_KEY)
public ICustomObjectMapper defaultXMLCustomObjectMapper() {
return new DefaultCustomObjectMapper(new XmlMapper());
}
}

Overriding spring beans with the same Qualifier Name

I have 2 configuration classes in my spring application.
Configuration and AnotherConfiguration. The AnotherConfiguration is conditioned to create beans only if a certain parameter is provided (this is handled by the ConditionalOnClass annotation).
Configuration.java
#Configuration
public class Configuration {
#Bean
public Stage testStage() {
return someStage1;
}
#Bean
public Stage testStage2() {
return someStage2;
}
}
AnotherConfiguration.java
#Configuration
#ConditionalOnClass()
public class AnotherConfiguration {
#Bean
public Stage testStage2() {
return newStage2;
}
}
The use case is that if I supply an argument that satisfies the Conditional argument for AnotherConfiguration, newStage2 should be returned to all the classes expecting a testStage2 bean. But currently, the testStage2 bean is being resolved from Configuration class instead of being overridden by AnotherConfiguration.
I have tried adding the #Primary annotation to the definition in AnotherConfiguration but that just resolves newStage2 to all the classes expecting bean of type Stage irrespective of the qualifier. Is there a way to instruct spring to override bean definitions only of the same QualifierName (here testStage2.
Due to the project constraints, I cannot make changes to Configuration.java but can make any change to AnotherConfiguration.java keeping the name (testStage2()) same.
I really don't recomend it but
use a conditional instead of an onClass because that will always be true without params
public class Cond implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false;
}
}
and then define the overridden bean to load into the context
#Component("testStage2")
#Conditional(value = Cond.class)
#Primary
public class AnotherStage extends Stage {
public AnotherStage(){
//do whatever
}
}
Sorry bean style
#Configuration
public class AnotherConfiguration {
#Bean("testBean2")
#Conditional(value = Cond.class)
#Primary
public Stage testStage2() {
return newStage2;
}
}

How to programmatically mark a spring bean as #Primary on startup?

I have multiple DataSources in my application.
The standard org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration is annotated with #ConditionalOnSingleCandidate(DataSource.class)
I am attempting to select a #Primary DataSource programmatically.
I have tried a BeanFactoryPostProcessor that naively selects the first DataSource and marks as primary):
#Bean
public BeanFactoryPostProcessor beanFactoryPostProcessor() {
return this::setPrimaryDataSource;
}
public void setPrimaryDataSource(ConfigurableListableBeanFactory beanFactory) {
// Get all DataSource bean names
String[] dataSourceBeanNames = beanFactory.getBeanNamesForType(DataSource.class);
// Find primaryBeanName
String primaryBeanName = dataSourceBeanNames.length > 0 ? dataSourceBeanNames[0] : null;
// Return appropriate bean
assert primaryBeanName != null;
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(primaryBeanName);
beanDefinition.setPrimary(true);
LOGGER.info("Primary DataSource: {}", primaryBeanName);
}
However, this does not appear to work - the #ConditionalOnSingleCandidate(DataSource.class) check on HibernateJpaConfiguration still fails.
Is there anywhere else I can put this code such that it will be executed before the check for #ConditionalOnSingleCandidate?
BeanFactoryPostProcessor worked for me:
#Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// logic to retrieve your bean name
String beanName = beanFactory.getBeanNamesForType(MyService.class)[0];
BeanDefinition bd = beanFactory.getBeanDefinition(beanName);
bd.setPrimary(true);
}
}
If your code is in a class with #Configuration, the method need to be static in order to update the bean definition before any bean creation.
Here is a sample for PropertySourcesPlaceholderConfigurer.
#Configuration
public class AppConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
In the documentation:
You may declare #Bean methods as static, allowing for them to be called without creating their containing configuration class as an instance. This makes particular sense when defining post-processor beans (for example, of type BeanFactoryPostProcessor or BeanPostProcessor), since such beans get initialized early in the container lifecycle and should avoid triggering other parts of the configuration at that point.

Respect #Lazy annotation on non-#Primary #Bean

I'm having problems getting Spring to respect the #Lazy annotation on #Bean methods when it is configured to use a different #Bean method that returns an implementation of the same interface that is flagged as #Primary.
Specifically, I have a #Configuration-annotated class with several #Bean methods that all return the same interface. Many of these #Bean methods are #Lazy, as they contact external services for which the application may not currently be using. The #Primary bean is not #Lazy, as it looks at runtime configuration to determine which implementation to return.
Here is a contrived example of that configuration class, revolving around a fictitious ThingService interface:
#Configuration
#ComponentScan(basePackages = { "com.things" })
public class ThingConfiguration {
#Bean
public ThingOptions thingOptions() {
ThingOptions options = new ThingOptions();
options.sharing = true;
return options;
}
#Primary
#Bean
public ThingService primaryThing(ThingOptions options, ApplicationContext context) {
System.out.println("PrimaryThing -- Initialized");
if (options.sharing) {
return context.getBean("OurThing", ThingService.class);
} else {
return context.getBean("YourThing", ThingService.class);
}
}
#Lazy
#Bean(name = "YourThing")
public ThingService yourThing() {
System.out.println("YourThingService -- Initialized");
return new YourThingService();
}
#Lazy
#Bean(name = "OurThing")
public ThingService ourThing() {
System.out.println("OurThingService -- Initialized");
return new OurThingService();
}
}
I then have a #Component that depends on this interface which that the #Primary annotation will ensure that the correct implementation will be injected into the object. Here is an example of that downstream #Component:
#Component
public class ThingComponent {
private final ThingService thingService;
#Inject
public ThingComponent(ThingService thingService) {
this.thingService = thingService;
}
}
I then built a small test to ensure that #Lazy and #Primary are all being respected.
public class ThingTest {
#Test
public void TestLazyAndPrimary() {
// Arrange
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(ThingConfiguration.class);
context.refresh();
// Act
ThingComponent component = context.getBean(ThingComponent.class);
// Assert
Assert.assertNotNull(component);
}
}
However, when I run this test, I found that #Lazy was being ignored. The following text is emitted to the console:
PrimaryThing -- Initialized
OurThingService -- Initialized
YourThingService -- Initialized
The "YourThing" #Bean should not have been initialized, as it was #Lazy and not loaded at runtime via the ApplicationContext.getBean() method. Yet when the ThingComponent is resolved, it causes the #Bean methods with that return an implementation of ThingService to be hydrated before the #Primary mean is chosen.
How do I get the #Primary annotated implementation of an interface to be respected without causing all of the non-#Primary implementations annotated with #Lazy to be hydrated?
I have been unable to stop the #Primary annotation from forcing eager hydration of all #Bean methods that return that interface, even though this information seems available without forcing hydration from the annotations in exclusivity. I got around this by using a naming convention on #Bean methods instead.
Specifically, I changed my #Primary annotated #Bean method to include a name like so:
#Configuration
#ComponentScan(basePackages = { "com.things" })
public class ThingConfiguration {
// #Primary -- I don't want someone to accidentally use this without a #Qualifier!
#Bean(name = "PrimaryThingService")
public ThingService primaryThing(ThingOptions options, ApplicationContext context) {
System.out.println("PrimaryThing -- Initialized");
if (options.sharing) {
return context.getBean("OurThing", ThingService.class);
} else {
return context.getBean("YourThing", ThingService.class);
}
}
// ... the rest of the methods removed for clarity ...
}
Then I placed a #Qualifier on the ThingService being injected into the #Component like so:
#Component
public class ThingComponent {
private final ThingService thingService;
#Inject
public ThingComponent(#Qualifier("PrimaryThingService") ThingService thingService) {
this.thingService = thingService;
}
}
Now when I rerun the test, I get the following output:
PrimaryThing -- Initialized
OurThingService -- Initialized
So this removes the #Primary annotation in place of using a named #Bean following a convention of "Primary{Interface}", stepping around the Spring's overeager hydration of non-#Primary annotated #Bean methods.

Spring with MyBatis: expected single matching bean but found 2

I've been using Spring with MyBatis and it's been working really well for a single database. I ran into difficulties when trying to add another database (see reproducible example on Github).
I'm using Spring Java configuration (i.e. not XML). Most of the examples I've seen show how to achieve this using XML.
I have two data configuration classes (A & B) like this:
#Configuration
#MapperScan("io.woolford.database.mapper")
public class DataConfigDatabaseA {
#Bean(name="dataSourceA")
public DataSource dataSourceA() throws SQLException {
SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
dataSource.setDriver(new com.mysql.jdbc.Driver());
dataSource.setUrl("jdbc:mysql://" + dbHostA + "/" + dbDatabaseA);
dataSource.setUsername(dbUserA);
dataSource.setPassword(dbPasswordA);
return dataSource;
}
#Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSourceA());
return sessionFactory.getObject();
}
}
Two mappers, and a service that autowires the mappers:
#Service
public class DbService {
#Autowired
private DbMapperA dbMapperA;
#Autowired
private DbMapperB dbMapperB;
public List<Record> getDabaseARecords(){
return dbMapperA.getDatabaseARecords();
}
public List<Record> getDabaseBRecords(){
return dbMapperB.getDatabaseBRecords();
}
}
The application won't start:
Error creating bean with name 'dataSourceInitializer':
Invocation of init method failed; nested exception is
org.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type [javax.sql.DataSource] is defined:
expected single matching bean but found 2: dataSourceB,dataSourceA
I've read that it's possible to use the #Qualifier annotation to disambiguate the autowiring, though I wasn't sure where to add it.
Can you see where I'm going wrong?
If you want to use two data sources at same time and they are not primary and secondary, you should disable DataSourceAutoConfiguration by #EnableAutoConfiguration(excludes = {DataSourceAutoConfiguration.class}) on your application annotated by #SpringBootApplication. Afterwards, you can create your own SqlSessionFactory and bundle your own DataSource. If you also want to use DataSourceTransactionManager, you should do that too.
In this case, you haven't disabled DataSourceAutoConfiguration, so spring framework will try to #Autowired only one DataSource but got two, error occurs.
As what I've said before, you should disable DataSourceAutoConfiguration and configure it manually.
You can disable data source auto configuration as following:
#SpringBootApplication
#EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
public class YourApplication implements CommandLineRunner {
public static void main (String... args) {
SpringApplication.run(YourApplication.class, args);
}
}
And if you are really want to use multiple databases at same time, I suggest you to registering proper bean manually, such as:
package xyz.cloorc.boot.mybatis;
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Repository;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.sql.DataSource;
#Configuration
public class SimpleTest {
private DataSource dsA;
private DataSource dsB;
#Bean(name = "dataSourceA")
public DataSource getDataSourceA() {
return dsA != null ? dsA : (dsA = new BasicDataSource());
}
#Bean(name = "dataSourceB")
public DataSource getDataSourceB() {
return dsB != null ? dsB : (dsB = new BasicDataSource());
}
#Bean(name = "sqlSessionFactoryA")
public SqlSessionFactory getSqlSessionFactoryA() throws Exception {
// set DataSource to dsA
return new SqlSessionFactoryBean().getObject();
}
#Bean(name = "sqlSessionFactoryB")
public SqlSessionFactory getSqlSessionFactoryB() throws Exception {
// set DataSource to dsB
return new SqlSessionFactoryBean().getObject();
}
}
#Repository
public class SimpleDao extends SqlSessionDaoSupport {
#Resource(name = "sqlSessionFactoryA")
SqlSessionFactory factory;
#PostConstruct
public void init() {
setSqlSessionFactory(factory);
}
#Override
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
super.setSqlSessionFactory(sqlSessionFactory);
}
public <T> T get (Object id) {
return super.getSqlSession().selectOne("sql statement", "sql parameters");
}
}
In the end, we put each mapper in its own folder:
src/main/java/io/woolford/database/mapper/a/DbMapperA.java
src/main/java/io/woolford/database/mapper/c/DbMapperB.java
We then created two DataConfig classes, one for each database. The #MapperScan annotation resolved the expected single matching bean but found 2 issue.
#Configuration
#MapperScan(value = {"io.woolford.database.mapper.a"}, sqlSessionFactoryRef="sqlSessionFactoryA")
public class DataConfigDatabaseA {
It was necessary to add the #Primary annotation to the beans in one of the DataConfig classes:
#Bean(name="dataSourceA")
#Primary
public DataSource dataSourceA() throws SQLException {
...
}
#Bean(name="sqlSessionFactoryA")
#Primary
public SqlSessionFactory sqlSessionFactoryA() throws Exception {
...
}
Thanks to everyone who helped. No doubt, there's more than one way to do this. I did try #Qualifier and #EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class}) as recommended by #eduardlofitskyi and #GeminiKeith, but that generated some further errors.
In case it's useful, the solution that worked for us is posted here: https://github.com/alexwoolford/mybatis-spring-multiple-mysql-reproducible-example
You can use #Qualifier annotation
The problem is that you have two the same type beans in Spring container. And when you try autowire beans, Spring cannot resolve which bean inject to field
The #Qualifier annotation is the main way to work with qualifiers. It can be applied alongside #Autowired or #Inject at the point of injection to specify which bean you want to be injected.
So, your DbService should look like this:
#Service
public class DbService {
#Autowired
#Qualifier("dataSourceA")
private DbMapperA dbMapperA;
#Autowired
#Qualifier("dataSourceB")
private DbMapperB dbMapperB;
public List<Record> getDabaseARecords(){
return dbMapperA.getDatabaseARecords();
}
public List<Record> getDabaseBRecords(){
return dbMapperB.getDatabaseBRecords();
}
}
I had the same issue and could not start my Spring Boot application, and by renaming the offending class and all the layers that dealt with it, strangely the application started successfully.
I have the classes UOMService, UOMServiceImpl UOMRepository and UOMRepositoryImpl. I renamed them to be UomService, UomServiceImpl, UomRepository and UomRepositoryImpl and that solved the problem!

Categories

Resources