Abstract classes and Spring MVC #ModelAttribute/#RequestParam - java

I have a hierarchy of model classes in my Spring/Hibernate application.
When submitting a POST form to a Spring MVC controller, is there any standard way of specifying the type of the object being submitted, so Spring can instantiate the correct subclass of the type declared in the receiving method's #ModelAttribute or #RequestParam?
For example:
public abstract class Product {...}
public class Album extends Product {...}
public class Single extends Product {...}
//Meanwhile, in the controller...
#RequestMapping("/submit.html")
public ModelAndView addProduct(#ModelAttribute("product") #Valid Product product, BindingResult bindingResult, Model model)
{
...//Do stuff, and get either an Album or Single
}
Jackson can deserialize JSON as a specific subtype using the #JsonTypeInfo annotation. I'm hoping Spring can do the same.

Jackson can deserialize JSON as a specific subtype using the
#JsonTypeInfo annotation. I'm hoping Spring can do the same.
Assuming you use Jackson for type conversion (Spring uses Jackson automatically if it finds it on the classpath and you have <mvc:annotation-driven/> in your XML), then it has nothing to do with Spring. Annotate the types, and Jackson will instantiate the correct classes. Nevertheless, you will have to do instanceof checks in your Spring MVC controller method.
Update after comments:
Have a look at 15.3.2.12 Customizing WebDataBinder initialization. You could use an #InitBinder method that registers an editor based on a request parameter:
#InitBinder
public void initBinder(WebDataBinder binder, HttpServletRequest request) {
String productType = request.getParam("type");
PropertyEditor productEditor;
if("album".equalsIgnoreCase(productType)) {
productEditor = new AlbumEditor();
} else if("album".equalsIgnoreCase(productType))
productEditor = new SingleEditor();
} else {
throw SomeNastyException();
}
binder.registerCustomEditor(Product.class, productEditor);
}

Related

#Valid bean validation

Consider the two frameworks shown below. Here I need to validate the bean
Controller
In controller I m using #Valid and does the java validation. Works fine
#RequestMapping("")
void testIt(#Valid #RequestBody User user){
}
Normal Spring application without controller
Is there any way to do validation here. Its not a controller and #Valid doesn't work here.
Anyways to use #Valid or any similar type of validation for normal function?
void testIt(#Valid User user){
}
You can enable method validation by declaring beans of type org.springframework.validation.beanvalidation.MethodValidationPostProcessor and org.springframework.validation.beanvalidation.LocalValidatorFactoryBean and annotating the class containing testIt() with the #Validated annotation.
#Validated
#Component
public class TestIt {
public void testIt(#Valid User user) {
...
}
}
ConstraintViolationException will be thrown if validation errors occur when calling testIt(). Also, make sure you have Hibernate Validator in your classpath.

Why is #Validated required for validating Spring controller request parameters?

With the following validation setup in an annotated MVC controller:
#RestController
#RequestMapping("/users")
#Validated // <-- without this, the #Size annotation in setPassword() has no effect
public class UserController {
#PutMapping("/{id}/password")
public void setPassword(#PathVariable long id, #RequestBody #Size(min = 8) String password) {
/* ... */
}
#PutMapping("/{id}/other")
public void setOther(#PathVariable long id, #RequestBody #Valid MyFormObject form) {
/* ... */
}
}
#Validated on the controller is required for the method parameter since it's not a "complex" object. In comparison, the #Valid annotation on the setOther method works without the #Validated annotation.
Why is #Validated required? Why not enable it by default? Is there a cost to its use?
edit
Note that Difference between #Valid and #Validated in Spring is related (I read it before asking this), but it doesn't address the why in my question.
Validation of objects is done by Hibernate Validator using the annotations from Jakarta Bean Validation 2.0. Something needs to trigger hibernate validator to run.
SpringMVC calls the controller methods when it sees a parameter with #Valid it will pass that object to hibernate validator. Hibernate validator will
Examine the class of the object to figure out what validation rules have been put on the class fields
Execute the validation rules against the fields marked up with validation annotations.
So in this case
#PutMapping("/{id}/other")
public void setOther(#PathVariable long id, #RequestBody #Valid MyFormObject form) {
/* ... */
}
MyFormObject has annotations on it that the hibernate validator can find to validate the object.
In this case
#PutMapping("/{id}/password")
public void setPassword(#PathVariable long id, #RequestBody #Size(min = 8) String password) {
/* ... */
}
java.lang.String does not have any annotations defined on it for hibernate validator to discover.
#Valid comes from the Bean validation package javax.validation.Valid while #Validated comes from Spring org.springframework.validation.annotation.Validated
#Validated annotation activates the Spring Validation AOP interceptor and it will examine method parameters to see if they have any validation annotations on them, if they do then Spring will call hibernate validator with each specific annotation for example #Size(min = 8) String password means call hibernate size validator and pass the value of the parameter password in this case hibernate validator does not need to scan java.lang.String to see if it has validation annotations on it. #Validated works on any spring #Component you can use it on #Service classes for example.
There is extra overhead for using #Validated similar to using #Transactional so that is why you have to opt into it. In the case of javax.validation.Valid Spring MVC needs to check the annotations on the controller method parameters so when it sees #Valid it is easy for it to send that object the Hibernate Validator without needing to add an AOP interceptor.

How to bind request params without setters?

I have a simple controller with a GET handler that accepts an object to bind request parameters:
#RestController
#RequestMapping("/test")
public class SampleController {
#GetMapping
public SomeResponse find(RequestParams params) {
// some code
}
}
The RequestParams is a simple POJO class:
public class RequestParams {
private String param1;
private String param2;
// constructor, getter, and setters
}
Everthing works fine, but I would like to get rid of the setters to make the object immutable to public use. In the documentation for #RequestMapping handler method up to Spring 5.0.2, we read that
possible valid method arguments are:
Command or form objects to bind request parameters to bean properties
(via setters) or directly to fields
Is it possible to somehow override the default Spring Boot configuration so that request parameters are bound to class properties using reflection and not with setters?
Update 2018
In the later versions of Spring's documentation, the quoted statement has been rephrased and no longer contain information about binding request parameters directly to fields.
In addition to JSON annotations suggested by #jihor you can try to use custom Web Data binder, adding following code to your controller or to Controller Advice class to span functionality across multiple controllers.
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.initDirectFieldAccess();
}
Spring Boot libraries depend on Jackson (spring-boot-starter-web<-spring-boot-starter-json<-jackson libraries), so one can use its annotations to control json bindings.
#JsonCreator-annotated constructors or static methods allow to instantiate objects without explicit setters:
#JsonCreator
public RequestParams(#JsonProperty("param1") String param1,
#JsonProperty("param2") String param2) {
this.param1 = param1;
this.param2 = param2;
}
Documentation
https://fasterxml.github.io/jackson-annotations/javadoc/2.8/com/fasterxml/jackson/annotation/JsonCreator.html
https://github.com/FasterXML/jackson-annotations/wiki

Instantiating bean using factory pattern in Spring MVC 3

I got Object coming in a REST web service controller's web method which is locally initialized.
#RequestMapping(method = RequestMethod.POST,value = "/test",headers="Accept=*/*")
public #ResponseBody ModelAndView computeDetails(#RequestBody RequestObj reqObj, ModelMap model) {
System.out.println(reqObj.getcode());
return new ModelAndView("responsedetails", "object", reqObj);
}
This RequestObj object holds the key code to instantiate dependency using factory.
Different codes classes have been defined which implement BaseCode Interface.
How can I use factory method to instantiate particular code class based on code value coming in as BaseCode type in my service bean?
Any idea? Thanks in advance.
What I usually do in such cases is:
inject the factory into the controller using Spring's bean
create a method getBaseCode(String code) in the factory (please note: String here stands for code type, so use the actual code type if not String
make getBaseCode returning the BaseCode interface while constructing the real implementation
supposing you have an execute method in BaseCode, use the getBaseCode method into the controller to get the real collaborator and then call the execute method to perform the actual action
Ignoring the first point (which I think you can easily looking at any Spring tutorial) the factory will be something like
public class BaseCodeFactory {
public BaseCode getBaseCode(String code) {
if(code.equals("something")) return new ThisBaseCodeImpl();
else //and so on
}
}
while computeDetails becomes similar to:
#RequestMapping(method = RequestMethod.POST,value = "/test",headers="Accept=*/*")
public #ResponseBody ModelAndView computeDetails(#RequestBody RequestObj reqObj, ModelMap model) {
//...
factory.getBaseCode(reqObj.getcode()).execute();
//...
}
As a side note, I will not go for names like the one I choose here, I suggest you to look for something more significative in your domain (BaseCode has no meaning for example), take this snippets just as a directive.
Base on OP comment. If you have ThisBaseCodeImpl which makes use of other Spring bean you can
annotate it with #Configurable so, when you use new ThisBaseCodeImpl(/*args if you like*/) its bean are instantiated by Spring. I don't personally like this solution since, in my opinion, it pollutes the code with hidden Spring's bean. On the other hand is quite flexible, since it allows you to manage both runtime constructor arguments and Spring beans
add ThisBaseCodeImpl to the Spring context and change the factory, so that a collaborator for ThisBaseCodeImpl is injected into it.
1st point example:
#Configurable
public class ThisBaseCodeImpl {
#Resource
private Bean bean;
}
2nd point example:
public class BaseCodeFactory {
#Resource
ThisBaseCodeImpl thisBaseCodeImpl;
public BaseCode getBaseCode(String code) {
if(code.equals("something")) return thisBaseCodeImpl;
else //and so on
}
}
I'm not sure if I understood your problem well, but in general spring dependencies have nothing to do here. Just write custom Factory class and return BaseCode implemetation depending on the reqObj.getcode().
I did it this way -
Make your factory as ServletContextAware in a way to get the currentContext. And define getInstance method as
WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
ctx.getBean(classNameToBeInstantiated);
Define your bean's inheritance in spring context so that Spring injects its dependencies.

RestEasy - Parameters Binding - Validation and Errors - EJB

Lets say I define a POJO with parameters that is passed to a REST call
class MyVO {
#NotNull
#PathParam("name")
private String name;
#NotNull
#PathParam("age")
private Integer age;
// getters and setters
}
public class RESTclass {
public postData( #Form MyVO vo ) {
}
}
It automatically binds the objects in MyVO. But where do I get the validation errors?
Does it trigger the validation during binding? If not, how to trigger the validations?
Spring does all these well. It has BindingResult parameter that you can inject.
What is the equivalent here?
Any idea?
RestEasy Versions Prior to 3.0.1.Final
For bean validation 1.0, Resteasy has a custom validation provider that uses hibernate's bean validator implementation under the covers.
In order to get validation up and running in Resteasy you need to do the following:
Add the resteasy-hibernatevalidator-provider dependency to your project. Here is the maven pom entry if you are using maven:
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-hibernatevalidator-provider</artifactId>
<version>${resteasy.version}</version>
</dependency>
Annotate your resource classes where you want validation to occur with the #ValidateRequest annotation.
#Named
#Path("/users")
#ValidateRequest
public class UserResource extends BaseResource
{
#POST
#Consumes({MediaType.APPLICATION_JSON})
#Produces({MediaType.APPLICATION_JSON})
public Response createUser(#Valid User user)
{
//Do Something Here
}
}
Resteasy will automatically detect the HibernateValidatorAdapter on the classpath and begin invoking bean validation.
Create an ExceptionMapper<MethodConstraintViolationException> implementation to handle the validation errors.
Unlike in Spring where you have to check the BindingResult, when validation errors are encountered in Resteasy the hibernate validator will throw a MethodConstraintViolationException. The MethodConstraintViolationException will contain all of the validation errors within it.
#Provider
public class MethodConstraintViolationExceptionMapper extends MyBaseExceptionMapper
implements ExceptionMapper<MethodConstraintViolationException>
{
#Override
public Response toResponse(MethodConstraintViolationException exception)
{
//Do Something with the errors here and create a response.
}
}
RestEasy Version 3.0.1.Final
The latest version of Resteasy is now supporting bean validation spec 1.1 and has changed the api and exceptions thrown.
Instead of the resteasy-hibernatevalidator-provider you are going
to need the resteasy-validator-provider-11 dependency.
You will not need to add #ValidateRequest to your resource classes
as validation testing is turned on by default with
resteasy-validator-provider-11.
Instead of throwing a MethodConstraintViolationException when
violations are detected, an instance of RESTEasyViolationException
will be thrown.
Documentation: 3.0.1.Final Validation Documentation

Categories

Resources