How do I get a custom configuration value available in #ConditionalOnExpression annotation - spring

I am trying to have specific beans enabled based on the "functionality" for the deployment, such as a rest interface, a message consumer, an indexer, an archiver, and an admin portal. In some instances the app should have all, some or one of the "functionalities" like local, dev, and qa should have all of the functionalities, but in staging, and production the functionalities should be segregated so that they can have performance improvements, like memory, threads, etc...
To do this I've setup a custom configuration based on the functionality passed in through the command line. I'm using a ConfigurationProperties to determine whether each of the "funcionalities" should be available. I have a custom configuration:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
#Configuration
#ConfigurationProperties(prefix = "com.example.config.functionality")
public class FunctionalityConfig {
public static final Logger LOGGER = LoggerFactory.getLogger(FunctionalityConfig.class);
private boolean restInterface;
private boolean messageConsumer;
private boolean adminInterface;
private boolean indexing;
private boolean archive;
public void setRestInterface(final boolean restInterface) {
this.restInterface = restInterface;
}
public boolean isRestInterface() {
return restInterface;
}
public void setMessageConsumer(final boolean messageConsumer) {
this.messageConsumer = messageConsumer;
}
public boolean isMessageConsumer() {
return messageConsumer;
}
...
}
Then I have a custom annotation:
...
/**
*
*/
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE, ElementType.METHOD})
#Documented
#ConditionalOnExpression("#{ functionalityConfig.isRestInterface }")
public #interface ConditionalOnRestInterface {
}
But when I add it to a bean definition like this:
#Component
#ConditionalOnRestInterface
public class RestInterface implements InitializingBean {
private static final Logger LOGGER = LoggerFactory.getLogger(RestInterface.class);
public void afterPropertiesSet() throws Exception {
LOGGER.info("Rest Interface is available.");
}
}
I get the following error: Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'functionalityConfig' cannot be found on object of type 'org.springframework.beans.factory.config.BeanExpressionContext' - maybe not public?
If I get rid of the #ConditionalOnExpression annotation, everything works, additionally:
in the Application class I have the following lines:
#Value("#{functionalityConfig.restInterface}")
public boolean restInterface;
And they work perfectly. I'm trying to figure out why the #ConditionalOnExpression isn't picking it up. I've even added the #EnableConfigurationProperties(FunctionalityConfig.class) annotation to the application, but no change to the exception.

Related

Spring Cloud Gateway : actuator refresh does not reload properties

I am actually working on a project with Spring Cloud Gateway.
I have a Configuration class which gets its Properties from a custom PropretySourceFactory. I want to make a hot reload of the properties so I call actuator/refresh (curl localhost:8080/actuator/refresh -d {}H "Content-Type: application/json") but it does not reload my configuration properties. No error or exceptions.
Here is the Configuration class:
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
#Configuration
#ConfigurationProperties
#PropertySource(value="", factory=NatsPropertySourceFactory.class)
public class NatsConfiguration {
private String apiKey;
private String apiKeyConsumer;
private String apiKeyValidity;
private String apiKeyCreation;
private int ratelimitMaxTokens;
private int ratelimitReplenishFrequency;
private int ratelimitReplenishRate;
// Getters and setter
//...
}
value is empty on PropertySource because I will not get my configuration from a file but from a message queue.
and the NatsPropertySourceFactory:
public class NatsPropertySourceFactory implements PropertySourceFactory{
final private NatsConfigurationService natsConfigurationService = new NatsConfigurationServiceImpl();
#Override
public PropertySource<?> createPropertySource(String arg0, EncodedResource arg1) throws IOException {
MapPropertySource result = null;
Logger log = LoggerFactory.getLogger(this.getClass());
try {
result = new MapPropertySource("nats", natsConfigurationService.getConfiguration());
} catch (ConfigurationException e) {
log.error("RECUPERATION DE CONFIGURATION DEPUIS NATS EN ERREUR", e);
System.exit(1);
}
return result;
}
}
My properties are not used with #Value, so I should not need #RefreshScope.
When I call /actuator/refresh the NatsConfiguration class is not recreated.
For information I use webflux with SpringSecurity (actuator urls are permitAll: pathMatchers("/actuator/**").permitAll())
#EnableWebFluxSecurity
#EnableWebFlux
public class SecurityConfiguration implements WebFluxConfigurer {
Where am I wrong?
By the way, I found the exact behaviour of /actuator/refresh: it reinstanciates the #Configuration class but does nothing for the PropertySourceFactory.
I have found a workaround: I created a REST Controler which calls the createPropertySource method of the PropertySourceFactory and then calls the /actuator/refresh url. It does exactly what I wanted: the #Configuration class is up to date with the new properties given by the PropertySourceFactory.

SpringBoot: how to inject two classes having same name

In my application, I have two classes having the same name, but of course in different packages.
Both classes need to be injected in the application; Unfortunately, I get the following error message:
Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'myFeature' for bean class [org.pmesmeur.springboot.training.service.feature2.MyFeature] conflicts with existing, non-compatible bean definition of same name and class [org.pmesmeur.springboot.training.service.feature1.MyFeature]
My issue can be reproduced by the following sample:
#Component
#EnableConfigurationProperties(ServiceProperties.class)
public class MyService implements IService {
private final ServiceProperties serviceProperties;
private final IProvider provider;
private final org.pmesmeur.springboot.training.service.feature1.IMyFeature f1;
private final org.pmesmeur.springboot.training.service.feature2.IMyFeature f2;
#Autowired
public MyService(ServiceProperties serviceProperties,
IProvider provider,
org.pmesmeur.springboot.training.service.feature1.IMyFeature f1,
org.pmesmeur.springboot.training.service.feature2.IMyFeature f2) {
this.serviceProperties = serviceProperties;
this.provider = provider;
this.f1 = f1;
this.f2 = f2;
}
...
package org.pmesmeur.springboot.training.service.feature1;
public interface IMyFeature {
void print();
}
package org.pmesmeur.springboot.training.service.feature1;
import org.springframework.stereotype.Component;
#Component
public class MyFeature implements IMyFeature {
#Override
public void print() {
System.out.print("HelloWorld");
}
}
package org.pmesmeur.springboot.training.service.feature2;
public interface IMyFeature {
void print();
}
package org.pmesmeur.springboot.training.service.feature2;
import org.springframework.stereotype.Component;
#Component
public class MyFeature implements IMyFeature {
#Override
public void print() {
System.out.print("FooBar");
}
}
If I use different names for my classes MyFeature, my problem disappears!!!
I am used to work with Guice and this framework does not have this kind of problem/limitation
It seems that the spring dependencies injection framework uses only
the class-name instead of package-name + class-name in order to
select its classes.
In "real-life" I have this problem with a far-bigger project and I would strongly prefer not to have to rename my classes: can anyone help me?
One last point, I would prefer to avoid "tricks" such as using
#Qualifier(value = "ABC") when injecting my classes: in my sample,
there should be no ambiguity for finding the correct instance of
MyFeature as they do not implement the same interface
Simply re-implementing BeanNameGenerator adds a new problem for beans declared/instantiated by names
#Component("HelloWorld")
class MyComponent implements IComponent {
...
}
#Qualifier(value = "HelloWorld") IComponent component
I solved this issue by extending AnnotationBeanNameGenerator and redefining method buildDefaultBeanName()
static class BeanNameGeneratorIncludingPackageName extends AnnotationBeanNameGenerator {
public BeanNameGeneratorIncludingPackageName() {
}
#Override
public String buildDefaultBeanName(BeanDefinition beanDefinition, BeanDefinitionRegistry beanDefinitionRegistry) {
return beanDefinition.getBeanClassName();
}
}
You can assigna a value for each component e.g. #Component(value="someBean") and then inject it with #Qualifier e.g.
#Autowired
public SomeService(#Qualifier("someBean") Some s){
//....
}
Spring provides autowire by type and name. Your classname are same. By default spring considers only className not package. But you can override this behaviour by defining custom implementation of BeanNameGenerator interface in which you can generate name using both package and name. I am not providing code solution because i think you should explore more on this.
You can do something like this;
in package a
public class MyFeature implements IMyFeature {
#Override
public void print() {
System.out.print("FooBar");
}
}
in package b
public class MyFeature implements IMyFeature {
#Override
public void print() {
System.out.print("HelloWorld");
}
}
and in some config class;
#Configuration
public class Configuration {
#Bean
public a.MyFeature f1() {
return new a.MyFeature();
}
#Bean
public b.MyFeature f2() {
return new b.MyFeature();
}
}
Then you can autowire them with names f1 and f2, that are the names of their respective bean constructor methods.
You can do the similar thing with #Component("f1") &
#Component("f2")
Even though different interfaces are implemented and are in different packages, identical bean name causes this trouble, and you have to utilize some sort of custom naming to distinguish. Utilizing some custom Spring logic would be way too ugly compared to what you'd do with above solutions.

Autowire working in unit test but not in main java class

I've a domain class that I want to auto-populate from external config. Here is my domain class:
#Data
#Configuration
#PropertySource("classpath:application.properties")
public class StudioVo {
#Value("${studio.code}")
private code;
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
Here is my context xml:
<bean class="org.springframework.batch.core.scope.StepScope" />
<bean id="ItemReader" class="com.sdm.studio.reader.StudioReader" scope="step">
<property name="studioVo" ref="StudioVo" />
</bean>
<bean id="StudioConfigVo" class="com.sdm.studio.domain.StudioVo" />
</bean>
Here is the class where I want to use the vo:
#Slf4j
#Data
public class StudioReader implements ItemReader<List<Studio>> {
private StudioVo studioVo;
public List<Studio> read() throws Exception {
System.out.println("getCode: " + studioVo.getCode()); //code is null here
return null;
}
}
However when I run it via unit test by autowiring, it runs fine. Like this:
#RunWith(SpringRunner.class)
#SpringBootTest
public class StudioTest {
#Autowired
private StudioVo studioVo;
#Test
public void testAutoPopulationOfStudio(){
System.out.println("getCode: "+ studioVo.getCode()); // works!
// Assert.assertTrue(studioVo.getCode().equals("102"));
}
}
Not sure what's going on here - I'm working with an old Spring Batch application wrapped in Spring Boot (so there is a mix of XML based and Java based config - and may be that is the cause of this issue). What am I missing?
In your StudioTest, you are autowiring StudioReader where as you missed the #Autowired in your StudioReader code, so add it as shown below:
#Slf4j
#Data
public class StudioReader implements ItemReader<List<Studio>> {
#Autowired //add this so that studioVo can be injected
private StudioVo studioVo;
//add other code
}
Please be certain to note that using #Autowire requires a chain of Spring-managed beans below it from wherever you are using it including the class in which you are using #Autowire. That is because Spring needs the precedent references to match up the object-reference hierarchy. I.e., in business logic layer ClassA, you want to #Autowire a field. ClassA itself needs to be a managed bean. Further, if the field you want to #Autowire holds an object that has referential dependencies to other objects (and most do), these also must be Spring-managed.
For example, the following will work:
package com.example.demo;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MessageRunner {
private static SetterMessage setterMessage;
public static void main(String[] args) {
setterMessage = (SetterMessage) (new AnnotationConfigApplicationContext(DemoConfiguration.class)).getBean("setterMessage");
setterMessage.setMessage("Finally it works.");
p(setterMessage.getMessage());
}
private static void p(String s) {
System.out.println(s);
}
}
DemoConfiguration.java looks like this:
package com.example.demo;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#Configuration
#ComponentScan(basePackages = "com.example.demo")
public class DemoConfiguration {
}
SetterMessage.java, this:
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
#Service
#Scope("prototype")
public class SetterMessage {
private String message = null;
#Autowired
private SetterMessage2 setterMessage2;
public String getMessage(){
return message+setterMessage2.getSubMessage();
}
public void setMessage(String message) {
this.message = message;
setterMessage2.setSubMessage("("+message+")");
}
}
SetterMessage2.java:
package com.example.demo;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
#Service
#Scope("prototype")
public class SetterMessage2 {
private String subMsg = "";
public void setSubMessage(String msg) {
subMsg = msg;
}
public String getSubMessage() {
return subMsg;
}
}
Note that SetterMessage2.java is annotated as a Component (#Service) but no field in it is autowired. That is because it's the end of the object reference chain. But because it is a Component, it can be autowired into SetterMessage.java. However look at MessageRunner.java's main() method and field declarations. Note that the class field SetterMessage is NOT autowired. If it were annotated as #Autowired, main() would fail at runtime, throwing an NPE with the reference to setterMessage in main(). This is because MessageRunner.java is not marked as some kind of component. So we need to grab a valid instance of MessageSetter from the application context and use it.
To emphasize, the following version of MessageRunner.java's main() method WILL FAIL, throwing an NPE, if MessageRunner.java looked like this:
...
public class MessageRunner {
#Autowired // <-- This will not do the job for us
private static SetterMessage setterMessage;
public static void main(String[] args) {
setterMessage.setMessage("Finally it works."); // NPE here on ref to setterMessage
p(setterMessage.getMessage());
}
...
This is a real gotchya for people new to Spring. In fact, I'd place it among the Top Five Spring Newbie Discouragers and a really evil, pernicious detail that has caused new Spring programmers countless hours in aggravation and Google searches. So I do hope that noting this phenom here will save at least some newbies time and high blood pressure spikes.
Note: If you go to create the above classes in your IDE, bear in mind these were developed with Spring Boot enabled.

Spring annotations - #Configuration to invoke spring bean auto-building

If I declare a class using #Bean and then component scan for the class, spring will instantiate the class by invoking it's constructor and injecting constructor args and injecting any fields marked with #Inject. For simplicity's sake, lets call this spring auto-building.
I dislike component scan and wish to avoid it completely (I don't wish to discuss my reasons for not liking it). I would like to use a #Configuration object instead but would still like to have the auto-building functionality available to me. Is it possible to invoke spring to auto-build my objects instead of explicitly having to pass all the constructor arguments in my #Configuration object?
Lets assume that I have a bean:
public class MyServiceImpl implements MyService {
public MyServiceImpl(Dependency1 d1, Dependency d2) { ... }
....
}
I could define a configuration object like this:
#Configuration
public class MyConfiguration {
// lets assume d1 and d2 are defined in another #Configuration
#Inject
Dependency1 d1;
#Inject
Dependency2 d2;
#Bean
public MyService myService() {
// I dislike how I have to explicitly call the constructor here
return new MyServiceImpl(d1, d2);
}
}
But now, I have explicitly had to call the MyServiceImpl constructor myself so I will have to keep updating this as my constructor changes over time.
I was hoping that I could declare an abstract method so that spring auto-building could take place:
#Configuration
public abstract class MyConfiguration {
#Bean
public abstract MyServiceImpl myService();
}
But this doesn't work. Is there a way that I can invoke spring auto-building without using a component scan?
In Google Guice, this can be done via the Binder:
https://google-guice.googlecode.com/svn/trunk/javadoc/com/google/inject/Binder.html
In Tapestry IOC, this can be done via the ServiceBinder:
http://tapestry.apache.org/ioc-cookbook-basic-services-and-injection.html#IoCCookbook-BasicServicesandInjection-SimpleServices
Update
Based on spod's answer, I was able to achieve what I was after (thanks!). Test case included for anyone that wants to do the same:
import java.util.Date;
import javax.inject.Inject;
import junit.framework.Assert;
import org.junit.Test;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
public class AutoBuildConfigurationTest {
#Configuration
public static class MyConfiguration {
#Inject
private AutowireCapableBeanFactory beanFactory;
#Bean
public Date date() {
return new Date(12345);
}
#Bean
public MyService myService() {
return autoBuild(MyService.class);
}
protected <T> T autoBuild(Class<T> type) {
return type.cast(beanFactory.createBean(type, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, true));
}
}
public static class MyService {
private Date date;
public MyService(Date date) {
this.date = date;
}
public Date getDate() {
return date;
}
}
#Test
public void testAutoBuild() {
ApplicationContext appContext = new AnnotationConfigApplicationContext(MyConfiguration.class);
MyService myService = appContext.getBean(MyService.class);
Assert.assertEquals(12345, myService.getDate().getTime());
}
}
The java based container configuration doesnt depend on doing a component scan in any way. Its merely a different approach for the XML based component configuration. With the XML configuration you'd just have to declare your bean with the MyServiceImpl class in case its already #inject annotated. Spring would recognize the annotations and take care of them. If you really want to instanciate MyServiceImpl from a #Configuration java class without calling the constructor yourself, then you'd have to make use of the bean factory (havent tested it, just give it a try):
#Configuration
public class MyConfiguration {
#Autowired AutowireCapableBeanFactory beanFactory;
#Bean public MyService myService() {
return beanFactory.createBean(MyServiceImpl.class, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, true);
}
}

Using Spring beans as a key with #Cacheable annotation

How to make following to work:
- a spring bean that has a method that should be cached with #Cacheable annotation
- another spring bean that creates keys for the cache (KeyCreatorBean).
So the code looks something like this.
#Inject
private KeyCreatorBean keyCreatorBean;
#Cacheable(value = "cacheName", key = "{#keyCreatorBean.createKey, #p0}")
#Override
public List<Examples> getExamples(ExampleId exampleId) {
...
However the above code doesn't work: it gives following exception:
Caused by: org.springframework.expression.spel.SpelEvaluationException:
EL1057E:(pos 2): No bean resolver registered in the context to resolve access to bean 'keyCreatorBean'
I checked the underlying cache resolution implementation, there doesn't appear to be a simple way to inject in a BeanResolver which is required for resolving the beans and evaluating expressions like #beanname.method.
So I would also recommend a somewhat hacky way along the lines of one which #micfra has recommended.
Along what he has said, have a KeyCreatorBean along these lines, but internally delegate it to the keycreatorBean that you registered in your application:
package pkg.beans;
import org.springframework.stereotype.Repository;
public class KeyCreatorBean implements ApplicationContextAware{
private static ApplicationContext aCtx;
public void setApplicationContext(ApplicationContext aCtx){
KeyCreatorBean.aCtx = aCtx;
}
public static Object createKey(Object target, Method method, Object... params) {
//store the bean somewhere..showing it like this purely to demonstrate..
return aCtx.getBean("keyCreatorBean").createKey(target, method, params);
}
}
In the case you have a static class function, it will work like this
#Cacheable(value = "cacheName", key = "T(pkg.beans.KeyCreatorBean).createKey(#p0)")
#Override
public List<Examples> getExamples(ExampleId exampleId) {
...
}
with
package pkg.beans;
import org.springframework.stereotype.Repository;
public class KeyCreatorBean {
public static Object createKey(Object o) {
return Integer.valueOf((o != null) ? o.hashCode() : 53);
}
}

Resources