Respect #Lazy annotation on non-#Primary #Bean - java

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.

Related

Can #ConditionalOnMissingBean be used for providing fallback bean?

Now I'm developing spring starter libraries, and suddenly I feel like #ConditionalOnMissingBean is misused in my code. Assumes that there are two auto-configuration classes.
#Configuration
public class ProviderAutoConfiguration {
#Bean
#Primary
public Component component() {
return new Component();
}
}
#Configuration
#AutoConfigureAfter(ProviderAutoConfiguration.class)
public class ConsumerAutoConfiguration {
#Bean
#ConditionalOnMissingBean
public Component fallback() {
return Component.NOOP;
}
#Bean
public Consumer consumer(Component component) {
return new Consumer(component);
}
}
The scenario is these two auto-configuration classes are in different libraries, users may only import the consumer library. So if I use #ConditionalOnMissingBean, the ConsumerAutoConfiguration will register a fallback bean to ApplicationContext. This behavior may cause other problems.
#Configuration
public class OtherProviderAutoConfiguration {
#Bean
#ConditionalOnMissingBean
public Component thirdParty() {
return new Component();
}
}
Assumes that users import another library which is developed by others. Now nobody knows the behavior of Component bean initialization.
#Configuration
public class ConsumerAutoConfiguration {
#Autowired(required = false)
private Component component = Component.NOOP;
#Bean
public Consumer consumer() {
return new Consumer(component);
}
}
So my question is should I use #Autowired instead of #ConditionalOnMissingBean in this kind of scenario? Or is there a better way than both of them?

Why do we use a qualifier when we can have a name for the bean?

Why do we use qualifiers with #Bean when we can have different names for different beans of the same type (class)?
#Bean
#Qualifier("fooConfig")
public Baz method1() {
}
Isn't the following code more clean?
#Bean("fooConfig")
public Baz method1() {
}
If I create two beans of the same type with different names (using #Bean annotation), then can we inject them specifically using the #Qualifier annotation(can be added on field/constructor parameter/setter) in another bean?
#Bean("fooConfig")
public Baz method1(){
}
#Bean("barConfig")
public Baz method2(){
}
// constructor parameter of a different bean
final #Qualifier("fooConfig") Baz myConfig
If the above is true, then where do we use #Qualifier (with #Bean or #Component) instead of giving the bean a name as shown below?
#Bean
#Qualifier("fooConfig")
public Baz method1(){
}
#Bean
#Qualifier("barConfig")
public Baz method2(){
}
// constructor parameter of a different bean
final #Qualifier("fooConfig") Baz myConfig
Beans have names. They don't have qualifiers. #Qualifier is annotation, with which you tell Spring the name of Bean to be injected.
No.
Default Qualifier is the only implementation of the interface(example is below, 4th question) or the only method with a particular return type. You don't need to specify the #Qualifier in that case. Spring is smart enough to find itself.
For example:
#Configuration
public class MyConfiguration {
#Bean
public MyCustomComponent myComponent() {
return new MyCustomComponent();
}
}
If you will try to inject myComponent somewhere, Spring is smart enough to find the bean above. Becaude there is only one Bean with return type MyCustomComponent. But if there was a couple of methods, that would return MyCustomComponent, then you would have to tell Spring which one to inject with #Qualifier annotation.
SIDENOTE: #Bean annotation by default Spring uses the method name as a bean name. You can also assign other name like #Bean("otherComponent").
You have one Interface, and a couple of Classes implementing it. You inject bean of your interface. How can Spring know which Class should be used?
This is you interface:
public interface TestRepository{}
This is your implementation 1:
#Repository
public class Test1Repository implements TestRepository{}
Your implementation 2:
#Repository
public class Test2Repository implements TestRepository{}
Now you are injecting it like:
private final TestRepository testRepository;
public TestServiceImpl(TestRepository testRepository) {
this.testRepository= testRepository;
}
QUESTION! How is Spring supposed to know which class to inject? Test1 or Test2? That's why you tell it with #Qualifier which class.
private final TestRepository testRepository;
public TestServiceImpl(#Qualifier("test1Repository") TestRepository testRepository) {
this.testRepository= testRepository;
}
I Prefer different method to not using #Qualifier
Create common Interface
public interface CommonFooBar{
public String commonFoo();
public String commonBar();
}
Extends to each service
public interface FooService extends CommonFooBar {
}
public interface BarService extends CommonFooBar {
}
Then using it to your class
#Autowired
FooService fooService;
or
#Autowired
BarService barService;
so, we can defined the single responsibility to each interface and This kind of segregation is more readable to every junior.
I quite like a different way of working. Surely if you provide a unique name for your bean, then that is all you need?
Given the example below, its easy to see that Spring will name the beans based on the method name used to create the beans. In other words, if you give your beans sensible names, then the code should become self-explanatory. This also works when injecting beans into other classes.
The end result of this is:
Spring will name your beans based on the method used to create them.
If you import a bean, Spring will try to match on the bean name.
If you try to import a bean that does not match the name, Spring will attempt to match the class.
If your injected field name does not match the bean name and there are more than one instance of your bean, Spring will throw an exception on startup as it won't know which one to inject.
Lets not over-complicate Spring.
#Bean
mqConnectionFactory() {
ConnectionFactory connectionFactory = new MQXAConnectionFactory();
return connectionFactory;
}
#Bean
public ConnectionFactory pooledConnectionFactory(ConnectionFactory mqconnectionFactory) {
JmsPoolConnectionFactory connectionFactory = new JmsPoolConnectionFactory();
connectionFactory.setConnectionFactory(mqConnectionFactory);
return connectionFactory;
}
#Bean
public ConnectionFactory cachingConnectionFactory(ConnectionFactory mqConnectionFactory) {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setTargetConnectionFactory(mqConnectionFactory);
return connectionFactory;
}
#Bean
public JmsTemplate jmsTemplate(ConnectionFactory cachingConnectionFactory) {
JmsTemplate jmsTemplate = new JmsTemplate();
jmsTemplate.setConnectionFactory(cachingConnectionFactory);
return jmsTemplate;
}
#Bean
public DefaultMessageListenerContainer messageListenerContainer(ConnectionFactory pooledConnectionFactory) {
DefaultMessageListenerContainer container = new DefaultMessageListenerContainer();
container.setConnectionFactory(pooledConnectionFactory);
...
return container;
}

#Bean ResourceProcessor with #Autowired

In my Spring Boot 1.5.10 application with Spring Data REST and HATEOAS, I have a ResourceProcessor bean with an #Autowired service, like:
#Bean
public ResourceProcessor<Resource<Order>> orderResourceProcessor() {
return new ResourceProcessor<Resource<Order>>() {
#Autowired
private OrderHandler orderHandler;
#Override
public Resource<Order> process(Resource<Order> resource) {
Order order = resource.getContent();
Payment payment = orderHandler.payment(order);
resource.add(makeLink(payment));
return resource;
}
private Link makelink(Payment payment) {
return new Link(/*...*/);
}
};
}
When the #Autowired service is added, the resource processor bean is no longer triggered, unfortunately; i.e., when OrderHandler is commented out, the resource processor runs as it should.
Can a ResourceProcessor use #Autowired services; and, if so, what's the right way to construct it?
This part of the #Bean annotation javadoc should interest you :
#Bean Methods in #Configuration Classes
Typically, #Bean methods are declared within #Configuration classes.
In this case, bean methods may reference other #Bean methods in the
same class by calling them directly. This ensures that references
between beans are strongly typed and navigable. Such so-called
'inter-bean references' are guaranteed to respect scoping and AOP
semantics, just like getBean() lookups would.
Example :
#Bean
public FooService fooService() {
return new FooService(fooRepository());
}
#Bean
public FooRepository fooRepository() {
return new JdbcFooRepository(dataSource());
}
It means that you have not to use #Autowired to set the dependency inside the #Bean declaration but reference another method annotated with #Bean.
But do you really need to set the dependency to create your bean ?
No at all. The OrderHandler is used only during the process() invocation.
So you can simply inject OrderHandler at the same level that the method annotated with #Bean and using it in the anonymous class :
#Autowired
private OrderHandler orderHandler; // only change
#Bean
public ResourceProcessor<Resource<Order>> orderResourceProcessor() {
return new ResourceProcessor<Resource<Order>>() {
#Override
public Resource<Order> process(Resource<Order> resource) {
Order order = resource.getContent();
Payment payment = orderHandler.payment(order);
resource.add(makeLink(payment));
return resource;
}
private Link makelink(Payment payment) {
return new Link(/*...*/);
}
};
}
I guess you can Autowire orderHandler to outer class. In your way it will not work as you create the instance of ResourceProcessor yourself.
#Autowired
private OrderHandler orderHandler;
#Bean
public ResourceProcessor<Resource<Order>> orderResourceProcessor() {
return new ResourceProcessor<Resource<Order>>() {
#Override
public Resource<Order> process(Resource<Order> resource) {
Order order = resource.getContent();
Payment payment = orderHandler.payment(order);
resource.add(makeLink(payment));
return resource;
}
private Link makelink(Payment payment) {
return new Link(/*...*/);
}
};
}

#Primary on Factory Beans

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.

Calling a bean method from another method annotated as Bean

I have two methods annotated with #Bean. I am calling one #Bean annotated method from another. Does it mean it creates two beans of the same type?
Here's my code:
#Configuration
#Import({BaseConfig.class})
public class TestConfig{
#Autowired
BaseConfig baseconfig;
#Bean
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public SampleTestClass sampleTest() {
return new SampleTestClass(baseconfig.createNewBean());
}
}
#Configuration
#Import(SomeClassConfig.class)
public class BaseConfig {
#Autowired
private int someAttribute;
#Bean
public SampleTest createNewBean() {
return new SampleTest(someAttribute);
}
}
No, it wouldn't.
SampleTest has a singleton scope which is the default, so even if you call the method "directly", Spring will make sure that there is only one instance per container.
No, it doesn't. Spring automatically proxies #Configuration classes at runtime and decorates #Bean methods to provide the correct scope behavior.
However, in your case it would be cleaner not to tangle the two configurations unnecessarily. Instead, you could do this:
#Bean
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public SampleTestClass sampleTest(SampleTest dependency) {
return new SampleTestClass(dependency);
}

Categories

Resources