How to get spring #Value annotated values injected even before calling the constructor? - spring

In spring wiring is there a way to get the properties defined in application.properties to be wired to the corresponding field annotated with #Value before the constructor call.
Please see my comment in below code. I want the field prop1 (with its value) to be available to the constructor. Is it possible. Similarly I want the field prop1 (with its value) to be available to the getInstanceProp() method. Is it possible??
package com.demo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
#Component
public class WirableComponent {
#Value("${prop1}")
String prop1;
String instanceProp = getInstanceProp(); //null - I want prop1 value to be injected before making this method call so i can use it in the method.
public WirableComponent() {
System.err.println("no-args WirableComponent Constructor invoked");
System.err.println("value prop1: " + prop1); //null - I want prop1 value to be injected before constructor call so i can use it in constructor.
System.err.println("value instanceProp: " + instanceProp); //null
}
public String getInstanceProp() {
System.err.println("value prop1: " + prop1);
return null;
}
}

#value works like dependency injection rules of spring, when the application context loads and the bean for the WirableComponent class is created the prop1 value is can be instantiated through constructor injection(this can be used for the method call)
eg
#Component
public class WirableComponent {
private final String prop1;
public WirableComponent(#Value("${prop1}") String prop1){
this.prop1 = prop1;
}
}
Secondly although prop1 is a string literal, it takes value from spring context ,hence it cannot be available in the class until the constructor call happens as in Java:
At the byte code level.
An object is created but not initialised.
The constructor is called, passing the object as this
The object is fully constructed/created when the constructor returns.

Related

Spring boot configuration properties validation not working

Trying to implement configuration properties validation for immutable beans as described in Spring boot docs:
#Validated
#ConstructorBinding
I'm using Spring boot 2.4.0.
Sample immutable properties class:
#Validated
#ConstructorBinding
#ConfigurationProperties("prefix")
public class Props {
private final String suffix;
public Props(#NotBlank String suffix) {
this.suffix = suffix;
}
public String getSuffix() {
return suffix;
}
#Override
public String toString() {
return "Props [suffix=" + suffix + "]";
}
}
For a test, I did this:
System.setProperty("prefix.suffix", "value");
and I get the correct binding, i.e.:
Props [suffix=value]
However, by making a typo in the property name (added 1 to the property name):
System.setProperty("prefix.suffix1", "value");
I get this:
Props [suffix=null]
I see validation activated in the logs:
HV000001: Hibernate Validator 6.1.6.Final
Form their docs, it is supposed to do constructor parameter validation:
As of Bean Validation 1.1, constraints can not only be applied to JavaBeans and their properties, but also to the parameters and return values of the methods and constructors of any Java type.
Why is #NotBlank (full import: import javax.validation.constraints.NotBlank;) not causing validation exceptions?

Custom serialization of single #RestController endpoint

Is there a way (preferably some type of annotation) to register a custom serializer for a single endpoint in a #RestController? Extending the bean and putting a #JsonSerialize on it would be an option, but that demands an otherwise pretty useless new bean class. I tried the following:
#JsonSerialize(using = CustomSerializer.class)
#RequestMapping(value = "/some_endpoint/", method = RequestMethod.GET)
public SomeType someEndpoint() {
return someObject;
}
But the #JsonSerialize annotation doesn't appear to have any meaning to Spring in that context. Is there an alternative or is the extra bean class my only option?
You can use #JsonView(View.Summary::class) in the attributes you want to add or ignore and in the method you want to apply that view, for example:
public class View {
public interface Summary
}
public class A{
#JsonView(View.Summary.class)
private String serialized = "",
private String notSerialized = ""}
and then in the controller:
#JsonView(View.Summary.class)
#GetMapping("/")
#ResponseBody
public A getA(){
return A()
}
If you want to reverse the JsonView (serialize the atributtes who doesnt have the view). you can add the following propertie: spring.jackson.mapper.default-view-inclusion=true

How to inject values to a wicket page using spring?

Is it possible to inject a value to a wicket page using spring?
With #Value it's possible to inject values to a spring bean.
I know the #SpringBean annotation, but this is only for beans.
My workaround is to wrap the value with a spring bean and then inject this with #SpringBean to my wicket page. Is there a better way to do this?
We have solved this problem using getter & setter in our custom child of WebApplication. This child is standard Spring bean and is configured in spring's configuration.
Otherwise you have to create some "config" bean.
You can write a Wicket resource loader to load spring values, and then those values will be resolved like regular wicket messages. If instead you need it within the body of the wicket class to do some business logic, that may be an opportunity to refactor that logic outside of the view layer.
Here's what the resource loader looks like:
public class SpringPropertiesResourceLoader
implements IStringResourceLoader
{
public SpringPropertiesResourceLoader()
{
}
#Override
public String loadStringResource(Class<?> clazz, String key, Locale locale, String style, String variation)
{
return loadStringResource(key);
}
#Override
public String loadStringResource(Component component, String key, Locale locale, String style, String variation)
{
return loadStringResource(key);
}
private String loadStringResource(String key)
{
try
{
ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(WebPortalApplication.get().getServletContext());
ConfigurableBeanFactory beanFactory = (ConfigurableBeanFactory)applicationContext.getAutowireCapableBeanFactory();
String rv = beanFactory.resolveEmbeddedValue("${" + key + "}");
return rv;
}
catch (IllegalArgumentException iae)
{
// no property with the name - move along
return null;
}
}
}
Then add that to your application in init():
getResourceSettings().getStringResourceLoaders().add(new SpringPropertiesResourceLoader());

Spring MVC : Common param in all requests

I have many controllers in my Spring MVC web application and there is a param mandatoryParam let's say which has to be present in all the requests to the web application.
Now I want to make that param-value available to all the methods in my web-layer and service-layer. How can I handle this scenario effectively?
Currently I am handling it in this way:
... controllerMethod(#RequestParam String mandatoryParam, ...)
and, then passing this param to service layer by calling it's method
#ControllerAdvice("net.myproject.mypackage")
public class MyControllerAdvice {
#ModelAttribute
public void myMethod(#RequestParam String mandatoryParam) {
// Use your mandatoryParam
}
}
myMethod() will be called for every request to any controller in the net.myproject.mypackage package. (Before Spring 4.0, you could not define a package. #ControllerAdvice applied to all controllers).
See the Spring Reference for more details on #ModelAttribute methods.
Thanks Alexey for leading the way.
His solution is:
Add a #ControllerAdvice triggering for all controllers, or selected ones
This #ControllerAdvice has a #PathVariable (for a "/path/{variable}" URL) or a #RequestParam (for a "?variable=..." in URL) to get the ID from the request (worth mentioning both annotations to avoid blind-"copy/past bug", true story ;-) )
This #ControllerAdvice then populates a model attribute with the data fetched from database (for instance)
The controllers with uses #ModelAttribute as method parameters to retrieve the data from the current request's model
I'd like to add a warning and a more complete example:
Warning: see JavaDoc for ModelAttribute.name() if no name is provided to the #ModelAttribute annotation (better to not clutter the code):
The default model attribute name is inferred from the declared
attribute type (i.e. the method parameter type or method return type),
based on the non-qualified class name:
e.g. "orderAddress" for class "mypackage.OrderAddress",
or "orderAddressList" for "List<mypackage.OrderAddress>".
The complete example:
#ControllerAdvice
public class ParentInjector {
#ModelAttribute
public void injectParent(#PathVariable long parentId, Model model) {
model.addAttribute("parentDTO", new ParentDTO(parentId, "A faked parent"));
}
}
#RestController
#RequestMapping("/api/parents/{parentId:[0-9]+}/childs")
public class ChildResource {
#GetMapping("/{childId:[0-9]+}")
public ChildDTO getOne(#ModelAttribute ParentDTO parent, long childId) {
return new ChildDTO(parent, childId, "A faked child");
}
}
To continue about the warning, requests are declaring the parameter "#ModelAttribute ParentDTO parent": the name of the model attribute is not the variable name ("parent"), nor the original "parentId", but the classname with first letter lowerified: "parentDTO", so we have to be careful to use model.addAttribute("parentDTO"...)
Edit: a simpler, less-error-prone, and more complete example:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#RestController
public #interface ProjectDependantRestController {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
*
* #return the suggested component name, if any
*/
String value() default "";
}
#ControllerAdvice(annotations = ParentDependantRestController.class)
public class ParentInjector {
#ModelAttribute
public ParentDTO injectParent(#PathVariable long parentId) {
return new ParentDTO(parentId, "A faked parent");
}
}
#ParentDependantRestController
#RequestMapping("/api/parents/{parentId:[0-9]+}/childs")
public class ChildResource {
#GetMapping("/{childId:[0-9]+}")
public ChildDTO getOne(#ModelAttribute ParentDTO parent, long childId) {
return new ChildDTO(parent, childId, "A faked child");
}
}

Injecting properties on constructor

I have to consume a REST api which follows a common syntax across all retrievable objects:
baseUrl + domainObjectName + qualifier
E.g.
"http://myweb.com/api/" + "cities" + "/{id}"
I created a BaseDao for my data layer and I would like to set up in DAO instantiation the base url for each domain object (baseUrl + domainObjectName). The problem is I have my api Base url defined in the properties file (and would like to keep it that way), and it is not available in the DAO constructor.
This is what I have:
public abstract class BaseDao {
protected static final String ID_QUALIFIER = "/{id}";
protected String domainObjectName = "";
protected String doBaseUrl = "";
#Value("#{config['baseUrlRest']}")
public String apiBaseUrl;
public GenericDaoRestImpl(String domainObjectName) {
this.domainObjectName = domainObjectName;
this.doBaseUrl = apiBaseUrl + domainObjectName;
}
}
When my dao is instantiated, apiBaseUrl is still null, although after creation it is indeed injecting the baseUrl property.
Is there any way around this, like injecting the property as a static constant?
This happens because Java doesn't allow to set fields of a class before the constructor is called. So Spring can't inject the value. There are two solutions:
Pass the value to the constructor instead (example 1)
Use #PostConstruct (example 2)
Example 1:
public GenericDaoRestImpl(
#Value("#{config['baseUrlRest']}") String apiBaseUrl
String domainObjectName
) {
...
}
Example 2:
#Value("#{config['baseUrlRest']}")
public String apiBaseUrl;
public GenericDaoRestImpl(String domainObjectName) {
this.domainObjectName = domainObjectName;
}
#PostConstruct
public void init() {
this.domainObjectName = domainObjectName;
this.doBaseUrl = apiBaseUrl + domainObjectName;
}
I prefer the #PostConstruct because constructor injection eventually leads to constructors with many parameters which makes them unwieldy.
If you don't like it, your third option is using the builder pattern with a fluent interface.

Resources