Spring boot autowiring: strange case of property value being populated - java

I've been blown away by something and my sleep is nowhere after finding out that my code is working from last 2 years when clearly it shouldn't!
So it's a Spring boot application. The application has some controllers. One of the classes I'm using extends WebServiceGatewaySupport, so requires that I #Autowire this. The class name is AClient. Now, this class needs 4 properties to work. Three of them are coming from properties file, e.g.
#Value("${a.userName}")String userName;
So three properties are obviously picked from application.properties file. Now the fourth property had me blown away. It is NOT being fetched as rest three, but is being copied from constructor parameter. So we have only one constructor in the class:
public AClient(String d){
this.d=d;
}
Now this property is being used in one of the methods this class has
public String getSomeData(){
// This method gets Data based on property d
}
And interestingly and surprisingly, this property's value is present everytime this bean is accessed! The bean is #Autowired at one place only, inside the Controller class. I haven't marked the property value to be fetched from application.properties file. I haven't provided this class any clue where to get the value of d from. I haven't provided any public methods for other classes to set this value. Yet the code is working and I can even place a debug pointer in method getSomeData() and see that the value of d is present!
Now I understand I might be missing something obvious, but what? Is there a way I can get into Spring container when #Autowired objects are being instantiated and debug from that point to see where this value is coming from? I've checked the code multiple times. Run hundreds of query on Google to find something like Spring boot does some magic stuff to map the missing String properties. But the variable name in properties file is different from what it is in AClient class. So how can it even map? This is truly killing me now!
Addition:
Less relevant, but the code is being accessed in a standard way:
#Autowired
AClient aClient;
public someOtherMethod(){
aClient.getSomeData();
}
So when I place a debugger on first line of someOtherMethod() and hover over aClient, it shows variable d value populated, same as in application.properties file!
Edit:Here's what I missed:
#Configuration
public class someConfig{
#Bean
public AClient aClient(){
// Someone else fetched property from application.properties file, created an object of AClient class using argument constructor and returned that object here. So now Spring is using #Autowire reference for this object I guess
}
}

So basically, your #Configuration class looks similar to this?
#Configuration
public class SomeConfig {
#Value("${a-client.important-value-d}")
private String importantValueDFromApplicationProperties;
#Bean
public AClient aClient() {
return new AClient(importantValueDFromApplicationProperties);
}
}
If yes, then Spring will the aClient for every #Autowired requesting it. Therefore the value of importantValueDFromApplicationProperties will be present in this certain instance.
Another note:
I would recommend using Spring Boot's #ConfigurationProperties instead of #Value. Take a look.

Related

Get Spring #Value from groovy script

Inside Java code:
#Value("${myVar}") private String myVar;
Inside Groovy code:
import org.springframework.beans.factory.annotation.Value;
import groovy.transform.Field;
#Field #Value('${myVar}') String myVar;
return myVar;
returns null.
How to get myVar from Groovy script, assuming the Beans and myVar are not passed in?
If I understand correctly, you want Spring context to be available during your Groovy script execution and perform the usual injection on your script properties.
Groovy scripts are compiled to Groovy classes, and so you should be able to simply use beanFactory.autowireBean(this). However, to obtain a reference to a BeanFactory in the first place, you need to manually start a Spring context like so:
new ClassPathXmlApplicationContext('path/to/applicationContext.xml').withCloseable { context ->
context.autowireCapableBeanFactory.autowireBean(this)
... //the rest of your script goes here
}
(You might need to adjust the new ClassPathXmlApplicationContext("path/to/applicationContext.xml") part to suit your needs, depending on what method you prefer to use to bootstrap your Spring context; also, if the script is part of a larger codebase, you might want to craft a smaller application context than for the rest of the app, to save startup time and avoid the usual side effects. Depends on the use case, though).
The only part I'm unsure of is whether #Field retains the original annotations when transforming a variable to a field, but if you're not getting any errors, I would assume that to be the case.
In case annotations are not retained, you can always declare a simple annotated POJO bean acting as a container for the injectable properties and call def myPojo = new Pojo(); context.getBeanFactory()).autowireBean(myPojo) instead.
You need to make sure the groovy script is managed by spring, you can either add the #Service or #Component annotations on the class. see example below
#Service
class Demo {
public #Value('${valueA}') String valueA
String showValue() {
return valueA
}
}

How do I use a variable from an application.yml file in my normal code?

For example, let's say that in my yml file I had a variable called indicator. And based on what the indicator variable's value was I want the code to do something different. How would I access the yml variable in the regular code and use it accordingly?
You can use this:
#Value("${your.path.yml.string}")
private String x;
YML:
your:
path:
yml:
string: hello
x will be "hello"
You need to use Spring Expression Language which says we should write it as
#Value("${spring.application.name}")
private String appName;
For Default value if key is not present in yaml/yml or properties file
#Value("${spring.application.name: defaultValue}")
private String appName;
The last way you can fetch value is using environment object
#Autowired
private Environment environment;
String appName = environment.get("spring.application.name");
You can add #Value annotation to any field in your beans.
#Value("$(path.to.your.variable)")
String myString;
Works with constructors as well.
public MyClass(#Value("$(path.to.your.variable)") String myString) {
You can use #Value on fields or parameters to assign the property to some variable.
Property example:
#Value("${indicator}")
private String indicator
Parameter example:
private void someMethod(#Value("${indicator}") String indicator) {
...
}
Then you can use indicator as you want.
Note: the class where you use #Value should be a Spring Component
With Spring-Boot, you have the file application.yml automatically provided for you. What you can do is adding a property in this file, for instance:
my.properties: someValue
Then, in one of your Spring Bean (either define with #Component or #Bean) you can retrieve this value using the annotation #Value. Then, do whatever you want with this variable.
For instance:
#Component
public class MyClass {
#Value("${my.properties"}
private String myProp; // will get "someValue" injected.
...
// Just use it in a method
public boolean myMethod() {
if(myProp.equals("someValue") {
// do something
} else {
// do something else
}
}
}
The best way to do this is not to have a tight coupling between Spring and your "normal" code at all, but instead to use the normal Java features like constructors along with Spring #Bean methods:
class MyService {
final String indicatorName;
MyService(String indicatorName) {
this.indicatorName = indicatorName;
}
}
... in your configuration class...
#Bean
MyService myService(#Value("indicator.name") String indicatorName) {
return new MyService(indicatorName);
}
Two notes for Spring Boot specifically:
The #ConfigurationProperties feature allows you to map properties onto structured Java data classes and is typically cleaner than using #Value by hand.
Always namespace properties that you define yourself to avoid future collisions with other libraries, so instead of indicator.name use company.project.indicator.name. I recommend looking at DataSourceProperties in Boot to see an example of how to set all this up.
More broadly, though, when you say that you want the code to "do something different", it sounds like the better option might be to have different classes that get activated under different circumstances. Both Spring profiles and Spring Boot auto-configuration help to do this.
The problem statement can be re-defined as Configuration Management in Java.
You should have a component like ConfigManager that gets instantiated as part of your application start up. That component will read a properties file, a yaml in your use case. Subsequent app logic will fetch these values from the ConfigManager exposed as simple key/value pairs.
All that is left for you to identify how to read and parse values from yaml file. This is already answered here:
Parse a YAML file

How do I tell Spring to skip loading a Configuration class based on a property?

I know that with the #Profile annotation, you can tell Spring to only load a certain class when using the specified profile, like this:
#Configuration
#Profile("dev")
public class MyCustomConfigurationClass {
// this will only be instantiated when the "dev" profile is active
}
However I'm wondering if there's some equivalent way of doing that for a given application property / environment variable? Here's some pseudo code to illustrate what I want to do:
#Configuration
#OnlyInstantiateWhen(property = "${my.property}", value = "true")
public class MyCustomConfigurationClass {
// this would theoretically only be instantiated when
// the value of my.property is true, either in application.properties
// or in an environment variable
}
Is anything like this possible?
Take a look at implementation of your own condition or using #ConditionalOnProperty() like answers for [this question] suggest. (Conditional spring bean creation)

Spring DeferredImportSelector run before any Configurations

I am trying to create something like #Enable... autoconfiguration. I wanted to create such an annotation for a custom library that has an extensive spring config, yet needs like 2 beans supplied, and based on those initializes various contexts. It indeed initializes beans in all #configuration classes that are returned in an array, but I also want to do some custom configauration logic based on already registered beans. Now the javadoc for that
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/ImportSelector.html
States that
ImportSelectors are usually processed in the same way as regular #Import annotations, however, it is also possible to defer selection of imports until all #Configuration classes have been processed (see DeferredImportSelector for details).
So I turned to DeferredImportSelector cause that Selector is said to be run after all #Configuration beans so I can do conditional Beans. Now javadoc is quite clear here (https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/DeferredImportSelector.html)
A variation of ImportSelector that runs after all #Configuration beans have been processed. This type of selector can be particularly useful when the selected imports are #Conditional
So this is perfect for me. Well until it turns out that no matter what I do, the import selector selectImports method is run always as first before all #Configuration beans.
I figured out that maybe the main #Configuration bean is always last, and the javadoc actually mentions all imported #Configuration beans. It appears that is also not the case. I have been checking precedence using the debugger, but here is the test code I did, and it is pretty simple:
The import selector that does nothing except system out:
public class TestImportSelector implements DeferredImportSelector{
#Override
public String[] selectImports(AnnotationMetadata arg0) {
System.out.println("ImportSelector");
return new String[0];
}
}
The configuration class (the imported to check if any of mentioned ideas work)
#Configuration
public class ImportedTestContext {
#Bean
public String testBeanString2(){
System.out.println("bean2");
return "string2";
}
}
The main context class (also tried swapping the imported classes)
#Configuration
#Import({TestImportSelector.class, ImportedTestContext.class})
public class TestMainContext {
#Bean
public String testBeanString(){
System.out.println("bean1");
return "string";
}
}
And finally my main class
public class Test {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(TestMainContext.class);
}
}
Sooo, as You run the main. You always get the same output
ImportSelector
bean2
bean1
The import selector is always first no matter what. Additionally I tried to mess around with Ordered interface as the javadoc says
Implementations can also extend the Ordered interface or use the Order annotation to indicate a precedence against other DeferredImportSelectors
But it appears that getOrder method is not even called. Well that might be because it says that it only checks it agains other DeferredImportSelectors (and there were none but was worth trying)
I have done this with spring-context 4.3.2.RELEASE as this is what is used in my project, but just to make sure also tested with 5.0.5.RELEASE.
Exactly the same result.
So I believe I do not understand something with regards to ImportSelector, DeferredImportSelector, spring or there is a little chance that the javadoc is not saying the truth or I misunderstood it....
I would appreciate any help or advice....
Just to make it clear: Based on that DeferredImportSelector, I want it to implement BeanFactoryAware (this part works, Spring indeed injects the BeanFactory) that would check what beans were already defined (like those funny test string beans) and based on that will tell spring what additional configurations should be loaded. Based on javadoc this is what it was made for.....
It looks like DeferredImportSelector has a little bit unclear documentation. After running a few tests and checking the code, it turns out that what is deferred is Import, not ImportSelector.
So, if you make use of DeferredImportSelector, you can select a configuration class(es) which import will be deferred.
A selectImports method will get executed normally - during configuration files resolving/parsing, so - using BeanFactory to check if other beans' definitions are already loaded would be certainly a bad idea (as some might not yet be).
The best approach would be to put this logic into #Conditional annotations family (within a target configuration class) and make sure it will be processed after all user-defined configurations.

Method with #Transactional called on target not on proxy instance

I'm currently migrating one of my projects form "self configured spring" to spring boot. while most of the stuff is already working I have a problem with a #Transactional method where when it is called the context is not present as set before due to a call to the "target" instance instead of the "proxy" instance (I'll try to elaborate below).
First a stripped down view of my class hierarchy:
#Entity
public class Config {
// fields and stuff
}
public interface Exporter {
int startExport() throws ExporterException;
void setConfig(Config config);
}
public abstract class ExporterImpl implements Exporter {
protected Config config;
#Override
public final void setConfig(Config config) {
this.config = config;
// this.config is a valid config instance here
}
#Override
#Transactional(readOnly = true)
public int startExport() throws ExporterException {
// this.config is NULL here
}
// other methods including abstract one for subclass
}
#Scope("prototype")
#Service("cars2Exporter")
public class Cars2ExporterImpl extends ExporterImpl {
// override abstract methods and some other
// not touching startExport()
}
// there are other implementations of ExporterImpl too
// in all implementations the problem occurs
the calling code is like this:
#Inject
private Provider<Exporter> cars2Exporter;
public void scheduleExport(Config config) {
Exporter exporter = cars2Exporter.get();
exporter.setConfig(config);
exporter.startExport();
// actually I'm wrapping it here in a class implementing runnable
// and put it in the queue of a `TaskExecutor` but the issue happens
// on direct call too. :(
}
What exactly is the issue?
In the call to startExport() the field config of ExporterImpl is null although it has been set right before.
What I found so far:
With a breakpoint at exporter.startExport(); I checked the id of the exporter instance shown by eclipse debugger. In the debbug round while writing this post it is 16585. Continuing execution into the call/first line of startExport() where i checked the id again (of this this time) expecting it to be the same but realizing that it is not. It is 16606 here... so the call to startExport() is done on another instance of the class... in a previous debug round i checked to wich instance/id the call to setConfig() is going... to the first on (16585 in this case). This explains why the config field is null in the 16606 instance.
To understand what happens between the line where i call exporter.startExport(); and the actuall first line of startExport() i clicked into the steps between those both lines in eclipse debugger.
There i came to line 655 in CglibAopProxy that looks like this:
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
checking the arguments here i found that proxy is the instance with id 16585 and target the one with 16606.
unfortunately I'm not that deep into springs aop stuff to know if that is how it should be...
I just wonder why there are two instances that get called on diffrent methods. the call to setConfig() goes to the proxy instance and the call do startExport() reaches the target instance and thus does not have access to the config previously set...
As mentioned the project has been migrated to spring boot but we where before already using the Athens-RELEASE version of spring platform bom. From what i can tell there where no special AOP configurations before the migration and no explicitly set values after the migration.
To get this problem fixed (or at least somehow working) i already tried multiple things:
remove #Scope from the sub class
move #Transactional from method level to class
override startExport() in subclass and put #Transactional here
add #EnableAspectJAutoProxy to application class (i wasn't even able to log in - no error message)
set spring.aop.proxy-target-class to true
the above in diffrent combinations...
Currently I'm out of clues on how to get this back working...
Thanks in advance
*hopes someone can help*
Spring Boot tries to create a cglib proxy, which is a class based proxy, before you probably had an interface based (JDK Dynamic Proxy).
Due to this a subclass of your Cars2ExporterImpl is created and all methods are overridden and the advices will be applied. However as your setConfig method is final that cannot be overridden and as a result that method will be actually called on the proxy instead on the proxied instance.
So either remove the final keyword so that CgLib proxy can be created or explicitly disable class based proxies for transactions. Add #EnableTransationManagement(proxy-target-class=false) should also do the trick. Unless there is something else triggering class based proxies that is.

Categories

Resources