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.
Related
I want to test my controller using postman but don't know how to send a model attribute using postman.
I tried to send all attributes in row json fornamt and x-www-form-urlencoded in body but it is not working for me, I didn't understand where i'm getting wrong
My controller class looks like :
#RestController
public class DemoController {
#Autowired
private DemoService demoService;
#RequestMapping(value = "/userDetail", method = { RequestMethod.GET }, produces = { MediaType.APPLICATION_JSON })
public String testme(
ModelMap model,
#ModelAttribute("inputParameter") InputParameter inputParameter,
BindingResult result) {
return demoService.getDetail(inputParameter);
}
}
Model Class :
public class InputParameter {
private String id;
private String name;
private String number;
private String address;
private String pass;
}
Suppose I have a model UserInfo which I used in my post service as #RequestBody and when I invoke the service with UserInfo payload it is working.
Class UserInfo {
Private String firstName;
Private String lastName
}
How do I restrict the post call if someone sends some additional fields in the payload which is not exists in UserInfo model (e.g. age)?
In this case, are you using this?
#Autowired
ServiceInterface serviceInterface;
#PostMapping(value = "/userSave")
#RequestBody
public RequestEntity saveUserInfo(**#ModelAttribute** UserInfo userInfo){
return new ResponseEntity(serviceInterface.saveUser(userInfo),HttpStatus.OK);
}
when we are using #ModelAttribute annotation this gets only attribute values in UeserInfo DTO (DATA TRANSFER OBJECT). Also, you can use validations in the UserInfo DTO Class. Like
Class UserInfo {
#NotNull
private String firstName;
#NotNull
private String lastName;
}
But However when using #RequestBody whatever user sends additional data that save only UserInfo Attribute data.
Try to add a property to application.properties:
spring.jackson.deserialization.fail-on-unknown-properties=true
Or create a component:
#Component
public class Jackson2ObjectMapperBuilderCustomizerImpl implements Jackson2ObjectMapperBuilderCustomizer {
#Override
public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
jacksonObjectMapperBuilder.featuresToEnable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
}
}
I'm new with Spring Boot and I have difficult to understand how can I pass data. For example:
I want pass those data to my server:
{
"code", 1,
"name": "C01"
}
So I have create always a custom Object with code and name as attributes to have this http post api?
#RequestMapping(value = "/new/", method = RequestMethod.POST)
public ResponseEntity<?> createOrder(#RequestBody CustomObject customObject){
...
}
Another solution I see that can be this but I can't pass numbers (int code), right?
#RequestMapping(value = "/new/{code}/{name}", method = RequestMethod.POST)
public ResponseEntity<?> createOrder(#PathVariable("code") int code, #PathVariable("name") String name) {
...
}
Kind regards :)
You can pass code and name as PathVariables just like in your example:
#RequestMapping(value = "/new/{code}/{name}")
public ResponseEntity<?> createOrder(#PathVariable("code") int code, #PathVariable("name") String name) {
...
}
A PathVariable can be an int or a String or a long or a Date, according to the docs:
A #PathVariable argument can be of any simple type such as int, long, Date, etc. Spring automatically converts to the appropriate type or throws a TypeMismatchException if it fails to do so.
You could also define a PathVariable of type Map<String, Object> like this:
#RequestMapping(value = "/new/{code}/{name}")
public ResponseEntity<?> createOrder(#PathVariable("map") Map<String, Object> map) {
Integer code = (Integer) map.get("code");
String name = (String) map.get("name");
...
}
You could even use #RequestParam and supply the data in the form of URL query parameters.
So, there are numerous ways in which data can be passed to a Spring MVC controller (more details in the docs) but I think the convention for posting complex data (by "complex" I mean more than a single piece of state) is to define a request body which contains a serialised form of that complex state i.e. what you showed in the first example in your queston:
#RequestMapping(value = "/new/", method = RequestMethod.POST)
public ResponseEntity<?> createOrder(#RequestBody CustomObject customObject){
...
}
If this question is about RESTful best practice, since you are developing webservice for creating an Order object, this is how I would design it
Order.java
public class Order {
private Integer code;
private String name;
public Integer getCode() {
return code;
}
public void setCode(final Integer code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
}
Controller
#RequestMapping(value = "/orders", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public ResponseEntity<Order> createOrder(#Valid #RequestBody Order order){
...
}
Technically, you can do many things to achieve the same thing, but that will not be a RESTful service, it will be an RPC at best.
Example:
public String getStudentResult(#RequestParam(value = "regNo", required = true) String regNo, ModelMap model){
How can I use #valid for the regNo parameter here?
Late answer. I encounter this problem recently and find a solution. You can do it as follows,
Firstly register a bean of MethodValidationPostProcessor:
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
and then add the #Validated to the type level of your controller:
#RestController
#Validated
public class FooController {
#RequestMapping("/email")
public Map<String, Object> validate(#Email(message="请输入合法的email地址") #RequestParam String email){
Map<String, Object> result = new HashMap<String, Object>();
result.put("email", email);
return result;
}
}
And if user requested with a invalid email address, the ConstraintViolationException will be thrown. And you can catch it with:
#ControllerAdvice
public class AmazonExceptionHandler {
#ExceptionHandler(ConstraintViolationException.class)
#ResponseBody
#ResponseStatus(HttpStatus.BAD_REQUEST)
public String handleValidationException(ConstraintViolationException e){
for(ConstraintViolation<?> s:e.getConstraintViolations()){
return s.getInvalidValue()+": "+s.getMessage();
}
return "请求参数不合法";
}
}
You can check out my demo here
#Valid can be used to validate beans. I have'nt seen it used on single string parameters. Also it requires a validator to be configured.
The #Valid annotation is part of the standard JSR-303 Bean Validation API, and is not a Spring-specific construct.
Spring MVC will validate a #Valid object after binding so-long as an appropriate Validator has been configured.
Reference : http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html
one way to do it is to write a Wrapper Bean like the following :
public class RegWrapperBean{
#NotNull
String regNo ;
public String getRegNo(){
return regNo ;
}
public void setRegNo(String str){
this.regNo=str;
}
}
and your handler method will be like the following :
#RequestMapping(value="/getStudentResult", method=RequestMethod.POST)
public String getStudentResult(#Valid #ModelAttribute RegWrapperBean bean,
BindingResult validationResult, Model model) {
}
and please refer to these answers here and here .
Hope that Helps .
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();
}
}