Using #Profile annotation with property place holder value - spring

When we define profile for any component in spring, we declare it as
#Profile(value="Prod"). But i want to give that value from properties file.
Is it possible? If yes, how?

Going through the source code of Spring, I have arrived to the conclusion that what you are asking is not possible. To make this clear, it is not possible to have Spring evaluate ${property} inside #Profile.
Specifically take a look at ProfileCondition which checks whether or not the profile is active.
class ProfileCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (context.getEnvironment() != null) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
return true;
}
}
return false;
}
}
return true;
}
}
The meat is context.getEnvironment().acceptsProfiles(((String[]) value)).
Now if you check the source of AbstractEnvironment where acceptsProfiles resides, you will find that the control reaches
protected boolean isProfileActive(String profile) {
validateProfile(profile);
return doGetActiveProfiles().contains(profile) ||
(doGetActiveProfiles().isEmpty() && doGetDefaultProfiles().contains(profile));
}
which does not attempt to evaluate the expression, but takes the String verbatim (also note that nowhere before isProfileActive is the String expression being evaluated either)
You can find the code I have mentioned above here and here.
One another note, I am not sure why you would need to have a dynamic profile name.

You seem to be trying to abuse the #Profile annotation. Use profiles for enabling functionality. Not for saying that a Bean is active in a specific environment.
A way to achieve something closer to what I think you are looking for, would be to have properties files specific to your environment, which define the profiles which should be active in them. This way, you can start your app with an arg such as:
--spring.profiles.active=prd
Spring Boot will then attempt to load application-prd.properties, where you could activate environment-specific profiles:
spring.profiles.active=sqlserver,activedirectory,exchangeemail
That way your beans will only be activated when the functionality they provide is required.

An alternative would be when creating the ApplicationContext:
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(ConfigClass.class);
String profile = aplicationContext.getEnvironemnt().getRequiredProperty("profile");
applicationContext.getEnvironment().setActiveProfiles(profile);

Related

enabling aspectj with environment variables

How can we enable/disable an aspect using environment variables?
I know it is possible to enable/disable aspectj in spring boot application using following properties
spring:
aop:
auto: true
Or:
spring.aop.auto=true
And removing #EnableAspectJAutoProxy, but this stops all of our other aspects / joins.
This is the one I want to disable, how do I do it
#Aspect
#Component
public class SomeAspect {
#Around("#annotation(someAnnotation)")
public Object doSomething(ProceedingJoinPoint joinPoint, SomeAnnotation sa) throws Throwable {
// ...
}
//others
}
In order to dynamically deactivate a single advice inside an aspect class, you can use an if() pointcut.
If you want to completely disable an aspect (or any other Spring bean or component) based on conditions like e.g. a property in application.config, have a look at #Conditional and its special cases #ConditionalOn*. For example:
#Aspect
#Component
#ConditionalOnProperty(prefix = "org.acme.myapp", name = "aspect_active")
public class SomeAspect {
// ...
}
Something like this in application.config would deactivate the aspect:
org.acme.myapp.aspect_active=false
The aspect would also be inactive if there was no such property in the application config at all. If you rather want to default to an active aspect, just use
#ConditionalOnProperty(prefix = "org.acme.myapp", name = "aspect_active", matchIfMissing = true)
You can further fine-tune the behaviour as described in the javadoc.
See also:
https://www.baeldung.com/spring-conditional-annotations
https://www.baeldung.com/spring-conditionalonproperty
Update:
In order to dynamically deactivate a single advice inside an aspect class, you can use an if() pointcut.
Oops, sorry, I am a native AspectJ user and forgot that Spring AOP does not support the if() pointcut designator. So probably the best you can do is an if expression at the beginning of your advice, depending on a #Value property.
#Value("${org.acme.myapp.advice_active:false}")
private boolean adviceActive;
#Around("#annotation(someAnnotation)")
public Object doSomething(ProceedingJoinPoint joinPoint, SomeAnnotation sa) throws Throwable {
// Skip advice logic if inactive, simply proceed and return original result
if (!adviceActive)
return joinPoint.proceed();
// Regular advice logic if active
System.out.println(joinPoint);
// Either also proceed or do whatever else is the custom advice logic, e.g.
// - manipulate method arguments,
// - manipulate original return value,
// - skip proceeding to the original method altogether and return something else.
return joinPoint.proceed();
}
Of course, you can also use my original solution and just factor out the advice you wish to deactivate into a separate aspect class, if you need that kind of granularity. That would be less hassle, and the advice method code would be more readable.

Spring AOP - annotation with args

I am stuck with a problem in spring boot. I am trying to give extra functionality to some RestControllers, and I am trying to achieve it with some custom annotations. Here is an example.
My annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface MyCustomAnnotation {
String someArg();
}
My aspect:
#Aspect
#Component
public class MyAspect {
#Around(
value = "#annotation(MyCustomAnnotation)",
argNames = "proceedingJoinPoint,someArg"
)
public Object addMyLogic(ProceedingJoinPoint proceedingJoinPoint, String someArg)
throws Throwable
{
System.out.println(someArg);
return proceedingJoinPoint.proceed();
}
}
My method:
#MyCustomAnnotation(someArg = "something")
#GetMapping("/whatever/route")
public SomeCustomResponse endpointAction(#RequestParam Long someId) {
SomeCustomResult result = someActionDoesNotMatter(someId);
return new SomeCustomResponse(result);
}
Mostly based on the docs (https://docs.spring.io/spring/docs/3.0.3.RELEASE/spring-framework-reference/html/aop.html - 7.2.4.6 Advice parameters) I am pretty sure, it should work.
I am here, because it does not...
What drives me crazy, is that even Intellij, when tries to help with argNames (empty string -> red underline -> alt+enter -> Correct argNames attribute) gives me this, and keeps it red...
Based on the docs, proceedingJoinPoint is not even needed (it does not work without it either): "If the first parameter is of the JoinPoint, ProceedingJoinPoint..."
With the current setup, it says "Unbound pointcut parameter 'someArg'"
At this point, I should also note, that without the args it is working fine.
I have two questions, actually:
Why does this does not work? (That was pretty obvious)
If I would like to give some extra functionality to some controllers, and I would like to parameterise it from the outside, is it the right pattern in spring boot? (With python, it was quite easy to do this with decorators - I am not quite sure, that I am not misguided by the similar syntax)
One example (the example above was pretty abtract):
I would like to create a #LogEndpointCall annotation, and the developer of a route can later just put it on the endpoint that he is developing
...however, it would be nice, if he could add a string (or more likely, an enum) as a parameter
#LogEndpointCall(EndpointCallLogEnum.NotVeryImportantCallWhoCares)
or
#LogEndpointCall(EndpointCallLogEnum.PrettySensitiveCallCheckItALot)
so that the same logic is triggered, but with a different param -> and a save to a different destination will be made.
You cannot directly bind an annotation property to an advice parameter. Just bind the annotation itself and access its parameter normally:
#Around("#annotation(myCustomAnnotation)")
public Object addMyLogic(
ProceedingJoinPoint thisJoinPoint,
MyCustomAnnotation myCustomAnnotation
)
throws Throwable
{
System.out.println(thisJoinPoint + " -> " + myCustomAnnotation.someArg());
return thisJoinPoint.proceed();
}
It will print the something like this with Spring AOP
execution(SomeCustomResponse de.scrum_master.app.Application.endpointAction(Long)) -> something
and something like this with AspectJ (because AJ also knows call joinpoints, not just execution)
call(SomeCustomResponse de.scrum_master.app.Application.endpointAction(Long)) -> something
execution(SomeCustomResponse de.scrum_master.app.Application.endpointAction(Long)) -> something
If you want your method to intercept method that take on consideration args you must explicit mention that in you pointcut expression , to make it work here is what you should do :
#Around(
value = "#annotation(MyCustomAnnotation) && args(someArg)",
argNames = "someArg")
notice that i add && args(someArg), you can add as much arguments as you want, in argNames you can omit proceedingJoinPoint.

Can you or can't you use a property from a config file when using Spring #Condition?

I have read pretty much everything I can find on StackOverflow and other sites and I don't see a definitive answer anywhere.
I have a class that implements #Condition that I use in a #Configuration file to conditionally load some beans. I am doing something like this:
public class MyCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metdata) {
String property = context.getEnvironment().getProperty("some.prop.from.file");
boolean enable = Boolean.parseBoolean(property);
return enable;
}
}
When debugging I see that getting the property from the environment always returns null, even though the property is injected in other beans using #Value.
So my question can you or can't you attempt to get a property value from a file within a #Condition class? Can you only get System properties? I would think that this is a common use case that I would think Spring could handle.
Had to add the property to application.properties and not the other property files that are loaded during startup.

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()
}

#Value annotation in child context

I think there are some problems when using #Value annotation and default values. I am using the following annotation on a variable. I expect to see the variable propAVar set with the property value of propA however, its always set to the default value -10
#Component
public class SomeClass {
#Value("${propA:-10}")
public String propAVar;
}
I am using PropertyPlaceholderConfigurer in the parent and child context. The child context has the context:component-scan element which scans the SomeClass. On digging into the Spring code i found the following method where the issue might lie.
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
//...
public String resolveEmbeddedValue(String value) {
String result = value;
for (StringValueResolver resolver : this.embeddedValueResolvers) {
if (result == null) {
return null;
}
result = resolver.resolveStringValue(result);
}
return result;
}
//...
}
Looks like resolveEmbeddedValue will go through all embeddedValueResolvers and ascertain value of that property. However, if we define a default it will look for that property in the first embeddedValueResolvers and return the default value if its not found there. Shouldn't it go through all resolvers and then return the default value? Would like to mention that this works fine without the default value.
Would be great if someone could help explain the expected behavior of #Value with default value in case of parent-child context's.
[cross-posted on spring forum]
I believe you are seeing this bug. There are some workarounds mentioned by other users in there. Also, I would suggest voting for fixing it, in case your situation is the same as the one described there.

Resources