Long story short:
Is there a way to interpret the string resulting from ${my.property} as a SpEL expression within a #Value annotation without using converters, e.g. something like #Value("#{${my.property}})?
I have an abstract factory (simplified) that lets me build some common objects that are part of the configuration of my system.
#Component
public class Factory {
public Product makeVal(int x) { return new Product(5); }
}
In order to be more flexible, I'd like to let users write SpEL expressions in the app.properties file, so that the factory can directly be accessed:
my.property = #Factory.makeVal(12)
Now, in the class needing this property, to achieve my goal I wrote the following code.
#Value("#{${my.property}}")
private Product obj;
I thought that ${my.property} would be be macro-expanded and then evaluated by #{} as the corresponding SpEL expression, #Factory.makeVal(12) in the example above. Unfortunately, this wasn't the case, and loading the Spring context resulted in an error saying that it could not convert a string (the property's value ${my.property}) to the destination type Product.
Now, I solved this by writing a class implementing Converter<String, Product>, but it's very convoluted as I need there to programmatically evaluate the string as a SpEL expression by instantiating the ExpressionParser and so on.
But is there a simpler solution? Is there a single SpEL expression to be put in #Value annotations that lets me simply evaluate ${my.property} as a SpEL expression by itself, please?
Maybe it just a matter of replacing #Factory with factory in the property value. This test passes for me:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { SpelTest.Config.class })
public class SpelTest
{
#Value("#{${my.property}}")
Product _product;
#Test
public void evaluating_spel_from_property_value() throws Exception
{
Assert.assertEquals(1234, _product.value);
}
#Component
public static class Factory
{
public Product makeVal(int x) { return new Product(x); }
}
public static class Product
{
public final int value;
public Product(final int value) { this.value = value; }
}
#Configuration
#ComponentScan(basePackageClasses = SpelTest.class)
public static class Config
{
#Bean
public Factory factory() { return new Factory(); }
#Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
final PropertySourcesPlaceholderConfigurer psc = new PropertySourcesPlaceholderConfigurer();
final MutablePropertySources sources = new MutablePropertySources();
sources.addFirst(new MockPropertySource()
.withProperty("my.property",
"factory.makeVal(1234)"));
psc.setPropertySources(sources);
return psc;
}
}
}
Related
I’d like to apply a customizable aspect on two different services (spring bean). My problem is how/where to set/define the pointcut expression. One normally defines the pointcut expression on a ‘dummy method’ or directly on the advice method. However that means the pointcut is static (not customizable).
I’d like to define the pointcut at the bean creation level to be able to create the same kind of advice for different targets. Ideally I’d like to do something like this:
#Aspect
public class ServiceAspect {
private static final Logger LOG = LoggerFactory.getLogger(ServiceAspect.class);
private final String discriminator;
// no advice defined here!!!
public ServiceAspect(String discriminator) { this.discriminator = discriminator; }
public Object around(ProceedingJoinPoint jp) throws Throwable {
LOG.info(discriminator + " called");
return jp.proceed();
}
}
#Configuration
#EnableAspectJAutoProxy
#PropertySource("classpath:application.properties")
public class ServiceConfiguration {
#Bean
public MyService service1() { return new MyServiceImpl(); }
#Bean
#Around("bean(service1)") // define the advice when bean is created
#ConditionalOnProperty("aspect1Enbaled")
public ServiceAspect aspect() {
return new ServiceAspect("Aspect-1");
}
#Bean
public YourService service2() { return new YourServiceImpl(); }
#Bean
#Around("bean(service2)") // define a different advice when bean is created
#ConditionalOnProperty("aspect2Enbaled")
public ServiceAspect aspect() {
return new ServiceAspect("Aspect-2");
}
}
Notice that the #Around annotation is on the definition of the bean. I can thus reuse the aspect for different target. Using the #ConditionalOnProperty, this would enable me to turn on/off individual aspect base on a property.
Can anyone help me with this? I suspect I’ll need to create some kind of factory but can’t seem to see how I can REPLACE an already defined bean (the service bean) with a proxy!
I'd like to use queue names using a specific pattern, like project.{queue-name}.queue. And to keep this pattern solid, I wrote a helper class to generate this name from a simple identifier. So, foo would generate a queue called project.foo.queue. Simple.
But, the annotation RabbitListener demands a constant string and gives me an error using my helper class. How can I achieve this (or maybe another approach) using RabbitListener annotation?
#Component
public class FooListener {
// it doesn't work
#RabbitListener(queues = QueueName.for("foo"))
// it works
#RabbitListener(queues = "project.foo.queue")
void receive(final FooMessage message) {
// ...
}
}
To create and listen to a queue name constructed from a dynamic UUID, you could use random.uuid.
The problem is that this must be captured to a Java variable in only one place because a new random value would be generated each time the property is referenced.
The solution is to use Spring Expression Language (SpEL) to call a function that provides the configured value, something like:
#RabbitListener(queues = "#{configureAMQP.getControlQueueName()}")
void receive(final FooMessage message) {
// ...
}
Create the queue with something like this:
#Configuration
public class ConfigureAMQP {
#Value("${controlQueuePrefix}-${random.uuid}")
private String controlQueueName;
public String getControlQueueName() {
return controlQueueName;
}
#Bean
public Queue controlQueue() {
System.out.println("controlQueue(): controlQueueName=" + controlQueueName);
return new Queue(controlQueueName, true, true, true);
}
}
Notice that the necessary bean used in the SpEL was created implicitly based on the #Configuration class (with a slight alteration of the spelling ConfigureAMQP -> configureAMQP).
Declare a magic bean, in this case implicitly named queueName:
#Component
public class QueueName {
public String buildFor(String name) {
return "project."+name+".queue";
}
}
Access this using a "constant string" that will be evaluated at runtime:
#RabbitListener(queues = "#{queueName.buildFor(\"foo\")}")
If {queue-name} would came from yml file - it should work:
#RabbitListener(queues = "${queue-name}")
public void receiveMessage(FooMessage message) {
}
Spring will inject value from application.yml.
I have the following classes:
#Component
#ConifgurationProperties("redis")
public class RedisProperties {
private List<String> hosts;
// getters, setters
}
#Component
public class StaticRedisHostsProvider implements RedisHostsProvider {
private final RedisProperties redisProperties;
public StaticRedisHostsProvider(RedisProperties redisProperties) {
this.redisProperties = redisProperties;
}
#Override
public List<String> getAll() {
return redisProperties.getHosts();
}
}
#Component
public DiscoveryBasedRedisHostsProvider { ... }
I want StaticRedisHostsProvider to be used if redis.hosts property is specified, DiscoveryBasedRedisHostsProvider otherwise.
I could annotate StaticRedisHostsProvider with #ConditionalOnProperty(prefix = "redis", name = "hosts"), but there is no similar #ConditionalOnMissingProperty annotation for using with DiscoveryBasedRedisHostsProvider.
I tried to use #ConditionalOnExpression("#redisProperties.hosts.empty"), but it doesn't work for some reason:
Description:
A component required a bean named 'redisProperties' that could not be found.
Action:
Consider defining a bean named 'redisProperties' in your configuration.
Is there some way to fix that (maybe with #Order or similar annotations)?
Here's my take on this issue with the use of custom conditions in Spring autoconfiguration.
#Conditional annotations are executed very early in during the application startup. Properties sources are already loaded but ConfgurationProperties beans are not yet created. However we can work around that issue by binding properties to Java POJO ourselves.
First I introduce a functional interface which will enable us to define any custom logic checking if properties are in fact present or not. In your case this method will take care of checking if the property List is empty or null.
public interface OptionalProperties {
boolean isPresent();
}
Now let's create an annotation which will be metannotated with Spring #Conditional and allow us to define custom parameters. prefix represents the property namespace and targetClass represents the configuration properties model class to which properties should be mapped.
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Conditional(OnConfigurationPropertiesCondition.class)
public #interface ConditionalOnConfigurationProperties {
String prefix();
Class<? extends OptionalProperties> targetClass();
}
And now the main part. The custom condition implementation.
public class OnConfigurationPropertiesCondition extends SpringBootCondition {
#Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
MergedAnnotation<ConditionalOnConfigurationProperties> mergedAnnotation = metadata.getAnnotations().get(ConditionalOnConfigurationProperties.class);
String prefix = mergedAnnotation.getString("prefix");
Class<?> targetClass = mergedAnnotation.getClass("targetClass");
// type precondition
if (!OptionalProperties.class.isAssignableFrom(targetClass)) {
return ConditionOutcome.noMatch("Target type does not implement the OptionalProperties interface.");
}
// the crux of this solution, binding properties to Java POJO
Object bean = Binder.get(context.getEnvironment()).bind(prefix, targetClass).orElse(null);
// if properties are not present at all return no match
if (bean == null) {
return ConditionOutcome.noMatch("Binding properties to target type resulted in null value.");
}
OptionalProperties props = (OptionalProperties) bean;
// execute method from OptionalProperties interface
// to check if condition should be matched or not
// can include any custom logic using property values in a type safe manner
if (props.isPresent()) {
return ConditionOutcome.match();
} else {
return ConditionOutcome.noMatch("Properties are not present.");
}
}
}
Now you should create your own configuration properties class implementing OptionalProperties interface.
#ConfigurationProperties("redis")
#ConstructorBinding
public class RedisProperties implements OptionalProperties {
private final List<String> hosts;
#Override
public boolean isPresent() {
return hosts != null && !hosts.isEmpty();
}
}
And then in Spring #Configuration class.
#Configuration
class YourConfiguration {
#ConditionalOnConfigurationProperty(prefix = "redis", targetClass = RedisProperties.class)
StaticRedisHostsProvider staticRedisHostsProvider() {
...
}
#ConditionalOnMissingBean(StaticRedisHostsProvider.class)
DiscoveryBasedRedisHostsProvider discoveryRedisHostsProvider() {
...
}
}
There are two downsides to this solution:
Property prefix must be specified in two locations: on #ConfigurationProperties annotation and on #ConditionalOnConfigurationProperties annotation. This can partially be alleviated by defining a public static final String PREFIX = "namespace" in your configuration properties POJO.
Property binding process is executed separately for each use of our custom conditional annotation and then once again to create the configuration properties bean itself. It happens only during app startup so it shouldn't be an issue but it still is an inefficiency.
I want to let Spring assign a property value.
public class Foobar {
#Value("${example.property.foo:bar}")
private String foo;
}
Let's say I want to refer to example.property.foo in several different places, so I'd rather assign the flag as a constant on Foobar:
public class Foobar {
public static final String FOO_PROPERTY_FLAG = "example.property.foo";
}
The setting of example.property.foo=whatever happens elsewhere (as a system property, or in a #TestPropertySource).
How can I refer to FOO_PROPERTY_FLAG in the annotation? This works:
#Value("${" + FOO_PROPERTY_FLAG + ":bar}")
But it's kind of ugly. Can I use the "#{}" expression syntax here somehow?
#Value("${#{FOO_PROPERTY_FLAG}:bar}") // doesn't work; value is never injected
You can do something like:
public static final String KEY = "propertyName";
#Value("#{T(a.b.c.package.MyConstants).KEY}")
The important part is to specify package and class. Otherwise spring will try to lookup constant in BeanExpressionContext which is actually executing your SpEL
private #Value("${propertyName}") String propertyField;
No getters or setters!
With the properties being loaded via the config:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
p:location="classpath:propertyFile.properties" name="propertiesBean"/>
There's also the totally non-Xml version:
#PropertySource("classpath:propertyFile.properties")
public class AppConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
Make sure and add in the namespace URI xmlns:p="springframework.org/schema/p"; to use the p: prefixed attributes
In Spring 4.2+, we can use #EventListener annotation with a "condition" expression.
In my scenario, I need to match the id of the event object with a regular expression that is configured in a .properties file.
However, it seems impossible to reference any bean's property or method from the condition's regular expression, as the root context seems to be the event object itself.
So far, I have an abstract class, that sets the event id pattern property based on the class name. The goal is to make the implementation of each Event Listener as clean and simple as possible.
#Service
#PropertySource(value = "classpath:subscriberEventMapping.properties")
public abstract class AbstractEventHandler implements IEventHandler {
private String eventIdPattern;
#Autowired
Environment env;
#Autowired(required = true)
public void configureEventIdPattern() {
String simpleClassName = this.getClass().getSimpleName();
String resolvedEventIdPattern = env.getProperty(
simpleClassName.substring(0,1).toLowerCase() +
simpleClassName.substring(1, simpleClassName.length()));
this.eventIdPattern = resolvedEventIdPattern == null ? ".*" : resolvedEventIdPattern;
}
public String getEventIdPattern() {
return eventIdPattern;
}
}
The properties file looks like this:
regExpEventHandler=^(901|909|998|1000)$
dummyEventHandler=^([1-9][0-9]{0,2}|1000)$
And then, I have a sample Event Listener that extends the above Abstract class:
#Service
public class RegExpEventHandler extends AbstractEventHandler {
#Log
private ILog logger;
#Override
#EventListener(condition = "#event.eventid matches #regExpEventHandler.getEventIdPattern()")
public void onEvent(Event event) {
logger.debug("RegExpEventHandler processing : {} with event pattern : {}", event, getEventIdPattern());
}
}
The problem is that the expression
"#event.eventid matches #regExpEventHandler.getEventIdPattern()"
does not work, because the bean "#regExpEventHandler" cannot be found in the context used by the #EventListener.
Is there a way to access methods or properties of an existing Spring Bean here? Any other better approach for this scenario ?
I know I can easily access STATIC constants or methods by using something like:
#event.eventid matches T(my.package.RegExpEventHandler.MY_CONSTANT)
But a String constant (static final) cannot be initialized from a properties file using a #Value expression.
Using NON-FINAL static constants can work, but then EACH Event Listener needs to add boiler-plate to initialize the static constant from a non-static variable using a #Value expression, which we want to avoid.
Thanks a lot in advance !
It works for me - I looked at the EventExpressionEvaluator and saw that it added a bean resolver to the evaluation context...
public EvaluationContext createEvaluationContext(ApplicationEvent event, Class<?> targetClass,
Method method, Object[] args, BeanFactory beanFactory) {
Method targetMethod = getTargetMethod(targetClass, method);
EventExpressionRootObject root = new EventExpressionRootObject(event, args);
MethodBasedEvaluationContext evaluationContext = new MethodBasedEvaluationContext(
root, targetMethod, args, getParameterNameDiscoverer());
if (beanFactory != null) {
evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
}
return evaluationContext;
}
So I wrote a quick test...
#SpringBootApplication
public class So43225913Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(So43225913Application.class, args);
context.publishEvent("foo");
}
#EventListener(condition = "#bar.accept(event)")
public void listen(Object event) {
System.out.println("handler:" + event);
}
#Bean
public Bar bar() {
return new Bar();
}
public static class Bar {
public boolean accept(Object o) {
System.out.println("bar:" + o);
return true;
}
}
}
and it works just fine...
bar:org.springframework.context.PayloadApplicationEvent[...
handler:foo
(This was with 4.3.7; boot 1.5.2).