Explicit Registrations of handler mappings - spring

I was going through spring documentation more specifically handler mapping section and came to know that we can register the handler mapping method at runtime. I understand how it is done but the thing that I am not able to grasp is why do we need such functionality in the first place?
Please refer following code snippet for registering handlers.
#Autowired
public void setHandlerMapping(RequestMappingHandlerMapping mapping, HelloRestController handler)
throws NoSuchMethodException {
RequestMappingInfo info = RequestMappingInfo
.paths("/mycustomapi").methods(RequestMethod.GET).build();
Method method = HelloRestController.class.getMethod("customHandler");
mapping.registerMapping(info, handler, method);
}
Documentation
If someone can explain some use cases where it required then it will be helpful thank you.

I have never used this, but here is one utility I can think of :
As annotations only accept constant expression as parameters, you can't write something like that :
#GetMapping(requestMapping(...)) // The value for annotation GetMapping.value must be a constant expression
public String customHandler(...) {
...
}
But you could write that :
RequestMappingInfo info = RequestMappingInfo
.paths(requestMapping(...))
.methods(RequestMethod.GET)
.build();
In oher words, you could write handlers for URI that are calculated (stored in a configuration file for example).

Related

Accessing multiple controllers with same request mapping

Please find my HomeController and DemoController
class HomeController{
#RequestMapping(value="index")
public void home(){
}
}
class DemoController{
#RequestMapping(value="index")
public void demo(){
}
}
when I try to send a request to index, which one will get executed?
I wanted to know how can we have same request mapping value for multiple controllers
https://stackoverflow.com/a/34590355/2682499 is only partially correct at this point.
You can have multiple controller methods use the same URI so long as you provide Spring enough additional information on which one it should use. Whether or not you should do this is a different question. I would certainly not recommend using the same URI in two separate controller classes to avoid confusion, though.
You can do something like this:
class HomeController{
#RequestMapping(value="/index", params = {"!name", "!foo"})
public List<Something> listItems(){
// retrieve Something list
}
#RequestMapping(value="/index", params = "name")
public List<Something> listItems(String name) {
// retrieve Something list WHERE name LIKE %name%
}
#RequestMapping(value="/index", params = {"!name", "foo"})
public List<Something> listItems(String foo) {
// Do something completely different
}
}
For the full documentation on what is possible when overloading URIs you should reference the #ReqeustMapping documentation: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestMapping.html. And, specifically https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestMapping.html#params-- for the section request parameters.
In Spring Web MVC this is not possible. Each mapping must be unique in your context. If not, you will receive a RuntimeException during context initialization.
You cannot even use parameters to differentiate your endpoints because they are not evaluated while searching for a suitable handler (applicable for Servlet environments). From #RequestMapping javadoc:
In a Servlet environment, parameter mappings are considered as restrictions that are enforced at the type level. The primary path mapping (i.e. the specified URI value) still has to uniquely identify the target handler, with parameter mappings simply expressing preconditions for invoking the handler.
Note that you can do the opposite, so multiple URLs can point to the same handler. Have a look at Spring MVC: Mapping Multiple URLs to Same Controller
Unfortunately, this is not possible. The request mapping has to be unique otherwise the application can't determine which method the incoming request should be mapped to.
What you can do instead is to extend the request mapping:
class HomeController{
#RequestMapping(value="home/index")
public void home(){
}
}
class DemoController{
#RequestMapping(value="demo/index")
public void demo(){
}
}

Spring validation keeps validating the wrong argument

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.

Spring #RequestMapping consumes charset?

I'm trying to use #RequestMapping with the consumes-element. Reading the API-document it works on the Content-Type-header of the request. However, using
#RequestMapping(consumes = "application/x-www-form-urlencoded;charset=UTF-8", value = "/test")
public void test() {
:
}
or
#RequestMapping(consumes = "application/x-www-form-urlencoded;charset=ISO-8859-1", value = "/test")
public void test() {
:
}
doesn't make a difference. The header in the request can look like
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
or
Content-Type: application/x-www-form-urlencoded
test() will be called in all four possible constellations.
However, and this is proof to me that Spring sees and tries to use the charset-part, if I specify
#RequestMapping(consumes = "application/x-www-form-urlencoded;charset=UTF-x8", value = "/test")
public void test() {
:
}
I get an exception during startup (!) of the web-app:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#0': Initialization of bean failed;
nested exception is java.nio.charset.UnsupportedCharsetException: UTF-x8
Note that the documentation on the produces-element also doesn't mention the use of charset, but according to Google some use it.
Any clues to what's happening here or what I'm doing wrong?
BTW, this is Spring 3.1.1.RELEASE.
I think you have already answered your question, so this is more of a confirmation, from a code point of view, as to why the charset is not taken into account when resolving mappings.
When digging into Spring code, the culprit seems to be the MediaType#includes(). method
More digging reveals that a RequestMappingInfo is created in association to the RequestMapping annotation of the method. This RequestMappingInfo stores a series of AbstractRequestCondition objects, one of them being the ConsumesRequestCondition which holds the MediaType defined in the consumes part of the annotation (i.e. application/x-www-form-urlencoded;charset=UTF-8).
Later when a request is made, this ConsumesRequestCondition has an inner ConsumeMediaTypeExpression class with a matchMediaType() method that extracts the MediaType of the HttpServletRequest and checks it against it's own MediaType to see if it's included.
If you look at the MediaType#includes() implementation (Lines 426 to 428), it returns true when type (i.e. application) and subtype (i.e. x-www-form-urlencoded) are equal, completely disregarding the parameters Map which in this case
holds the remnant "charset","UTF-8" combination.
Digging into the produces track seems to show similar results, but in this case it's the MediaType#isCompatibleWith() method involved, and again, it reaches only to type and subtype if they are equal.
If you found evidence on Google of the produces working for charset request mapping, I would doubt it (unless they changed core Spring stuff)
As to why it was designed this way, well that's another question :)

URI-specific ExceptionMapper in JAX-RS

I'm using Jersey, and Guice as my IOC-container. I'd like to know if it is possible to associate an ExceptionMapper with a specific URI. The reason for this is that I want to map the same exception differently based on what URI was visited. For example, suppose I've got the following two exception mappers for my custom exception:
public class MyExceptionMapperForFirstURI implements
ExceptionMapper<MyException> {..return response based on first URI..}
public class MyExceptionMapperForSecondURI implements
ExceptionMapper<MyException> {..return response based on second URI..}
As far as I understand you bind an ExceptionMapper in your ServletModule as follows:
public class MyModule extends ServletModule {
#Override
public void configureServlets() {
super.configureServlets();
bind(MyCustomExceptionMapper.class);
}
}
How would I go about binding MyExceptionMapperForFirstURI and MyExceptionMapperForSecondURI so that they get associated with the correct URIs. Is this possible, and if possible: is this the correct way to do this?
This is quite late answer ;-) but you can always inject the UriInfo and branch on that. So,
#Context
UriInfo uriInfo;
.....
if (matchesA(uriInfo.getAbsolutePath())) {
// do something
}
Not sure how the URI's of your app look like, but if it is possible to split your app into two servlets or filters, then you can do it like that - i.e. have one servlet/filter serve one set of resources and include the first mapper and have the other servlet/filter serve the other set of resources and include the other mapper.
If these are custom exceptions, you can also pass Request as an argument to the exception and have just a single mapper - decide on the response based in the request uri in the mapper.

Using EJB interceptors after a method call

I know one can use interceptors before a method call by using the #AroundInvoke annotation.
What I would like to do is execute certain code after the method call, so that I can for example create a log entry before and after a method execution.
Is this possible with EJB3, or do I need to use AOP?
#AroundInvoke interceptor is passed InvocationContext, and proceed() must be called to advance the method. Thus:
#AroundInvoke
public Object log(InvocationContext ic) throws Exception {
logEntry();
try {
return ic.proceed();
} finally {
logExit();
}
}
Depending on your needs, you could also log the return value or exceptions, filter the methods being logged, etc.

Resources