Does #Value support letting default value reference another #Value? - spring

#Value("${test.v1:hello}")
private String testV1;
#Value("${test.v2:${test.v1}}")
private String testV2;
When test.v2 is not configured, i want the testV2 is the same with testV1. Does #Value support letting default value reference another #Value?

I think the best thing you can do is to define test.v2=${test.v1} in your application.properties file. This way, if you override the value for test.v2 through some other ways like environment variables, it will have the defined value, otherwise, it will have the test.v1 value.

As an alternative, you can use the "+" operand.
#Value("${test.v1:hello}")
private String testV1;
#Value("${test.v2:}" + "${test.v1}")
private String testV2;

#PostConstruct
public void postConstruct() {
if (StringUtils.isEmpty(testV2) {
testV2 = testV1;
}
}

Related

Is it possible to use static final variables for Spring Expression Language?

I'm using CaffeineCache in my SpringBoot application, and here is one of my annotations for a method:
#Cacheable(value = PROMOCODE_BY_CONFIG_BUNDLE_CODE, key = "{#configBundleCode, #siebelId ?: 'all'}")
public Long countPromocodesByConfigBundleCodeAndSiebelId(String configBundleCode, String siebelId) {
return preferentialMapper.countPromocodesByConfigBundleCodeAndSiebelId(configBundleCode, siebelId, NULL_SIEBEL_PROMOCODE_CACHE_KEY);
}
I have this variable:
private static final String NULL_SIEBEL_PROMOCODE_CACHE_KEY = "all";
and I want to use it in my SpEL query instead of 'all'. I've tried to use it with # or $ symbols, but that doesn't work.
Is it possible to use the variable in the query, and how?
That is called a type operator: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#expressions-types
So, you can do like this:
T(com.my.proj.ClassWithConstant).NULL_SIEBEL_PROMOCODE_CACHE_KEY
But your NULL_SIEBEL_PROMOCODE_CACHE_KEY has to be public.

Spring inject environment variable value into a custom class (not String class)

Does anyone know how to inject environment variable to a custom class?
For example, I know #Value can be used to inject to a String class from environment variable or properties file
suppose I want to do the following:
// I want to inject region as US in environment variable
#value("${some_property}")
private static Region region;
Region class looks like:
public class Region {
public static final Region US = new Region("US");
private final String key;
public Region(String key) { this.key = key }
...
}
I tried this way, but at runtime it gives me null value
Most of the time, your environment variable is not an object but just a simple variable like this :
public class Region {
#value("${region.local}")
private String local;
}

Spring expression language: Using a variable as key for a map lookup in SpEL

I'm trying to use variable as key to look up a value in a map. I would like to be able to reference the variable directly (in this case jobTitle), but for some reason I need to prefix the variable with either #root. or #this. in order to get it to work. So this works:
parser.parseExpression("{ \"Manager\":\"37.5\", \"CEO\":\"40\"}[#root.jobTitle]"
(resolves to "37.5")
but this doesn't
parser.parseExpression("{ \"Manager\":\"37.5\", \"CEO\":\"40\"}[jobTitle]"
(resolves to null)
jobTitle is a root attribute on the context object. From looking at the SpEL docs it seems like I should be able to reference the attribute directly? Am I doing something wrong?
Full working code below
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
Employee employee = new Employee("Joe Bloggs", "Manager");
Expression exp = parser.parseExpression(
"{ \"Manager\":\"37.5\", \"Ceo\":\"40\"}[#root.jobTitle]");
StandardEvaluationContext context = new StandardEvaluationContext(employee);
context.setRootObject(employee);
System.out.println(exp.getValue(context, String.class));
}
static class Employee {
private String firstName;
private String jobTitle;
Employee(String firstName, String jobTitle) {
this.firstName = firstName;
this.jobTitle = jobTitle;
}
public String getJobTitle() {
return jobTitle;
}
}
From looking at the SpEL docs it seems like I should be able to reference the attribute directly?
This is correct for below case, both will print "Manager"
System.out.println(parser.parseExpression("#root.jobTitle").getValue(context, String.class));
System.out.println(parser.parseExpression("jobTitle").getValue(context, String.class));
But for expression inside [], the handling is different. Since there is no language specification for SpEL, I can just explain base on the behaviour.
When you run
System.out.println(parser.parseExpression(
"{\"jobTitle\":\"37.5\"}[jobTitle]").getValue(context, String.class));
This will actually print "37.5"([jobTitle] is treated as [\"jobTitle\"]). The author decided to make it easier to access value of Map, and the drawback is you need to specify with #root.jobTitle when referencing the root object.

How to make final variable when #Value annotation is used

I have read similar post in the forum but actually could not find an answer. I am reading a property from an application.yaml file but I want to make that variable final. But compiler does not allow me to do it and says the variable might not be initialized. What I want to do is to use final as below. So this variable is defined in a controller class.
#Value("${host.url}")
private final String url;
So how can I do it final ?
The only way to achieve it is using constructor injection.
#Component
public class MyBean {
private final String url;
public MyBean(#Value("${host.url}") String url) {
this.url = url;
}
}

How can I use a local variable in the annotation #Preauthorize?

i need to do something like this
String myVar = "myString";
...
#Preauthorize("customMethod(myVar)")
public void myMethod() {
...
}
but I'm failing at it. How can I do that? It says it cannot be resolved
EDIT:I'm decoupling few rest services and sometimes I have to share infos between them
#Value("${my-properties}")
String urlIWantToShare;
...
#PreAuthorize("isValid(#myValue,urlIWantToShare)")
#RequestMapping(value = "**/letsCheckSecurityConfig", method = RequestMethod.GET)
public boolean letsCheckSecurityConfig(#RequestHeader(name = "MY-VALUE") String myValue)) {
return true;
}
this "isValid" custom security method will call an external service, that doesn't know anything about the caller and his infos. I need to transmit few infos and I need to take them from different kind of sources
One of the sources is my application.properties
EDIT2: I managed to do this
#PreAuthorize("isValid(#myValue, #myProperty)")
#RequestMapping(value = "**/letsCheckSecurityConfig", method = RequestMethod.GET)
public boolean letsCheckSecurityConfig(#RequestHeader(name = "MY-VALUE") String myValue,
#Value("${my-property-from-app-properties}") String myProperty))
..but I want to use not only actual static properties but runtime one. Any help?
You can create a wrapper method without parameters which will call the desired method with parameters. In the annotation you can use the method without parameters
Apologies if I have misunderstood what you are trying to do, but from my understanding you're trying to set an annotation at runtime based on a variable / app.properties, so that you can then read this variable and then execute your class?
If this is the case, You cannot do this from an annotation alone as annotations cannot read local variables and cannot be set at runtime.
However, one option for you is to have an object which contains the 'values' of interest for you and then read the values from the object.
Something like the below:
PoJo
public class testObject{
#test
private String myVar;
private String myValue;
//Getters and Setters
}
Get Object values
public void getFields (Object obj){
Field fields = obj.getClass().getDeclaredFields();
for (Field f : fields){
test fieldAnnotation = f.getAnnotation(test.Class);
if (fieldAnnotation != null){
f.get(obj);
// Do checks based on this
}
}
}
Main Class
public static void main(String[] args){
//Create object
testObject test = new testObject();
test.setOne("testOne");
test.setTwo("testTwo");
getFields(test);
}
I've pulled this code based on what I had to do to get the fields - but in my case, I did not know the object types I was going to be passed. You are simply using the annotation to 'mark' the fields you want to retrieve and then reading the value from the object.
If you're in a similar situation, then you can see my answer here: initial answer
Let me know if i've misunderstood this and I can try and further clarify my answer.

Resources