Evaluate property from properties file in Spring's #EventListener(condition = "...") - spring

I would like to make the execution of an event handler dependent on whether or not a property is set to true in a properties file.
#EventListener(ContextRefreshedEvent.class, condition = "${service.enabled}")
public void onStartup() { }
However, this does not seem to work. I am getting the following error on startup:
org.springframework.expression.spel.SpelParseException: EL1043E:(pos 1): Unexpected token. Expected 'identifier' but was 'lcurly({)'
Is it possible to use a property from a properties file as a condition here?

The issue is condition argument is expecting a SPEL.
This works try it out.
In your bean where you have this #EventListener, add these lines
public boolean isServiceEnabled() {
return serviceEnabled;
}
#Value("${service.enabled}")
public boolean serviceEnabled;
change your declaration of evnt listener like this
#EventListener(classes = ContextRefreshedEvent.class, condition = "#yourbeanname.isServiceEnabled()")
public void onStartup() { }
change yourbeanname with the correct bean name .

I had the same annoying experience (with Spring Boot 2.4.2 on Java11).
In my case I had the boolean property in a #ConfigurationProperties class anyways in the same java file and still struggled a bit. First the #ConfigurationProperties need to be annotated as #Component to actually be a valid Bean and can be used in SpEL.
And I had to use the same long attributeName for the ConfigurationProperties in the Service itself and the EventListener Annotation for the Bean resolution to work fine. I needed some the ConfigurationProperties values also in another place of the Service, that's why they needed to be (Constructor) Autowired as well...
So this worked for me:
#ConfigurationProperties("my.custom.path")
#Component //Important to make this a proper Spring Bean
#Data //Lombok magic for getters/setters etc.
class MyCustomConfigurationProperties {
boolean refreshAfterStartup = true;
}
#Service
#RequiredArgsConstructor //Lombok for the constructor
#EnableConfigurationProperties(MyCustomConfigurationProperties.class)
#EnableScheduling
public class MyCustomService {
private final MyCustomConfigurationProperties myCustomConfigurationProperties;
#EventListener(value = ApplicationReadyEvent.class, condition = "#myCustomConfigurationProperties.refreshAfterStartup")
public void refresh() {
//the actual code I want to execute on startup conditionally
}
}

Related

SpelEvaluationException: Attempted to call method on null context object

I'm trying to do a simple cache task. I have a Holiday object, which has two fields: referenceDate and isHoliday. I then have a method that will make a HTTP request to a rest api in order to check if a date is a holiday or not. What I want to achieve is: if the current cached Holiday object has the same referenceDate as the one passed as a parameter, return the cached value. I have a specific class to perform that check. Here is the code:
Holiday class
#AllArgsConstructor
#Getter
public class Holiday {
public LocalDate referenceDate;
public boolean isHoliday;
}
CacheService class
#DomainService
public class CacheService {
#Autowired
private CacheManager cacheManager;
public boolean isReferenceDateCached(final LocalDate referenceDate){
final Holiday holiday = (Holiday) cacheManager.getCache("holiday").get("holidaycheck");
return(holiday.getReferenceDate().equals(referenceDate));
}
}
HolidatInfraService class
#AllArgsConstructor
#Service
#Slf4j
public class HolidayInfraService {
#Autowired
private final CacheService cacheService;
#Cacheable(value = "holiday", key = "holidaycheck", condition = "#cacheService.isReferenceDateCached(#holidayDateToCheck)")
public Holiday isHoliday(final LocalDate holidayDateToCheck) {
//some code to call a rest api
}
}
And this is the error I get from my unit test when I try to holidayInfraService.isHoliday(someDate):
org.springframework.expression.spel.SpelEvaluationException: EL1011E: Method call: Attempted to call method isReferenceDateCached(java.time.LocalDate) on null context object
From this exception message seems pretty obvious that cacheService is null. However, when I debbuged the code and got inside isHoliday, cacheService is not null. Maybe it hasn't been autowired yet by the time the annotation runs? It is also the first time I'm working with SPEL, so maybe something there too. If in fact cacheService is not autowired yet, is there a workaround?
Because you are using the #Cacheable(...) annotation, Spring will evaluate that SpEL expression with the context class MethodBasedEvaluationContext. This will set the root object to be a CacheExpressionRootObject, and property lookups will be performed using that root object.
Since you are trying to refer to a property on a bean, the easiest solution would be to directly reference that bean in the SpEL expression. This is done with an # (e.g. #myBeanName). Spring will then look for a bean with that name in the ApplicationContext. Remember that a bean defined without an explicit name will be named using the lower camel-came of the class name. For example, a class with the name MyBeanName will have a bean name of myBeanName.
Try changing your condition= block to
#Cacheable(value = "holiday", key = "holidaycheck", condition = "#cacheService.isReferenceDateCached(#holidayDateToCheck)")
public Holiday isHoliday(final LocalDate holidayDateToCheck) {
//some code to call a rest api
}
In SpEL's MethodBasedEvaluationContext to access method variable, you need to use #, but no # is needed for class variables. Property lookup will automatically find getter for the property without # symbol. So, your SpEL should be
cacheService.isReferenceDateCached(#holidayDateToCheck)
And if this SpEL won't work, try creating getter method for cacheService.
Try
#Cacheable(value = "holiday", key = "holidaycheck", condition = "#{cacheService.isReferenceDateCached(#holidayDateToCheck)}")
public Holiday isHoliday(final LocalDate holidayDateToCheck) {
//some code to call a rest api
}

How to test #ConfigurationProperties with ApplicationContextRunner from spring-boot-test?

I need to test my autoconfiguration classes that make use of #ConfigurationProperties beans. I'm making use of ApplicationContextRunner as documented in https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-test-autoconfig to make tests faster and avoid starting the servlet container between each variations. However, beans annotated with #AutoconfigurationProperties are not populated with values injected into ApplicationContextRunner.
I suspect that I'm hitting problem similar to https://stackoverflow.com/a/56023100/1484823
#ConfigurationProperties are not managed by the application context you build in tests, although they will be load when the application launches, because you have #EnableConfigurationProperties on your app main class.
How can I enable support for #ConfigurationProperties with ApplicationContextRunner ?
Here is the corresponding code
#Test
void ServiceDefinitionMapperPropertiesAreProperlyLoaded() {
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(
SingleServiceDefinitionAnswerAutoConfig.class,
DynamicCatalogServiceAutoConfiguration.class
))
// .withPropertyValues(DynamicCatalogProperties.OPT_IN_PROPERTY + "=true") //Not sure why this seems ignored
.withSystemProperties(DynamicCatalogConstants.OPT_IN_PROPERTY + "=true",
ServiceDefinitionMapperProperties.PROPERTY_PREFIX
+ServiceDefinitionMapperProperties.SUFFIX_PROPERTY_KEY+ "=suffix")
;
contextRunner.run(context -> {
assertThat(context).hasSingleBean(ServiceDefinitionMapperProperties.class);
ServiceDefinitionMapperProperties serviceDefinitionMapperProperties
= context.getBean(ServiceDefinitionMapperProperties.class);
assertThat(serviceDefinitionMapperProperties.getSuffix()).isEqualTo("suffix");
});
}
which fails with:
org.opentest4j.AssertionFailedError:
Expecting:
<"">
to be equal to:
<"suffix">
but was not.
Expected :suffix
Actual :
<Click to see difference>
at org.springframework.cloud.appbroker.autoconfigure.DynamicCatalogServiceAutoConfigurationTest
public class DynamicCatalogServiceAutoConfiguration {
[...]
#Bean
#ConfigurationProperties(prefix=ServiceDefinitionMapperProperties.PROPERTY_PREFIX, ignoreUnknownFields = false)
public ServiceDefinitionMapperProperties serviceDefinitionMapperProperties() {
return new ServiceDefinitionMapperProperties();
}
[...]
}
Full sources available at https://github.com/orange-cloudfoundry/osb-cmdb-spike/blob/0da641e5f2f811f48b0676a25c8cbe97895168d1/spring-cloud-app-broker-autoconfigure/src/test/java/org/springframework/cloud/appbroker/autoconfigure/DynamicCatalogServiceAutoConfigurationTest.java#L89-L107
ps: I was about to submit an issue to https://github.com/spring-projects/spring-boot/issues to suggest documentation enhancement to warn of such limitation in ApplicationContext, and to ask for ways to turn on support for #ConfigurationProperties. Following guidance at https://raw.githubusercontent.com/spring-projects/spring-boot/master/.github/ISSUE_TEMPLATE.md, I'm first making sure here I'm not misunderstanding the problem.
If you want to populate a bean annotated with #ConfigurationProperties class as part of your test, and you normally depend on a configuration class annotated with #EnableConfigurationProperties to populate that bean, then you can create a trivial configuration class just for the test:
#ConfigurationProperties("app")
public class ConfigProps {
private int meaningOfLife;
public int getMeaningOfLife() { return meaningOfLife; }
public void setMeaningOfLife(int meaning) { this.meaningOfLife = meaning; }
}
class ConfigPropsTest {
private final ApplicationContextRunner runner = new ApplicationContextRunner();
#EnableConfigurationProperties(ConfigProps.class)
static class TrivialConfiguration {
}
#Test
void test() {
runner.withUserConfiguration(TrivialConfiguration.class)
.withPropertyValues("app.meaning-of-life=42")
.run(context -> {
assertEquals(42, context.getBean(ConfigProps.class).getMeaningOfLife());
});
}
}
Passing TrivialConfiguration to the ApplicationContextRunner is sufficient to make it create ConfigProps and populate it using the available properties.
As far as I can tell, none of the classes involved in your test enable configuration property binding. As a result, no properties are bound to ServiceDefinitionMapperProperties. You can enable configuration property binding using #EnableConfigurationProperties. A typical place to add it would be on DynamicCatalogServiceAutoConfiguration as its serviceDefinitionMapperProperties bean relies on configuration properties being enabled.

Error while using parameter in #Scheduled in a spring 4.1 application

I have Spring 4.1 Application. I am trying to schedule based on value from property file. I have read this post. But I do not want the following way of EL inside #Scheduled
#Scheduled(fixedDelayString = "${my.fixed.delay.prop}")
public void readLog() {
...
}
Here is my class.
public class MyService {
#Value("${timerInMilliSeconds: 60000}")
private long timerinMilliSeconds;
public myService(){
}
#Scheduled(fixedRate = timerinMilliSeconds)
public void myTimer() {
//do stuff
}
}
I get this error.
The value for annotation attribute Scheduled.fixedRate must be a constant
expression
You can't do that; it's a limitation of the way annotations work - Strings in annotations have to be constants (they are stored in the class and can't be different for each instance).
By the way ${my.fixed.delay.prop} is not "EL", it's a property placeholder.

Spring Boot - Detect and terminate if property not set?

Is there any way for a Spring Boot web application to abort at startup if a required property is not set anywhere (neither in the application.properties file nor the other property sources)? Right now, if the property is included in another property, it seem that Spring Boot simply avoids substitution.
For example, in my application.properties file, I have the line:
quartz.datasource.url=jdbc:hsqldb:${my.home}/database/my-jobstore
Right now, if "my.home" is not set elsewhere, Spring Boot is setting the url literally to "jdbc:hsqldb:${my.home}/database/my-jobstore" (no substitution).
I would like to have the application fail to start if the property my.home were not set anywhere else.
To throw a friendly exceptions just put a default null value in property, check and throw a exception in afterProperty method.
#Component
public static class ConfigurationGuard implements InitializingBean {
#Value("${my.home:#{null}}")
private String myHomeValue;
public void afterPropertiesSet() {
if (this.myHomeValue == null or this.myHomeValue.equals("${my.home}") {
throw new IllegalArgumentException("${my.home} must be configured");
}
}
}
Create a bean with a simple #Value(${my.home}) annotated field. - Then Spring will try to inject that value and will fail and therefore stop when the value is not there.
Just #Value(${my.home}) private String myHomeValue; is enough for normal (not Boot) Spring applications for sure! But I do not know whether Boot has some other configuration to handle missing values: If there is an other failure management than you could check that value in an PostCreation method.
#Component
public static class ConfigurationGuard implements InitializingBean {
#Value(${my.home})
private String myHomeValue;
/**
* ONLY needed if there is some crude default handling for missing values!!!!
*
* So try it first without this method (and without implements InitializingBean)
*/
public void afterPropertiesSet() {
if (this.myHomeValue == null or this.myHomeValue.equals("${my.home}") {
throw new IllegalArgumentException("${my.home} must be configured");
}
}
}
The default behaviour in current versions of Spring Boot (1.5.x, 2.0.x, 2.1.x) is to throw an exception if a placeholder can not be resolved.
There will a be an exception like this one :
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'app.foo.undefined' in value "${app.foo.undefined}"
It works because a bean of type PropertySourcesPlaceholderConfigurer (from spring-context) is automatically registered in Spring Boot, in this class : PropertyPlaceholderAutoConfiguration. And by default, the property ignoreUnresolvablePlaceholders in PropertySourcesPlaceholderConfigurer is set to false, which means an exception must be thrown if a placeholder is unresolved (be it nested or not).
Although they work, I think the approach in the foremost answer is somewhat brittle, as it only works for the predefined name(s), and will silently stop checking the when someone changes quartz.datasource.url in the configs to use a different expansion.
Ideally, I want this value of ignoreUnresolvablePlaceholders to be false to get wholesale expansion checking when parsing my configs such as application.properties or its YAML variants, but it's hard-coded to true for these cases. This unfortunately leaves strings such as ${FOO} in its unexpanded form if FOO cannot be found, making troubleshooting extremely painful. This is especially the case for fields that don't readily appear in the logs such as passwords.
While I couldn't find a way of changing ignoreUnresolvablePlaceholders short of modifying Spring Boot's classes, I did find an alternative of using a custom PropertySource implementation and defining a new syntax such as "${!FOO}" to indicate FOO must exist as an environment variable or die. (The OP didn't mention whether my.home is an environment variable but the code below is for environment variables.)
First, an EnvironmentPostProcessor implementation is required for registering the custom PropertySource. This StrictSystemEnvironmentProcessor.java does this as well as holds the implementation of the custom PropertySource:
package some.package;
#Order(Ordered.LOWEST_PRECEDENCE)
class StrictSystemEnvironmentProcessor implements EnvironmentPostProcessor {
private static final String PROPERTY_SOURCE_NAME = "STRICT_" + StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME;
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
if (environment.getPropertySources().contains(PROPERTY_SOURCE_NAME)) {
return;
}
SystemEnvironmentPropertySource delegate = (SystemEnvironmentPropertySource)environment.getPropertySources()
.get(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME);
environment.getPropertySources().addLast(new StrictSystemEnvironmentPropertySource(delegate));
}
private static class StrictSystemEnvironmentPropertySource extends SystemEnvironmentPropertySource {
public StrictSystemEnvironmentPropertySource(SystemEnvironmentPropertySource delegate) {
super(PROPERTY_SOURCE_NAME, delegate.getSource());
}
#Override
public Object getProperty(String name) {
if (name.startsWith("!")) {
String variableName = name.substring(1);
Object property = super.getProperty(variableName);
if (property != null) {
return property;
}
throw new IllegalStateException("Environment variable '" + variableName + "' is not set");
}
return null;
}
}
}
Instead of returning null, an exception is thrown for names that start with !.
This META-INF/spring.factories is also required so that Spring initializes our EnvironmentPostProcessor:
org.springframework.boot.env.EnvironmentPostProcessor=some.package.StrictSystemEnvironmentProcessor
Then henceforth, I can write all environment variables substitutions in my configs as ${!FOO} to get strict existance checking.
You can also create a #ConfigurationProperties bean, and decorate it with #Validated and #NotNull. This will throw an exception during startup when the value is not present (or null), e.g.
#Validated
#ConfigurationProperties("my")
public class MyProperties {
#NotNull
private String home;
// getter/setter, or constructor. See #ConstructorBinding.
}
For reference: Spring Boot 2.6 - #ConfigurationProperties Validation.
Note that you may need to add spring-boot-starter-validation, or another validator, depending on your project.
Then, you can just supply it as a dependency when needed, e.g.
#Component
public class AnotherBean {
private final MyProperties myProps;
public AnotherBean(MyProperties myProps) {
this.myProps = myProps;
}
// some code that uses myProps.getHome()
}

Spring-Wicket: Bean Injection with Resource

I am writing tests for my wicket-application and need to inject a Spring Bean into a page (done by annotation) to do this.
Consider following code:
protected void setUp() {
tester = new WicketTester();
scanService = new ScanService();
ApplicationContextMock appctx=new ApplicationContextMock();
appctx.putBean("pxGenericService", new PxGenericServiceImpl());
tester.getApplication().getComponentInstantiationListeners().add(new SpringComponentInjector(tester.getApplication(), appctx));
}
This actually seem to work (no nullpointer). The problem is: the bean got a resource (variable with #Resource annotation) and when I run the test on the page, this resource turns out to be null (nullpointer exception). How do I fix this problem?
You also have to add an instance of all dependencies your bean has to the mock application context. So add an instance of the class PxGenericServiceImpl uses to appctx.
I don't think that SpringComponentInjector supports #Resource. The only supported annotations are #SpringBean and #Inject. See AnnotProxyFieldValueFactory:
#Override
public boolean supportsField(final Field field)
{
return field.isAnnotationPresent(SpringBean.class) || field.isAnnotationPresent(Inject.class);
}

Resources