Invoke method using spring SPEL with property - java

Is it possible to use a property value to invoke a method while assigning a value?
For instance, I know I can do this:
#Value("${name}")
private String name; // will have the value of the `name` property
I wonder if it's possible to do something like this:
#Value("#{myMethod(${name})}")
private String myModifiedVariable; // will have the result of invoking myMethod

After my research and a bit of testing, I found there is a way shown in this article Spring EL method invocation, But mybean should be a string bean
#Value("#{mybean.myMethod('${name}')}")
private String myModifiedVariable;
And if you want to call a method in the existing class then use the spring bean name of the same class
#Configuration // or any sterotype annoations
public class TestConfig {
#Value("#{testConfig.myMethod('${name}')}")
private String myModifiedVariable;
public String getValue(String val){
return val+"testValue";
}
}

When using interface projection (in Spring Data Repository) it is possible to call a static method like this:
public interface MyProjection {
// Here we are creating a 2 element list with 'Spring Data' and value taken from "MY_COLUMN_NAME" column
#Value("#{T(java.util.Arrays).asList('Spring Data', target.MY_COLUMN_NAME)}")
List<String> getSampleList();
}
You can also obtain a value of an enum (constant) with above notation. Sample for Spring Security check:
#PreAuthorize("hasRole(T(com.acme.UserRoles).ADMINISTRATOR.name())")
Similar notation should work in other places. Simply remember to use full path to class

Related

Is it safe to use String as a return type of a bean in spring?

#Configuration
public class Product {
#Bean("xyz")
public String getMethod() {
return "abc";
}
}
#Component
public class Test {
String b;
Test(String xyz) {
this.b = xyz;
}
}
Is this any harm with this approach? I am trying to make change in the existing code where I am replacing the #Value with the getter as the method parameter. As I don't want to change the structure of the existing code I am trying to inject the method as bean as a replacement to #Value.
I suggest you to keep the #Value annotation instead of the whole #Bean configurations.
Why?
What if the getMethod()'s returned value needs to be changed very often? Everytime when you're changing something in the Product class, during build time it needs to be recompiled. What happens if the project is getting bigger and you're using this approach? It leads to longer build time and the more important thing is that this solution is not intuitive and it's hard to keep it clean. Don't think about complex solutions only to make the code look fancy. When you need to inject String values, the easiest approach is to create properties files (which won't get recompiled) and use the #Value annotation.
Now, if you want to add new methods without changing the structure of the existing code there are some patterns which you can apply like decorator pattern.
The main idea is simple: you're creating a decorator class which has an object of the type you need.
The easiest example (which you'll find everywhere on the internet) is the classic Shape example:
public interface Shape {
String someMethod();
}
#Component
public class CustomShape implements Shape { //implement the method here }
And here is the decorator:
public interface ShapeDecorator {
String someMethodExtended();
void someExtraMethod();
}
#Component
public class CustomShapeDecorator implements ShapeDecorator{
#Autowired
// #Qualifier - optional (only if you have more Shape implementations)
private Shape shape;
// now you can either:
// 1. provide new methods
#Override
public void someExtraMethod(){
System.out.println("Hello world!");
}
// 2. or you can EXTEND the Shape's "someMethod()" implementation
#Override
public String someMethodExtended(){
String oldString = this.shape.someMethod();
return oldString + " EXTENDED";
}
}

Correct way to get environment variables and go through SonarLint

I have a problem reading my environment variables and satisfying SonarLint(detect and fix quality issues) at the same time ..
That way it does not work my variable is null
private String accessKey;
#Value("${bws.access.key}")
public void setAccessKey(String ak){
accessKey=ak;
}
Changing the method to static (as the sonarLint recommends) does not work the variable continuous null
private static String accessKey;
#Value("${bws.access.key}")
public static void setAccessKey(String ak){
accessKey=ak;
}
The only way I found to work is to mark the instance variable as static but not to mark the method as static
private static String accessKey;
#Value("${bws.access.key}")
public void setAccessKey(String ak){
accessKey=ak;
}
But there sonarLint points out the issue
Instance methods should not write to "static" fields
Is not this way I'm getting my enviroment variables across the boundaries not the right one?
You can use the following code:
A Configuration Class (annotated with #Component in order to be picked up by Spring) which will hold the values coming from the properties file, where you bind the value of bws.access.key to a property directly. And if you need accessor methods for accessKey you can just create them (setAccessKey and getAccessKey)
#Component
public class ConfigClass {
// #Value("${bws.access.key:<no-value>}") // <- you can use it this way if you want a default value if the property is not found
#Value("${bws.access.key}") // <- Notice how the property is being bind here and not upon the method `setAccessKey`
private String accessKey;
// optional, in case you need to change the value of `accessKey` later
public void setAccessKey(String ak){
this.accessKey = ak;
}
public String getAccessKey() {
return this.accessKey;
}
}
For more details checkout this GitHub sample project.
I tested this with
IntelliJ IDEA 2018.1.5 (Ultimate Edition),Build #IU-181.5281.24
SonarLint
(Edit) How to use it in a Controller:
An option (there are others) could be to declare a constructor for the controller (let's call it SampleController) and request a parameter of type ConfigClass inside it. Now we set a controller attribute (config) of the same type to the value received as parameter, like this:
#RestController
public class SampleController {
private final ConfigClass config;
public SampleController(ConfigClass configClass) { // <- request the object of type ConfigClass
this.config = configClass; // <- set the value for later usage
}
#RequestMapping(value = "test")
public String test() {
return config.getAccessKey(); // <- use the object of type ConfigClass
}
}
Now, Spring Boot will try to find a component (of any type) in the app of type ConfigClass and since we have one defined it will automatically inject it in our controller. This way you can set the parameter controller property config to the value received in configClass for later usage.
In order to test it, you can request the url test. You will see that the output will be anotherValue. So we can conclude that the Dependency Injection Mechanism successfully found an instance of ConfigClass and the method ConfigClass#getAccessKey works properly.

Spring Boot: #Value returns always null

I would like to use a value from application.properties file in order to pass it in the method in another class. The problem is that the value returns always NULL. What could be the problem? Thanks in advance.
application.properties
filesystem.directory=temp
FileSystem.java
#Value("${filesystem.directory}")
private static String directory;
You can't use #Value on static variables. You'll have to either mark it as non static or have a look here at a way to inject values into static variables:
https://www.mkyong.com/spring/spring-inject-a-value-into-static-variables/
EDIT: Just in case the link breaks in the future. You can do this by making a non static setter for your static variable:
#Component
public class MyComponent {
private static String directory;
#Value("${filesystem.directory}")
public void setDirectory(String value) {
this.directory = value;
}
}
The class needs to be a Spring bean though or else it won't be instantiated and the setter will be not be accessible by Spring.
For the ones still facing the issue after all the preceding suggestions, make sure you are not accessing that variable before the bean has been constructed.
That is:
Instead of doing this:
#Component
public MyBean {
#Value("${properties.my-var}")
private String myVar;
private String anotherVar = foo(myVar); // <-- myVar here is still null!!!
}
do this:
#Component
public MyBean {
#Value("${properties.my-var}")
private String myVar;
private String anotherVar;
#PostConstruct
public void postConstruct(){
anotherVar = foo(myVar); // <-- using myVar after the bean construction
}
}
Hope this will help someone avoid wasting hours.
Few things for you to cross check apart from #Plog's answer.
static variables can't be injected with value. Check #Plog's answer.
Make sure the class is annotated with #Component or #Service
The component scan should scan the enclosing package for registering the beans. Check your XML if xml enabled configuration.
Check if the property file's path is correct or in classpath.
The other answers are probably correct for the OP.
However, I ran into the same symptoms (#Value-annotated fields being null) but with a different underlying issue:
import com.google.api.client.util.Value;
Ensure that you are importing the correct #Value annotation class! Especially with the convenience of IDEs nowadays, this is a VERY easy mistake to make (I am using IntelliJ, and if you auto-import too quickly without reading WHAT you are auto-importing, you might waste a few hours like I did).
Of course, the correct class to import is:
import org.springframework.beans.factory.annotation.Value;
Spring uses dependency injection to populate the specific value when it finds the #Value annotation. However, instead of handing the value to the instance variable, it's handed to the implicit setter instead. This setter then handles the population of our NAME_STATIC value.
#RestController
//or if you want to declare some specific use of the properties file then use
//#Configuration
//#PropertySource({"classpath:application-${youeEnvironment}.properties"})
public class PropertyController {
#Value("${name}")//not necessary
private String name;//not necessary
private static String NAME_STATIC;
#Value("${name}")
public void setNameStatic(String name){
PropertyController.NAME_STATIC = name;
}
}
You can make use of this. reference to assess the value of (private String myVar)
this.myVar
Add the #Autowired annotation to the variable declaration of your class.
#Autowired
private FileSystem myFileSystem;

Spring MVC bind on a typed field

I have a Spring MVC controller like this:
#RequestMapping(value = "/search", method = RequestMethod.GET)
#ResponseBody
public Object grid(Search<MyFilter> search){
...
}
My Search object is like:
public class Search<F extends Filter> {
private int offset;
private int size;
private F filter;
//... getters/ setters
}
Filter is just an Interface. MyFilter is an implementation of Filter with some fields like name, title, etc.
I'm doing an HTTP GET to this controller with: /search?offset=0&size=10&filter.name=john
But spring can't instantiate MyFilter. I've tried to make Filter a normal empty class and MyFilter to extend it, but it's not possible either.
Is it possible to spring to instantiate the correct filter and then bind the values?
[TL;DR] No, Spring won't instantiate MyFilter from parametrized type.
The Search object is instantiated, when request arrives.
The problem is, that at runtime information about type parametrization is erased and converted to the most general type. I mean that at runtime your Search object effectively will look like:
class Search {
/*
* F is replaced by Filter since it is the most general type
* for <F extends Filter> parametrization.
* This will be the only Search class representation that compiler generates.
*/
private Filter filter;
//Rest of class body omitted
}
This is how generics works. Sicne Filter is an interface the object mapper has no clue how it should be resolved. You could guide object mapper how to resolve Filter into specific class based on i.e. additional type parameter (I belive that such case is answered here). But I don't think if mixing this with generics is good idea. It will lead to complex and confusing code.
You obviously depend on MyFilter implementation so you could use the approach from #DeezCashews suggestion. Or you could think of more flexible solutions. If all kinds of Filter are just containers of fields names and of wanted values you could do something like:
public class Search {
private int offset;
private int size;
private List<FieldPredicateTuple> filters;
//getters and setters omitted
}
public class FieldPredicateTuple {
String field;
String value;
//getters and setters omitted
}
And then call endpoint: /search?offset=0&size=10&filters[0].field=name&filters[0].value=john
I don't know how you want use those filters so I don't know what to propose. But I'd recommend to redesign this somehow.
I know this may not be exactly what you are looking for but if you are stuck perhaps consider extracting out the shared variables into its own class and then pass two parameters to your grid method instead of one. Spring Data has a Pageable class that is similar so perhaps you can look at that for inspiration. Then you can just pass a bean with the criteria and another bean with offset and size. Ex:
public Object grid(MyFilter f, Paged p);
public class MyFilter {
String name;
...
}
public class Paged {
int offset;
int size;
...
}

Spring autowiring bean with mixed constructor

I have two beans like this:
#Component
#Scope("prototype")
class A {
A(int number, B anotherBean) {
//...
}
}
#Component
class B {
//..
}
How can I build A and have B autowired? If I use new I won't get anotherBean's value, if I use autowiring I won't get number's value.
Edit: The number is calculated at runtime, so I can't use #Value like answers suggested.
I found this question today looking for an answer myself. After some consideration, here's a solution I think I'm going with. Basically I'm baking the factory method into the bean's class. This has the advantage of keeping the 'internal' dependencies (B in this example) all inside the 'A' class, and hidden from the caller, while still allowing prototype creation using a caller's runtime value. And it doesn't require yet another class file just for a factory method.
public class A {
private A (int number, B otherBean) {
...
}
#Configuration
public static class BeanConfig {
#Autowired
private B otherBean;
#Bean
#Scope("prototype")
public A makeA(int number) {
return new A(number, otherBean);
}
}
}
Then you can request a prototype bean instance while providing only the runtime value:
applicationContext.getBean(A.class, 1);
Make use of #Valueand inject the number's value via property. Your ClassA should look something like
#Component
#Scope("prototype")
class A {
#Autowired
A(#Value("${some.property}")int number, B anotherBean) {
//...
}
}
Edit (post additional condition of runtime value for number)
You can fetch the bean from BeanFactory.getBeans method as correctly pointed out by M.Deinum in comments.
You need to annotate the desired constructor-to-be-used as #Autowired, among other ways you can find with a Google search. As for your issue with 'number', there are ways to use annotations to set parameter values as well, also not difficult to find with a Google search.

Categories

Resources