Is there a way to specify a custom Getter method in SpringMVC binding, rather than the default PropertyDescriptors?
I feel like I'm venturing into outer space with this question, sounds so simple and yet no Google results. I'm doing this because my model has a Boolean has a getter named isSomething() and SpringMVC will only check that for primitive booleans. For the class Boolean it'll complain that a getter is not found, it requires getSomething().
I should be able to just specify custom getters/setters if I need those, isn't that the case?
Since I know people will suggest custom Property Editors etc., I put a breakpoint in these methods, and it doesn't even get here -- the problem happens somewhere up the chain, with PropertyDescriptors:
binder.registerCustomEditor(Boolean.class, new PropertyEditorSupport() {
#Override
public void setValue(Object value) {
// TODO Auto-generated method stub
super.setValue(value);
}
#Override
public Object getValue() {
// TODO Auto-generated method stub
return super.getValue();
}
});
I wanted to play with these custom editors, but the code never gets here. The error is thrown before that.
Invalid property 'test' of bean class [Model]: Bean property 'test' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
How can I achieve this fairly common scenario of custom getters/setters?
Related
I am trying to learn Spring Boot microservices. Now I am trying to implement circuit breaker with resilience4j if any of my called service is off.
If I set the fallback method return type as like the actual method return type than it works fine but I can't show the information that my service is off. Because it then send the response null in object's fields. But if I change the return type to String on actual method and also in fallback then I will not be able to get the object value as JSON.
Is it possible to return as string something like Branch service is down!.. with my fallback method and if OK then get the object value as JSON from actual called method? My attempts are below:
My controller method:
#GetMapping("/getById/{id}")
#CircuitBreaker(name = "default", fallbackMethod = "employeeFallback")
public ResponseModelEmployee getEmployee(#PathVariable("id") Long id) {
return employeeService.findByEmployeeId(id);
}
My fallback method in controller:
public ResponseModelEmployee employeeFallback(Long id, Exception ex) {
return new ResponseModelEmployee();
}
My service method called from controller:
public ResponseModelEmployee findByEmployeeId(Long id) {
ResponseModelEmployee empDetails = new ResponseModelEmployee();
...
Branch branch = restTemplate.getForObject("http://BRANCH-SERVICE/branch/getById/" +
employee.get().getBranchId(),
Branch.class);
...
return empDetails;
}
My desire method as fallback:
public String employeeFallback(Long id, Exception ex) {
return "Branch Service is down";
}
If I set my desire method for fallback then it gives the following error:
java.lang.NoSuchMethodException: class com.example.employee.VO.ResponseModelEmployee class com.example.employee.controller.EmployeeController.employeeFallback(class java.lang.Long,class java.lang.Throwable) at io.github.resilience4j.fallback.FallbackMethod.create(FallbackMethod.java:92) ~[resilience4j-spring-1.7.0.jar:1.7.0] ....
Resilince4j expects the fallback method to have the same return type as of the actual method.
Documentation says:
It's important to remember that a fallback method should be placed in
the same class and must have the same method signature with just ONE
extra target exception parameter).
If there are multiple fallbackMethod methods, the method that has the
most closest match will be invoked, for example:
If you try to recover from NumberFormatException, the method with
signature String fallback(String parameter, IllegalArgumentException
exception)} will be invoked.
You can define one global fallback method with an exception parameter
only if multiple methods has the same return type and you want to
define the same fallback method for them once and for all.
So, you cannot directly change the return type to a different one.
You can try few suggestions:
Add #CircuitBreaker and fallback to the service method.
Change return type of service method and fallback method to Object.
One more way could be , you can keep the return type as it is but add a String type message object to response model ResponseModelEmployee. Then set message string to it during fallback.
Another solution could be to return ResponseEntity from the from the method where rest call is made and in the fallback method use ResponseEntity<?> as response object.
you can use Object as a return type
in my case for example:
#GetMapping("/getUser/{id}")
#CircuitBreaker(name= something , fallbackMethod = "ContactsServiceDown")
public ResponseEntity<User> getDetailsById(#PathVariable(id)){
//some logic
return new ResponseEntity<User>(user , HttpStatus.OK);
}
public ResponseEntity<Object> ContactsServiceDown(int id , Exception e){
//some logic
return new ResponseEntity<Object>("ContactsServersDown", HttpStatus.Forbidden)
}
or in returnType ResponseEntity<> leave the type Field empty, hopefully it may work!
I have the following Umple model
class SomeClass{
att1;
Boolean setAtt1(String aAtt1){
//Do something
}
}
I need to override the setter method of att1. When I try to do it as in the model above, I get a warning. What is the appropriate way to extend setter or getter methods in such a case?
Setter and getter methods are auto generated, and should not be redefined. Alternatively, what you can do is to use the aspect-orientation features to add before or after as below
class SomeClass{
att1;
before setAtt1{
//Do something before
}
after setAtt1{
//Do something after
}
}
I have a controller with a web method that looks like this:
public Response registerDevice(
#Valid final Device device,
#RequestBody final Tokens tokens
) {...}
And a validator that looks like this:
public class DeviceValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return Device.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
// Do magic
}
}
}
I'm trying to get Spring to validate the Device argument which is being generated by an interceptor. But every time I try, it validates the tokens argument instead.
I've tried using #InitBinder to specify the validator, #Validated instead of #Validand registering MethodValidationPostProcessor classes. So far with no luck.
Either the validator is not called at all, or tokens argument is validated when I was the Device argument validated.
I'm using Spring 4.1.6 and Hibernate validator 5.1.3.
Can anyone offer any clues as to what I'm doing wrong? I've searched the web all afternoon trying to sort this out. Can't believe that the validation area of spring is still as messed up as it was 5 years ago :-(
Ok. Have now solved it after two days of messing about with all sorts of variations. If there is one thing Spring's validation lets you do - it's come up with an incredible array of things that don't work! But back to my solution.
Basically what I needed was a way to manually create request mapping arguments, validate them and then ensure that no matter whether it was a success or failure, that the caller always received a custom JSON response. Doing this proved a lot harder than I thought because despite the number of blog posts and stackoverflow answers, I never found a complete solution. So I've endeavoured to outline each piece of the puzzle needed to achieve what I wanted.
Note: in the following code samples, I've generalised the names of things to help clarify whats custom and whats not.
Configuration
Although several blog posts I read talked about various classes such as the MethodValidationPostProcessor, in the end I found I didn't need anything setup beyond the #EnableWebMvc annotation. The default resolvers etc proved to be what I needed.
Request Mapping
My final request mapping signatures looked like this:
#RequestMapping(...)
public MyMsgObject handleRequest (
#Valid final MyHeaderObj myHeaderObj,
#RequestBody final MyRequestPayload myRequestPayload
) {...}
You will note here that unlike just about every blog post and sample I found, I have two objects being passed to the method. The first is an object that I want to dynamically generate from the headers. The second is a deserialised object from the JSON payload. Other objects could just as easily be included such as path arguments etc. Try something like this without the code below and you will get a wide variety of weird and wonderful errors.
The tricky part that caused me all the pain was that I wanted to validate the myHeaderObj instance, and NOT validate the myRequestPayload instance. This caused quite a headache to resolve.
Also note the MyMsgObject result object. Here I want to return an object that will be serialised out to JSON. Including when exceptions occur as this class contains error fields that need to be populated in addition to the HttpStatus code.
Controller Advice
Next I created an ControllerAdvice class which contained the binding for validation and a general error trap.
#ControllerAdvice
public class MyControllerAdvice {
#Autowired
private MyCustomValidator customValidator;
#InitBinder
protected void initBinder(WebDataBinder binder) {
if (binder.getTarget() == null) {
// Plain arguments have a null target.
return;
}
if (MyHeaderObj.class.isAssignableFrom(binder.getTarget().getClass())) {
binder.addValidators(this.customValidator);
}
}
#ExceptionHandler(Exception.class)
#ResponseStatus(value=HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
public MyMsgObject handleException(Exception e) {
MyMsgObject myMsgObject = new MyMsgObject();
myMsgObject.setStatus(MyStatus.Failure);
myMsgObject.setMessage(e.getMessage());
return myMsgObject;
}
}
Two things going on here. The first is registering the validator. Note that we have to check the type of the argument. This is because #InitBinder is called for each argument to the #RequestMapping and we only want the validator on the MyHeaderObj argument. If we don't do this, exceptions will be thrown when Spring attempts to apply the validator to arguments it's not valid for.
The second thing is the exception handler. We have to use #ResponseBody to ensure that Spring treats the returned object as something to be serialised out. Otherwise we will just get the standard HTML exception report.
Validator
Here we use a pretty standard validator implementation.
#Component
public class MyCustomValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return MyHeaderObj.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
...
errors.rejectValue("fieldName", "ErrorCode", "Invalid ...");
}
}
One thing that I still don't really get with this is the supports(Class<?> clazz) method. I would have thought that Spring uses this method to test arguments to decide if this validator should apply. But it doesn't. Hence all the code in the #InitBinder to decide when to apply this validator.
The Argument Handler
This is the biggest piece of code. Here we need to generate the MyHeaderObj object to be passed to the #RequestMapping. Spring will auto detect this class.
public class MyHeaderObjArgumentHandler implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return MyHeaderObj.class.isAssignableFrom(parameter.getParameterType());
}
#Override
public Object resolveArgument(
MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
// Code to generate the instance of MyHeaderObj!
MyHeaderObj myHeaderObj = ...;
// Call validators if the argument has validation annotations.
WebDataBinder binder = binderFactory.createBinder(webRequest, myHeaderObj, parameter.getParameterName());
this.validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors()) {
throw new MyCustomException(myHeaderObj);
}
return myHeaderObj;
}
protected void validateIfApplicable(WebDataBinder binder, MethodParameter methodParam) {
Annotation[] annotations = methodParam.getParameterAnnotations();
for (Annotation ann : annotations) {
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] { hints });
binder.validate(validationHints);
break;
}
}
}
}
The main job of this class is to use whatever means it requires to build the argument (myHeaderObj). Once built it then proceeds to call the Spring validators to check this instance. If there is a problem (as detected by checking the returned errors), it then throws an exception that the #ExceptionHandler's can detect and process.
Note the validateIfApplicable(WebDataBinder binder, MethodParameter methodParam) method. This is code I found in a number of Spring's classes. It's job is to detect if any argument has a #Validated or #Valid annotation and if so, call the associated validators. By default, Spring does not do this for custom argument handlers like this one, so it's up to us to add this functionality. Seriously Spring ???? No AbstractSomething ????
The last piece, explicit Exception catches
Lastly I also needed to catch more explicit exceptions. For example the MyCustomException thrown above. So here I created a second #ControllerAdvise.
#ControllerAdvice
#Order(Ordered.HIGHEST_PRECEDENCE) // Make sure we get the highest priority.
public class MyCustomExceptionHandler {
#ExceptionHandler
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
#ResponseBody
public Response handleException(MyCustomException e) {
MyMsgObject myMsgObject = new MyMsgObject();
myMsgObject.setStatus(MyStatus.Failure);
myMsgObject.setMessage(e.getMessage());
return myMsgObject;
}
}
Although superficially the similar to the general exception handler. There is one different. We need to specify the #Order(Ordered.HIGHEST_PRECEDENCE) annotation. Without this, Spring will just execute the first exception handler that matches the thrown exception. Regardless of whether there is a better matching handler or not. So we use this annotation to ensure that this exception handler is given precedence over the general one.
Summary
This solution works well for me. I'm not sure that I've got the best solution and there may be Spring classes which I've not found which can help. I hope this helps anyone with the same or similar problems.
I have marked a class with #ControllerAdvice
Added a method
#ModelAttribute
public void setSourceAppId(Model model)
{
model.addAttribute("myattribute","1234");
}
But this method is not getting called at all.
I have not yet used modelattribute with a setter method so i cannot tell that thats a wrong approach, but changing it to a getter will do the job. Also, you dont need to add it to the model yourself. Modelattribute is handling exactly that.
#ModelAttribute("myattribute")
public int getSourceAppId()
{
return 1234;
}
Let's say I have the following command object:
class BreakfastSelectCommand{
List<Breakfast> possibleBreakfasts;
Breakfast selectedBreakfast;
}
How can I have spring populate "selectedBreakfast" with a breakfast from the list?
I was figuring I'd do something like this in my jsp:
<form:radiobuttons items="${possibleBreakfasts}" path="selectedBreakfast" />
But this doesn't seem to work. Any ideas?
thanks,
-Morgan
The key to it all of this is the PropertyEditor.
You need to define a PropertyEditor for your Breakfast class and then configure the ServletDataBinder using registerCustomEditor in the initBinder method of your controller.
example:
public class BreakfastPropertyEditor extends PropertyEditorSupport{
public void setAsText(String incomming){
Breakfast b = yourDao.findById( Integer.parseInt(incomming));
setValue(b);
}
public String getAsText(){
return ((Breakfast)getValue()).getId();
}
}
note you'll be needing some null checking etc, but you get the idea. In your controller:
public BreakfastFooBarController extends SimpleFormController {
#Override
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) {
binder.registerCustomEditor(Breakfast.class, new BreakfastPropertyEditor(yourDao));
}
}
things to watch out for:
PropertyEditor's are not thread safe
if you need spring beans, either manually inject them or define them in spring as prototype scope and use method injection into your controller
throw IllegalArgumentException if the inbound parameter is not valid/not found, spring will convert this into a binding error correctly
hope this helps.
Edit (in response to the comment):
It looks a little strange in the given example because BreakfastSelectCommand doesn't look like an entity, I'm not sure what the actual scenario you have is. Say it is an entity, for example like Person with a breakfast property then the formBackingObject() method would load the Person object from the the PersonDao and return it as the command. The binding phase would then change the breakfast property depending on the selected value, such that the command that arrives in onSubmit has the breakfast property all set up.
Depending on the implementation of your DAO objects calling them twice or attempting to load the same entity twice doesn't actually mean that you will get two SQL statements being run. This applies particularly to Hibernate, where it guarantees that it will return the same object that is in it's session for a given identifier, thus running letting the binding attempt to load the Breakfast selection even through it hasn't changed shouldn't result in any undue overhead.