Cloud config and SpEL in #Value - spring

I met a issue,I try to listen to EnvironmentChangeEvent and re-init some configuration.
While i found that #Value annotation with SpEL is not working, but plain #Value annotation is OK:
#Component
public class ConsumeService {
#Autowired
ConsumeConfig consumeConfig;
#EventListener(EnvironmentChangeEvent.class)
void onEnvChange() {
log.debug("{}",consumeConfig);
//when i print here, i get only consumeDesc, but cardList is an empty List.
}
}
#Configuration
#RefreshScope
public class ConsumeConfig {
#Value("${consume.desc}")
private String consumeDesc;
#Value("#{'${api.server.round.card}'.split(',')}")
private List<String> cardList;
}
Question:
Is this issue related with lifecycle of SpEL? It seems that, the value of SpEL is not yet parsed.

The split should return an array of String, not a list. Try this:
#Value("#{'${api.server.round.card}'.split(',')}")
private String[] cardList;

Related

Mapping an array of properties in spring boot and injecting into a prototype scope

I am very new to spring boot and here is how my yaml looks like
configs:
-
collection: col1
groupId: groupId1
topic: topic1
-
collection: col2
groupId: groupId2
topic: topic2
I would like to have 3 classes with their scope defined as a prototype and use the property from the yaml something like
#Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class mongoListener {
public mongoListener(#Value("${config.collection}") String collectionName) {
//do something
}
}
Similarly, have 2 more classes that would use groupID and topic as well. I am stuck here on how to design.
It will be of great help if anyone can explain how to achieve this.
Please try:
Put your application.yaml that consists of your yaml configuration into /src/main/resources/.
Use the property by #Value annotation like:
public mongoListener(#Value("${configs[0].collection}") String collectionName) {
// collectionName is 'col1'
//do something
}
[EDIT 1]
You can create singleton beans different from each other in name like:
#Configuration
public class MyConfiguration {
#Bean("mongoListener_A")
public mongoListener mongoListener_A(#Value("${configs[0].collection}") String collectionName) {
return new mongoListener(collectionName);
}
#Bean("mongoListener_B")
public mongoListener mongoListener_B(#Value("${configs[1].collection}") String collectionName) {
return new mongoListener(collectionName);
}
// ... and so on
}
[EDIT 2]
You can read your yaml configurations into java objects by using #ConfigurationProperties annotation.
#Data //lombok annotation.
#Component("myConfigs")
#ConfigurationProperties
public class Configs {
private final List<Config> configs;
#Data //lombok annotation.
public static class Config {
private String collection;
private String groupId;
private String topic;
}
}
You can create your mongoListeners from that objects like:
#Configuration
public class MyConfiguration {
#Bean
public List<mongoListener> mongoListeners(#Qualifier("myConfigs") Configs configs) {
return configs.getConfigs().stream()
.map(Config::getCollection)
.map(mongoListener::new)
.collect(Collectors.toList());
}
// ... and so on
}
The beans are still singleton but you can create an arbitrary number of instances depending on your yaml file.
See Also
Use YAML for External Properties - Spring Boot Reference Documentation “How-to” Guides
Spring #Value with arraylist split and obtain the first value

Spring Boot - #Configuration class is null in spring component

I have a problem with spring boot when using autowired on a configuration class.
I have minimized the problem by creating a small spring boot project on github.
I created the MyBean class declaring it as #Component and attempting the autowired of the MyConf class which is declared as #Configuration (and reads the property in the file myconfig.properties). In theory, everything is in the spring context, but when the application starts myConfigProp variable in MyBean is null.
Where am I wrong?
I also tried the following solutions, all not working:
Insert the #DependsOn in MyBean
Commented on the #Component and configured MyBean as #Bean of spring
The last test I did (not present on github project) was to pass MyConfigProp as a parameter in MyBean constructor, and it worked.
#Component
public class MyBean {
String message;
public MyBean(MyConfigProp myConfigProp) {
this.message = myConfigProp.getMessage();
}
}
I am somewhat confused.
Looks like you're not Autowiring MyConfigProp's into MyBean:
#Component
public class MyBean {
String message;
#Autowired
public MyBean(MyConfigProp myConfigProp) {
this.message = myConfigProp.getMessage();
}
}
You need to add the #EnableConfigurationProperties(MyConfigProp.class) in your MyBean class so that it looks like:
MyBean.java
#Component
#EnableConfigurationProperties(MyConfigProp.class)
public class MyBean {
String message;
#Autowired
public MyBean(MyConfigProp myConfigProp) {
this.message = myConfigProp.getMessage();
}
}
MyConfigProp.java
#PropertySource("classpath:myconfig.properties")
#ConfigurationProperties(prefix = "config")
public class MyConfigProp {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
I tried all the solutions and the only one that actually solve the problem is to pass MyConfigProp as a parameter in MyBean constructor, as from post, even without #Autowired.
Code update
#Component
public class MyBean {
String message;
public MyBean (MyConfigProp myConfigProp) {
this.message = myConfigProp.getMessage ();
}
}
I share the rest for knowledge.
In detail, trying the proposed solutions, the result was:
Adding the #Component stereo-type on MyConfigProp annotation to
use #Autowired in MyBean not work, even adding #ComponentScan. It still launches NullPointerException
The annotation #EnableConfigurationProperties seems to be useful only if
#Configuration is not used on MyConfigProp and does not solve the problem
From the tests and the documentation readings, if I understand correctly, the problem is that I try to use the object in # Autowired in theMyBean constructor and, during the creation of this, Spring has not yet instantiatedMyConfigProp, hence theNullPointerException.
I updated the code by adding a solutions package with the possible solutions:
MyBeanWorked: Solution shown above.
WithoutConstructor: By moving the instruction into a method, the
startup is successful and the application works.
WorkedWithInjectProp: Not declared as #Component but configured as
#Bean. A little longer but, needing only a property, perhaps cleaner.
More details in the code.
I hope I have done something pleasant.

spring inject logback TurboFilter

I use spring to inject DemoService has always been null, there is no problem with the filter inject of servlet, in the class of extends TurboFilter, how can I get the DemoService object?
https://stackoverflow.com/questions/30662641/inject-spring-bean-into-custom-logback-filter
I have tried the answer to this connection and did not solve the problem of inject.
public class ErrorLogTurboFilter extends TurboFilter {
#Autowired
private DemoService demoService;
#Override
public FilterReply decide(Marker marker, Logger logger, Level level, String s, Object[] objects, Throwable throwable) {
// todo
return FilterReply.NEUTRAL;
}
}
Problem: Logback starts up before the Spring context. Therefore you need to lazy initialize the Filter with the to be injected bean. Apart from that the Filter will not be called as a Spring bean, but as a Turbofilter, that does not know any injections and so on.
What you could try is define that Filter as a Spring bean in your context, that contains the DemoService. Inject the bean via a Setter for the service, but declare the field static, so you are able to access it from the logging context.
Now during the execution you need to check if the static field is already initialized, if so you can use it without a problem.
You are not trying the answer you are quoting, because your extended filter "ErrorLogTurboFilter" does not have a "#Named("errorLogTurboFilter")" which is the standard annotation to make your filter a spring bean.
see : What is javax.inject.Named annotation supposed to be used for?
#markusw According to your prompt, this is my solution,and thank you.
#Configuration
public class WebConfig {
#Bean
public DemoService demoService() {
return new DemoService();
}
}
public class ErrorLogTurboFilter extends TurboFilter {
private ApplicationContext ctx = new AnnotationConfigApplicationContext(WebConfig.class);
private DemoService demoService = ctx.getBean(DemoService.class);
#Override
public FilterReply decide(Marker marker, Logger logger, Level level, String s, Object[] objects, Throwable throwable) {
// todo
return FilterReply.NEUTRAL;
}
}

Spring #ConfigurationProperties not populated

I am experiencing problems using the #ConfigurationProperties feature.
Probably, I am missing something, since the mechanism seems very simple, but for me, it does not work.
I am using Spring Boot with the following main Application class
#SpringBootApplication
#EnableAspectJAutoProxy
#EnableConfigurationProperties(QueuesProperties.class)
#PropertySource("file:config/queues.properties")
#ImportResource("classpath:/spring-config.xml")
public class Application {
public static void main(String... args) {
ConfigurableApplicationContext ctx = SpringApplication.run(Application.class, args);
}
}
with QueuesProperties
#ConfigurationProperties(prefix = "wmq.in.queue")
public class QueuesProperties {
private static final Logger LOGGER = LoggerFactory.getLogger(QueuesProperties.class);
private String descr;
public String getDescr() {
return descr;
}
public void setDescr(String descr) {
this.descr = descr;
}
}
The properties file is very simple (I am trying to isolate the problem)
wmq.in.queue.descr = description
Then, I am trying to #Autowired the QueuesProperties in a #Component that I use in a spring-integration flow with a .
The QueuesProperties is correctly injected but the descr attribute is null.
#Autowired
private QueuesProperties queuesConfiguration;
while this
#Value("${wmq.in.queue.descr}")
private String descr;
is correctly evaluated.
I have made a lot of attempt with different configurations or code, but the result is the same. I get the QueuesProperties bean but it is not populated.
What am I missing?
Reading the question isn't very clear if the wmq.in.queue.descr = description properties is written in applciation.properties file. I said it because you say that the properties is correctly evaluated with #Value and not with
#Autowired
private QueuesProperties queuesConfiguration;
Even the #PropertySource("file:config/queues.properties") let me to think that probably the your wmq.in.queue.descr = description properties isn't written in applciation.properties but in file:config/queues.properties.
Summing
For use #ConfigurationProperties feature you have write the properties in application.properties and use #EnableConfigurationProperties(QueuesProperties.class) on #Component, #Configuration and so on annotated classes like below.
#Component
#EnableConfigurationProperties(QueuesProperties.class)
public class YourBean {
....
private final QueuesProperties queuesProperties;
public YourBean(QueuesProperties queuesProperties){
this.queuesProperties = queuesProperties;
}
.....
}
actually you can change the application.properties file name customizing spring boot properties evaluation but for your local app I discourage. I consider application.properties a good name for naming a place in which you put the configuration properties of your application
I hope that it can help you

Spring #Required properties when creating #Bean annotated beans

I'm developing a Spring Boot application and am trying out using Java annotation-based bean creation (using #Configuration and #Bean) rather than the familiar old XML-based bean creation. I'm puzzled though. If I attempt to create a bean in XML but fail to set an #Required property I get a BeanInitializationException when the application context is created. In my trials so far with annotation-based bean creation though this does not seem to be the case.
For example:
public class MyClass {
...
#Required
public void setSomeProp(String val){
}
}
Then in Spring XML:
<bean class="MyClass"/>
This will blow up during application startup (and IntelliJ flags it) because the required property is not set. But the same does not seem to be true of this:
#Configuration
public class MyConfig {
#Bean
public MyClass myClass() {
return new MyClass();
}
}
This application starts up just fine even though the required property is not ever set. I must be missing something here, because this seems like a pretty key feature in Spring.
UPDATE
I did some digging & debugging and it turns out that the bean definition is somehow being flagged to skip checking that #Required fields are set. In the Spring class 'RequiredAnnotationBeanPostProcessor' the boolean method 'shouldSkip()' is returning true for beans created this way. When I used the debugger to force that method to return false bean creation did indeed blow up with the expected exception.
Seeing as I'm making a pretty basic Spring Boot application I'm inclined (as Zergleb suggests) to submit this as a bug.
UPDATE 2
Some further debugging has revealed that even if the field is getting set forcing the check still throws the same exception, as if it hadn't been set. So perhaps dunni is correct and there is no way for this to work with #Bean notation.
As you said I also could not get #Required to run as expected this may be a bug and needs to be reported. I have a few other suggestions that did work for me.
Class annotated with #Configuration
//With the bean set up as usual These all worked
#Bean
public MyClass myClass() {
return new MyClass();
}
When you annotate the class #Component and load using component scanning works as expected.(The component scanning part is important you either need your #Configuration class to either have #ComponentScan or perhaps remove #Configuration and replace with #SpringBootApplication and this will enable scanning for components without needing to wire them up using #Bean configs)
#Component // Added this
public class MyClass {
...
#Required //Failed as expected
public void setSomeProp(String val){
}
}
Use #Autowired(required=true) //Fails with BeanCreationException //No qualifying bean of type [java.lang.String] found for dependency
//No more #Component
public class MyClass {
...
#Autowired(required=true) //Fails
public void setSomeProp(String val){
}
}
#Autowired required=false //Does not crash
public class MyClass {
...
#Autowired(required=false) //Simply never gets called if missing
public void setSomeProp(String val){
}
}
#Value //Does not work if test.property is missing // Could not resolve placeholder 'test.property' in string value "${test.property}
public class MyClass {
#Value("${test.property}")
String someProp;
//This getter is not neccesary neither is a setter
public String getSomeProp() {
return this.someProp;
}
}
#Value with default value//Does not crash // When getSomeProp is called it returns "My Default Value"(Unless you have test.property=Anything in your application.properties file then it returns "Anything"
public class MyClass {
#Value("${test.property:My Default Value}")
String someProp;
//This getter is not neccesary neither is a setter
public String getSomeProp() {
return this.someProp; //Returns "My Default Value"
}
}
Inside your #Configuration file also fails if it cannot find anything to populate String someProp in the myClass method
#Bean
public MyClass myClass(String someProp) { //Fails being unable to populate this arg
MyClass myObj = new MyClass();
myObj.setSomeProp(someProp);
return ;
}
If course this won't work, since you create the object of MyClass yourself (new MyClass()), thus the annotations are not evaluated. If you create a bean with a #Bean method, the container will only make sure, that all dependencies are there (method parameters) and that the bean scope is adhered to, meaning if it's a singleton bean, only one bean is created per application context. The creation of the bean/object itself is solely the responsibility of the developer.
The equivalent of the xml <bean> tag is annotating the class with #Component, where the bean is created completely by the container, thus the annotations are evaluated.
As it is being said that when you are having your own #Configuration class where you are creating the bean by itself, #Required doesn't apply there.
When you already have a #Component, let Spring Boot do the component scan and at the required setter property you can add #Autowired and it will work fine.
Found this link on web- https://www.boraji.com/spring-required-annotation-example
For example:
I have a Component called Employee having Id and Name.
#Component
public class Employee {
int id;
String name;
public int getId() {
return id;
}
#Autowired
#Required
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
I have a Configuration class called AppConfig.java
#Configuration
public class AppConfig {
#Bean
public int getId() {
return 1;
}
}
So now we see, that component Employee needs an Id property for binding during startup, so I wrote bean method of type Integer, which will get autowired during runtime. If you do not write a bean of type Integer, it will result a BeanCreationException.
And here is my main class file.
#SpringBootApplication
public class SingletonApplication {
public static void main(String[] args) {
ApplicationContext ctx =
SpringApplication.run(SingletonApplication.class, args);
Employee emp = (Employee)ctx.getBean(Employee.class);
System.out.println(emp.getId());
}
}

Resources