How Spring HttpMessageConverter parse request body to the model that I define - spring

I am a bit curious how spring handle this, and I did some experiment.
Here is my request handling method:
#PostMapping(value = "/testRequestBody")
public String testRequestBody(#RequestBody MyRequestBody requestBody) {
System.out.println(requestBody);
return "Success";
}
I have tried three types of MyRequestBody.
Tyep 1:
public class MyRequestBody {
private int id;
private String name;
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return "MyRequestBody(id="+id+",name="+name+")";
}}
Type 2:
public class MyRequestBody {
private int id;
private String name;
public MyRequestBody(int id, String name) {
this.id = id;
this.name = name;
}
public String toString() {
return "MyRequestBody(id="+id+",name="+name+")";
}}
Type 3:
public class MyRequestBody {
private int id;
private String name;
public String toString() {
return "MyRequestBody(id="+id+",name="+name+")";
}}
Both Type 1 and Type 2 I can get MyRequestBody(id=1,name=test name) in the console, for input {"id": 1,"name": "test name"}, and Type 3 gives me MyRequestBody(id=0,name=null). It seems Spring is able to choose different way to parse my model based on how setters and constructors are defined for my model. I would like to know how does Spring achieve that?

MappingJackson2HttpMessageConverter delegates to Jackson, the work of converting json to a java object. So, the behaviour that you are seeing can be explained by the rules jackson follows.
Following is a simplified sequence of the operations that Jackson performs, which explains the behavior that you are seeing with the three MyRequestBody definitions.
An object of the class is created by invoking the constructor. If the constructor accepts parameters, the values for the parameters are taken from the json string. Following are the rules to decide which constructor should be invoked.
If there is a constructor annotated with #JsonCreator, that constructor is invoked.
If there is no constructor annotated with #JsonCreator
If there is exactly one constructor, that constructor is invoked. This could be either a parameterized constructor or a default constructor.
If there are multiple constructors, including a default constructor, the default constructor is invoked.
In other cases, an exception is thrown.
Field values are set by calling setX() methods.
Java reflection is used for examining the constructor/methods and invoking them.
So, in your case
For the first RequestBody definition, default constructor is invoked and setX() methods are called.
For the second RequestBody definition, parameterized constructor is invoked (there is no default constructor)
The default constructor is invoked.

Related

Use converter to get form data parameters into a map inside a dto

I have a controller method in spring boot:
#PostMapping(produces = "text/html")
public String create(#Valid myDTO myDTO, BindingResult bindingResult, Model uiModel, HttpServletRequest httpServletRequest) {
// Omissions
return "redirect:/blue/tar/";
}
I have a dto:
public class MyDTO {
private Long id;
private List<FooBar> objects;
public MyDTO(Long id, List<FooBar> objects) {
this.id = id;
this.objects = objects;
}
public Long getId() {
return id;
}
public List<FooBar> getObjects() {
return objects;
}
}
And I have a converter, which i have added to the converterRegistry in spring:
private Converter<String, FooBar> getStringToLegalEntityConverter() {
return new org.springframework.core.convert.converter.Converter<>() {
public FooBar convert(#NotNull String id) {
return //* convert id to FooBar structure*//
}
};
}
My web form send form data. E.g:
fooBar: 1
fooBar: 3
fooBar: 4
And the above code handles this perfectly. The converter is called three times with 1, 3 and 4. The result a bit magically appears as a list with three FooBar objects in the dto in the controller.
The above works.
I attempted to use a map in MyDTO instead. Spring complains that there is no converter from String -> Map.
If i add one (which feels wrong, since there was no converter from String -> List before), it only enters the converter once with the last value. (4, in the example above).
So is there a solution here to get spring to allow me to manually convert a series of form params to a map in such a way that they end up in a DTO like that?
Below is my attempt which fails. Since spring only attempts to convert one value to fit in the map in the DTO. I would have needed the converter to be called with all the form parameters at once:
private Converter<String, Map<Long, FooBar>> attempt() {
return new org.springframework.core.convert.converter.Converter<>() {
public Map<Long, FooBar> convert(#NotNull String aString) {
/* Say if aString is the formparameters as "1,3,4" then i'd turn those into keys and then fetch their values from a db */
return /* The map */
}
};
}
Similar but I don't think the answers there are applicable: How to get Form data as a Map in Spring MVC controller?

How #Autowired Work using Reflection and how invoke appropriate Setter Method

My Main Problem is that i can use Reflection API and i can invoke the setter method in my User bean class.But I have Only have UserBean objects. I does not know which setter method to invoke.i want extract all information in my User bean Object and the invoke appropriate Method using Reflection.
//-->This My User Bean Class
public class User {
private String name;
private int age;
private String getName() {
return name;
}
private void setName(String name) {
this.name = name;
}
private int getAge() {
return age;
}
private void setAge(int age) {
this.age = age;
}
void callMethod() { System.out.println("Name :- "+name+" Age :-"+age); }
#Override
public String toString() { return "User [name=" + name + ", age=" + age + "]";}
}
//--->This My Main Method
public class TestCase {
public static Object getBean(String beanClassName) throws Exception
{
Class klass = Class.forName(beanClassName);
Object obj = klass.newInstance();
Method[] b = klass.getDeclaredMethods();
String MethodName1 = "setName";
String MethodName2 = "setAge";
String name ="sanket";
int age = 21;
for(int i=0;i<b.length;i++)
{
if(b[i].getParameterTypes().toString().equals(MethodName1))
{
b[i].setAccessible(true);
b[i].invoke(obj,name);
}
if(b[i].getName().equals(MethodName2))
{
b[i].setAccessible(true);
b[i].invoke(obj,age);
}
}
return obj;
}
public static void main(String ars[]) throws Exception
{
Object obj1 = getBean("www.Fouth.User");
System.out.println(obj1.toString());
}
}
I my case I can invoke the setter method manually because i can invoke the setter method bases of method name.
I want Extract information in User Bean Class, and identify on the bases of value which setter to invoked.
In spring #AutoWired is does that.How they will identify which setter to invoke and inject dependency.
The Spring identifies which setters to inject dependencies by figuring out which setters are annotated with #Autowired , #Inject , #Resources etc.
Then it figures out which value to inject into the setter by checking the type of the setter arguments and injects the bean that has the same type.
If multiple beans have the same type, it then check the bean name. It will inject the bean that has the same name as configured in the injection point using #Qaulifer or #Resources.
If there are still multiple beans that satisfy to inject , NoUniqueBeanDefinitionException happens.
If there are no beans that satisfy to inject, NoSuchBeanDefinitionException happens.

How to use Java 8 Optional with Moxy and Jersey

Is it possible to use Jersey with Moxy to/from Json and Java 8 Optionals?
How to configure it?
You can declare following class:
public class OptionalAdapter<T> extends XmlAdapter<T, Optional<T>> {
#Override
public Optional<T> unmarshal(T value) throws Exception {
return Optional.ofNullable(value);
}
#Override
public T marshal(Optional<T> value) throws Exception {
return value.orElse(null);
}
}
And use like this:
#XmlRootElement
public class SampleRequest {
#XmlElement(type = Integer.class)
#XmlJavaTypeAdapter(value = OptionalAdapter.class)
private Optional<Integer> id;
#XmlElement(type = String.class)
#XmlJavaTypeAdapter(value = OptionalAdapter.class)
private Optional<String> text;
/* ... */
}
Or declare in package-info.java and remove #XmlJavaTypeAdapter from POJOs:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlJavaTypeAdapters({
#XmlJavaTypeAdapter(type = Optional.class, value = OptionalAdapter.class)
})
But here are some drawbacks:
Adapter above can only work with simple types like Integer, String, etc. that can be parsed by MOXY by default.
You have to specify #XmlElement(type = Integer.class) explicitly to tell the parser type are working with, otherwise null values would be passed to adapter's unmarshal method.
You miss the opportunity of using adapters for custom types, e.g. custom adapter for java.util.Date class based on some date format string. To overcome this you'll need to create adapter something like class OptionalDateAdapter<String> extends XmlAdapter<String, Optional<Date>>.
Also using Optional on field is not recommended, see this discussion for details.
Taking into account all the above, I would suggest just using Optional as return type for your POJOs:
#XmlRootElement
public class SampleRequest {
#XmlElement
private Integer id;
public Optional<Integer> getId() {
return Optional.ofNullable(id);
}
public void setId(Integer id) {
this.id = id;
}
}

Spring controller method argument post init callback

I have a controller method that takes a bean as an argument. This controller method accepts POST requests and the corresponding post body is populated inside the argument bean. I want to call the postInit method inside PasswordChange Object after this bean is initialized and the json has been deserialized but before the service call inside the controller method is made.I know there is the Spring's #PostConstruct but because I am using Jackson for json deserialization via the MappingJacksonJsonView I wasn't sure certain if this method would be reliably called. Can anybody tell me if this is infact a valid use of #PostConstruct.
I am using Spring 3.2.8 with Jackson-databind 2.3.2.
#RequestMapping(value = "/passwordChange", method = RequestMethod.POST)
public #ResponseBody PasswordInfo passwordInfo(#RequestBody #Valid PasswordChange passwordChange)
throws PasswordChangeException {
return passwordService.changePassword(passwordChange.getLoginKey(), passwordChange.getOldPassword(), passwordChange.getNewPassword());
}
PasswordChange Bean.
public class PasswordChange {
private String loginKey;
private String oldPassword;
private String newPassword;
#Autowired
private LoginDao loginDao;
private LoginEntity login;
private Person person;
public PasswordChange() {
}
public PasswordChange(String loginKey, String oldPassword, String newPassword) {
this.loginKey = loginKey;
this.oldPassword = oldPassword;
this.newPassword = newPassword;
}
#PostConstruct
public void postInit() {
login = loginDao.findByLogin(loginKey);
person = login.getCorePerson();
}
}

#PathVariable not binding with #RequestBody

When I don't use #RequestBody the #PathVariable id is automatically set at my Entity class. But if I use #RequestBody it's not. I need that the id of Entity is set before my GenericValidator executes validation. Why does it work without #RequestBody and not with it?
The Entity class:
public class Entity {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
//...
}
The controller class:
#Controller
#RequestMapping(value = "/entity")
public class EntityController {
#Autowired
private GenericValidator validator;
#InitBinder
private void initBinder(WebDataBinder binder) {
binder.addValidators(validator);
}
#RequestMapping(value = "/{id}", method = RequestMethod.PUT)
public #ResponseBody Response update(
#PathVariable String id,
#Valid #RequestBody Entity entity)
{
//...
}
}
When used alone, #Valid works much like #ModelAttribute. The Entity method argument would be retrieved from the Model or instantiated, the WebDataBinder would handle the data binding process (this is when the id would be set), and then validation would occur.
#RequestBody arguments do not go through the data binding process like #ModelAttribute arguments. They're created via an HttpMessageConverter using the body of the request instead of matching the names of request parameters and path variables to the names of your object's fields. When combined with #Valid, the configured validator is run against the new object but #ModelAttribute style data binding still does not occur.

Resources