Does Spring Autowire by method names for Java Config - java

We have some beans defined in our ApplicationConfig like so -
#Bean
S3Repository s3Repository() {
AmazonS3 s3 = new AmazonS3Client(s3AdminReadWriteCreds());
return new S3Repository(s3);
}
#Bean
S3Repository s3PrivateContentRepository() {
AmazonS3 s3 = new AmazonS3Client(readOnlyS3Creds());
return new S3Repository(s3);
}
#Bean
S3Repository s3SyncFilesContentRepository() {
AmazonS3 s3 = new AmazonS3Client(readOnlySpecificBucketCreds());
return new S3Repository(s3);
}
And this is how they are used in code -
public class AssetReader {
#Autowired
private S3Repository s3PrivateContentRepository;
.... used inside the class ....
}
Similarly other beans are named the same as the method that is expected to produce them.
The application works fine but I'm a bit surprised by this, I'm not sure if the bean with the Admin credentials is being autowired everywhere by chance or if the correct beans are being wired in due to some implementation details of Spring?
I thought that it was mandatory to specify a qualifier if Autowiring could produce ambiguous. Assuming that this works as expected, is there any reason for us to qualify these beans?

The reason is that in case of missing explicit Qualifier annotation Spring applies a fallback method:
For a fallback match, the bean name is considered a default qualifier value. Thus you can define the bean with an id "main" instead of the nested qualifier element, leading to the same matching result.
Still, relying on variable names is cleary a risky approach and you should avoid it if possible. An otherwise harmless refactor can turn into a full meltdown if the names get out of synch.

Related

Using #ConditionalOnMissingBean on class is not working

My code:
#RestController
#ConditionalOnMissingBean(name = {"testController"})
#RequestMapping("/a")
public class TestController {
#GetMapping("/b")
private String b() {
return "1";
}
}
In this case, I want to register a bean with name "testController" when the context is not having this bean. But it does not work.
according documentation that is not supposed to work at all:
The condition can only match the bean definitions that have been
processed by the application context so far and, as such, it is
strongly recommended to use this condition on auto-configuration
classes only. If a candidate bean may be created by another
auto-configuration, make sure that the one using this condition runs
after.
you are misusing #ConditionalOnMissingBean

Why is #Bean(initMethod="") not detecting given method in spring?

Edit Fixed by changing package.
I have this configuration file for spring framework
#Configuration
public class AppConfig {
#Bean(initMethod = "populateCache")
public AccountRepository accountRepository(){
return new JdbcAccountRepository();
}
}
JdbcAccountRepository looks like this.
#Repository
public class JdbcAccountRepository implements AccountRepository {
#Override
public Account findByAccountId(long
return new SavingAccount();
}
public void populateCache() {
System.out.println("Populating Cache");
}
public void clearCache(){
System.out.println("Clearing Cache");
}
}
I'm new to spring framework and trying to use initMethod or destroyMethod. Both of these method are showing following errors.
Caused by: org.springframework.beans.factory.support.BeanDefinitionValidationException: Could not find an init method named 'populateCache' on bean with name 'accountRepository'
Here is my main method.
public class BeanLifeCycleDemo {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = new
AnnotationConfigApplicationContext(AppConfig.class);
AccountRepository bean = applicationContext.getBean(AccountRepository.class);
applicationContext.close();
}
}
Edit
I was practicing from a book and had created many packages for different chapters. Error was it was importing different JdbcAccountRepository from different package that did not have that method. I fixed it and it works now. I got hinted at this from answers.
Like you said, if you are mixing configurations types, it can be confusing. Besides, even if you created a Bean of type AccountRepository, because Spring does a lot of things at runtime, it can call your initMethod, even if the compiler couldn't.
So yes, if you have many beans with the same type, Spring can be confused an know which one to call, hence your exception.
Oh and by the way, having a Configuration creating the accountRepoisitory Bean, you can remove the #Repository from your JdbcAccountRepository... It is either #Configuration + #Bean or #Component/Repository/Service + #ComponentScan.
TL;DR
Here is more information and how Spring creates your bean : What object are injected by Spring ?
#Bean(initMethod = "populateCache")
public AccountRepository accountRepository(){
return new JdbcAccountRepository();
}
With this code, Spring will :
Detect that you want to add a Bean in the application Context
The bean information are retrieved from the method signature. In your case, it will create a bean of type AccountRepository named accountRepository... That's all Spring knows, it won't look inside your method body.
Once Spring is done analysing your classpath, or scanning the bean definitions, it will start instanciating your object.
It will therefor creates your bean accountRepository of type AccountRepository.
But Spring is "clever" and nice with us. Even if you couldn't write this code without your compiler yelling at you, Spring can still call your method.
To make sure, try writing this code :
AccountRepository accountRepository = new JdbcAccountRepository();
accountRepository.populateCache(); // Compiler error => the method is not found.
But it works for Spring... Magic.
My recommandation, but you might thinking the same now: If you have classes across many packages to answer different business case, then rely on #Configuration classes. #ComponentScan is great to kickstart your development, but reach its limit when your application grows...
You mix two different ways of spring bean declaration:
Using #Configuration classes. Spring finds all beans annotated with #Configuration and uses them as a reference to what beans should be created.
So if you follow this path of configuration - don't use #Repository on beans. Spring will detect it anyway
Using #Repository - other way around - you don't need to use #Configuration in this case. If you decide to use #Repository put #PostConstruct annotation on the method and spring will call it, in this case remove #Configuration altogether (or at least remove #Bean method that creates JdbcAccountRepository)
Annotate populateCache method with #PostConstruct and remove initMethod from #Bean. It will work.

Defining Spring Beans with same method name for different profiles

I have a configuration class defining two beans depending on the selected profile, with overridden configuration methods:
#Configuration
class MyConfig {
#Profile("profile")
#Bean
MyBean myBean(MyBeanProperties properties) {
return new MyBean(properties);
}
#Profile("!profile")
#Bean
MyBean myBean(MyBeanProperties properties, AdditionalProperties addProps) {
MyBean result = new MyBean(properties);
result.addAdditionalProperties(addProps);
return result;
}
}
and a class which autowires the MyBean into it
#Service
class MyService {
MyBean autowiredBean;
private MyService(MyBean bean) { this.autowiredBean = bean; }
}
Now, when I start the Spring context, it fails with the message
Parameter 0 of constructor in com.example.MyServce required a bean of type 'com.example.MyBean' that could not be found.
How is that possible? I clearly define the Spring bean so it should be present when the context is created.
The reason for this is that Spring considers these beans to be of the same name because of the configuration method name, so it fails to instantiate them (although only one should be created in any given active Profile). This will work fine:
#Configuration
class MyConfig {
#Profile("profile")
#Bean
MyBean myBean(MyBeanProperties properties) {
return new MyBean(properties);
}
#Profile("!profile")
#Bean
// note different method name
MyBean otherBean(MyBeanProperties properties, AdditionalProperties addProps) {
MyBean result = new MyBean(properties);
result.addAdditionalProperties(addProps);
return result;
}
}
I have not found this behavior explained anywhere so I posted this self-answered question to share.
The real-life case this occurred for me what a WebClient which was instantiated with a client registration in one profile, and without one in the other (because none was needed for creating an exchange filter).
This is caused when two beans are defined with the same method name and one of them is expected to be skipped based on some condition(in this case based on profile). In this case, "myBean" is defined twice with different profiles.
The way a config class gets parsed is by iterating through all the beanMethods in that class and adding the corresponding bean definition. The iteration is in order of how the beanMethods are defined in the config class. Here is the link to the code.
Depending on the order in which these beans are defined in the config class, if the first bean defined is expected to be skipped based on the profile annotation, the beanMethod name gets added to a list of "to-be-skipped" beanMethods. Here is the link to the code.
Now, when it encounters the second bean with the same name, it sees that this beanMethod name is already present in the "to-be-skipped" methods list and hence skips the bean even though there is no inherent condition (like the profile) that would cause it to be skipped. Here is the link to the code.
You will notice that if you swap the order of the beans and use the same profile to run which was failing earlier, the bean would get picked up.
Using unique beanMethod names within the config class would be the best way to avoid this scenario.
I figured why changing the names of method allowed application context to load:
The problem is, spring container requires all it's beans to have a unique name as descried here https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-beanname
Every bean has one or more identifiers. These identifiers must be unique within the container that hosts the bean.
When not using XML configuration, I think the only way to have same bean method name is to give a unique name to beans within the #Bean(NAME) annotation, see this for details Spring bean with same method name but different qualifier fail to load

Spring dependency injection via setter method and Configuration class

I wanna switch from xml based spring configuration into java-based. For constructor type injection it is easy. Example I am using:
#Configuration
public class BeanConfiguration {
#Bean
public GreetingService greetingService(GreetingDao greetingDao) {
return new GreetingService(greetingDao);
}
#Bean
public GreetingDao greetingDao() {
return new GreetingDao();
}
}
However, when I want to inject through setter method I am doing something like this:
#Configuration
public class BeanConfiguration {
#Bean
public MeetingDao meetingDao() {
return new MeetingDao();
}
#Bean
public MeetingService meetingService(MeetingDao meetingDao) {
MeetingService meetingService = new MeetingService();
meetingService.setMeetingDao(meetingDao);
return meetingService;
}
}
I am not sure if this is a possible way to inject with Java-based Configuration and setter method. Unfortunately I cannot find any example (for constructor dependencies a lot of examples exists). Also I am not sure in case of Java-based configuration when should I use constructor and when setter. In Spring docs it is described that I should use constructor for required and setter for optional dependency. However, I see it as the same result for this kind of approach.
Thanks for any clarification.
Adam
Under the assumption that you cannot change the classes themselves, you could always just not return the bean until you have injected all the values.
#Configuration
public class YourConfig{
#Value("${some.value.from.properties}")
private String someValue;
#Bean
#Autowired
public YourBean yourBean(TheDependency theDependency){
YourBean bean = new YourBean();
bean.setTheDependency(theDependency);
bean.setSomeValue(someValue);
return bean;
}
}
Constructor injection is always preferred with class dependencies, but not always possible.
If you have access to changing the source for these services and beans you are creating, then I suggest using constructor injection and placing "#Service" on the class and "#Autowired" on the constructors.
Properties are an example of "optional" dependencies; it is common to default values and behavior if not provided. I prefer just placing the "#Value" directly on that field instead of my constructor because otherwise you might end up with WAY too many parameters. This is "setter" injection, but you don't want an actual setter method (again, not normally anyways); you only want Spring to change the value, not any of your other code.
Using "#Value" is fine if you only have one or two properties(or 3? It's not an exact science); however, if you find you have a lot of fields with "#Value" then you should use a configuration bean instead. An "#ConfigurationProperties" bean can also be treated the same way with "setter" injection, but I prefer constructor injection to be sure that at least the bean is never null, even though it's values might be.

Spring Java config same Bean reference

Looking through question Autowire a bean within Spring's Java configuration I got a question.
#Configuration
public class Config {
#Bean
public RandomBean randomBean(){
return new RandomBean();
}
#Bean
public AnotherBean anotherBean(){
return new AnotherBean(randomBean()); // this line
}
}
How Spring guarantees that method randomBean() will return the same reference as one which was injected into AnotherBean?
Is it achieved via proxies?
On the other hand, doing it with providing dependencies as method parameters is quiet obvious:
#Configuration
public class Config {
#Bean
public RandomBean randomBean(){
return new RandomBean();
}
#Bean
public AnotherBean anotherBean(RandomBean randomBean){
return new AnotherBean(randomBean);
}
}
Edit: finally, I found this behavior described in Further information about how Java-based configuration works internally topic.
There is only one "randomBean" because the default scope is "singleton".(To force Spring to produce a new bean instance each time one is needed, you should declare the bean's scope attribute to be prototype)
singleton
This scopes the bean definition to a single instance per Spring IoC
container (default).
prototype
This scopes a single bean definition to have any number of object
instances.
Spring guarantees that method randomBean() will return the same reference as one which was injected into AnotherBean By using proxies.
In order to generate proxies, Spring uses a third party library called CGLIB.
Spring enhances classes by generating a CGLIB subclass which
interacts with the Spring container to respect bean scoping
semantics for methods.
Each such bean method will be overridden in the generated subclass,
only delegating to the actual bean method implementation if the
container actually requests the construction of a new instance.
Otherwise, a call to such an bean method serves as a reference back
to the container, obtaining the corresponding bean by name.
see org.springframework.context.annotation.ConfigurationClassEnhancer.BeanMethodInterceptor
// To handle the case of an inter-bean method reference, we must explicitly check the
// container for already cached instances.
// First, check to see if the requested bean is a FactoryBean. If so, create a subclass
// proxy that intercepts calls to getObject() and returns any cached bean instance.
// This ensures that the semantics of calling a FactoryBean from within #Bean methods
// is the same as that of referring to a FactoryBean within XML. See SPR-6602.
if (factoryContainsBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName) && factoryContainsBean(beanName)) {
Object factoryBean = this.beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
if (factoryBean instanceof ScopedProxyFactoryBean) {
// Pass through - scoped proxy factory beans are a special case and should not
// be further proxied
}
else {
// It is a candidate FactoryBean - go ahead with enhancement
return enhanceFactoryBean(factoryBean.getClass(), beanName);
}
}
If you want to get two different beans, you should change the default scope by #Scope
#Configuration
public class Config {
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public RandomBean randomBean(){
return new RandomBean();
}
#Bean
public AnotherBean anotherBean(){
return new AnotherBean(randomBean()); // this line
}
}
According to Spring documentation injection of inter-bean possible only when #Bean method declared within #Configuration. It uses CGLIB and all #Configuration classes are subclassed by it.
Please, check this reference https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-java-bean-annotation, section '7.12.4 Using the #Configuration annotation'. You will find answer on your question from original source.

Categories