Spring ControllerAdvice - Fail to override handleHttpRequestMethodNotSupported() in ResponseEntityExceptionHandler - spring

Here's a few facts for the situation that I'm currently facing
I have recently built a RestControllerAdvice with variousExceptionHandler as a global exception handler for my Spring RestController.
As I would like to return my customized response json for handling the pre-defined HTTP error as specified in ResponseEntityExceptionHandler, my RestControllerAdvice class inherits the ResponseEntityExceptionHandler and methods like handleHttpRequestMethodNotSupported(), handleHttpMessageNotReadable() are overriden.
I have successfully overridden handleHttpMediaTypeNotSupported() and handleHttpMessageNotReadable() but when it comes to handleHttpRequestMethodNotSupported(), I fail to do so.
Here's an excerpt of my code:
#Order(Ordered.HIGHEST_PRECEDENCE)
#RestControllerAdvice(annotations=RestController.class)
public class TestRestExceptionHandler extends ResponseEntityExceptionHandler{
#Override
protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request){
BaseResponseJson response = new BaseResponseJson();
response.setRespCode(BaseResponseJson.JSON_RESP_CODE_ERROR);
response.setRespMsg("Request Method Not Supported");
return handleExceptionInternal(ex, response, headers, status, request);
}
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request){
BaseResponseJson response = new BaseResponseJson();
response.setRespCode(BaseResponseJson.JSON_RESP_CODE_ERROR);
response.setRespMsg("Message Not Readable");
return handleExceptionInternal(ex, response, headers, status, request);
}
#Override
protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request){
BaseResponseJson response = new BaseResponseJson();
response.setRespCode(BaseResponseJson.JSON_RESP_CODE_ERROR);
response.setRespMsg("Media Type Not Supported");
return handleExceptionInternal(ex, response, headers, status, request);
}
}
The log for handleHttpRequestMethodNotSupported() is shown as follow:
[2019-06-05T17:49:50.368+0800][XNIO-74 task-7][WARN ][o.s.w.s.m.s.DefaultHandlerExceptionResolver] Resolved exception caused by Handler execution: org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported
The log for handleHttpMessageNotReadable() is shown as follow:
[2019-06-05T17:50:21.915+0800][XNIO-74 task-8][WARN ][o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver] Resolved exception caused by Handler execution
As you can see, the successful code is handled by ExceptionHandlerExceptionResolver while the malfunction code is handled by DefaultHandlerExceptionResolver.
I am wondering what is the underlying reason and I will appreciate if someone can recommend any available solution. Thank you.

From the jackycflau answer, we can summarise as 2 questions.
Q1. Why removing annotations=RestController.class will works for HttpRequestMethodNotSupportedException
Q2. Why only HttpRequestMethodNotSupportedException is not caught?
To answer these 2 questions, we need to take a look to code on how spring handle exceptions. The following source code are based on spring 4.3.5.
During spring DispatcherServlet processing the request, when error occur, HandlerExceptionResolver will try to resolve the exception. In the given case, the exception is delegated to ExceptionHandlerExceptionResolver. The method to determine which method to resolve the exception is (getExceptionHandlerMethod in ExceptionHandlerExceptionResolver.java line 417)
/**
* Find an {#code #ExceptionHandler} method for the given exception. The default
* implementation searches methods in the class hierarchy of the controller first
* and if not found, it continues searching for additional {#code #ExceptionHandler}
* methods assuming some {#linkplain ControllerAdvice #ControllerAdvice}
* Spring-managed beans were detected.
* #param handlerMethod the method where the exception was raised (may be {#code null})
* #param exception the raised exception
* #return a method to handle the exception, or {#code null}
*/
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
Class<?> handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null);
if (handlerMethod != null) {
ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
if (resolver == null) {
resolver = new ExceptionHandlerMethodResolver(handlerType);
this.exceptionHandlerCache.put(handlerType, resolver);
}
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
}
}
for (Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
if (entry.getKey().isApplicableToBeanType(handlerType)) {
ExceptionHandlerMethodResolver resolver = entry.getValue();
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(entry.getKey().resolveBean(), method);
}
}
}
return null;
}
Since we are using #RestControllerAdvice, we only need to focus in the for loop, which determine which ControllerAdviceBean to use. We can see that the method isApplicableToBeanType will determine if the ControllerAdviceBean is applicable, and the related code are (ControllerAdviceBean.java line 149)
/**
* Check whether the given bean type should be assisted by this
* {#code #ControllerAdvice} instance.
* #param beanType the type of the bean to check
* #see org.springframework.web.bind.annotation.ControllerAdvice
* #since 4.0
*/
public boolean isApplicableToBeanType(Class<?> beanType) {
if (!hasSelectors()) {
return true;
}
else if (beanType != null) {
for (String basePackage : this.basePackages) {
if (beanType.getName().startsWith(basePackage)) {
return true;
}
}
for (Class<?> clazz : this.assignableTypes) {
if (ClassUtils.isAssignable(clazz, beanType)) {
return true;
}
}
for (Class<? extends Annotation> annotationClass : this.annotations) {
if (AnnotationUtils.findAnnotation(beanType, annotationClass) != null) {
return true;
}
}
}
return false;
}
private boolean hasSelectors() {
return (!this.basePackages.isEmpty() || !this.assignableTypes.isEmpty() || !this.annotations.isEmpty());
}
By reading the code, we can explain what is happening:
Answer for Q1
When annotations=RestController.class is removed, hasSelectors will return false, and hence isApplicableToBeanType will return true. So HttpRequestMethodNotSupportedException will be handled by TestRestExceptionHandler in this case.
Answer for Q2
For HttpRequestMethodNotSupportedException, DispatcherSerlvet can not find controller method to handle request. Hence handlerMethod passed to getExceptionHandlerMethod is null, then beanType passed to isApplicableToBeanType is also null and false is returned.
On the other hand, DispatcherSerlvet can find controller method for HttpMessageNotReadableException or HttpMediaTypeNotSupportedException. So the rest controller handler method will be passed to getExceptionHandlerMethod and isApplicableToBeanType will return true.

I have found out the culprit of the issue, which is regarding the #RestControllerAdvice annotation.
Orginally, I have annotated the class with #RestControllerAdvice(annotations=RestController.class).
After I remove the annotations key-value pair (i.e. just annotate the class with #RestControllerAdvice), HttpRequestMethodNotSupportedException is now successfully caught.
This is the solution that I can only be able to share. I don't understand the underlying reason and such behavior seems quite weird to me... Probably because the HttpRequestMethodNotSupportedException is not under the control by #RestController??? (just a wild guess). I will be happy if someone can give a full explanation on such behavior.

Related

Strange behaviour of the exception handler on spring boot RestControllerAdvice handleExceptionInternal

Trying to save 2 times the same barcode card the method saveBarcodecard (left side image) throw the error: SQLIntegrityConstraintViolationException: "Duplicate entry '1-*****-EAN13' for key 'fcs_barcodecards.UK4ComplexKey" that I was expected to be intercepted by the method: handleExceptionInternal meant to handler ALL the Exceptions that are not specifically custom implemented but not.
Please note that:
SQLIntegrityConstraintViolationException extends ... extends ... Exception
I need to implement a custom handler handleSQLIntegrityConstraintViolationException (please see the image 2 below -green-) to solve this issue.
My simple question is: how come this? :)
Many thanks in advance for your answers, if any :)
#Service
public class FcsBarcodecardServiceImpl implements FcsBarcodecardService {
#Autowired
FcsBarcodecardRepository fcsBarcodecardRepository;
#Autowired
private FcsClientRepository fcsClientRepository;
#Autowired
private FcsBarcodecardMapper fcsBarcodecardMapper;
#Override
public FcsBarcodecardResponse saveBarcodecard(Long clientId, FcsBarcodecard fcsBarcodecard) {
Optional<FcsClient> fcsClient = fcsClientRepository.findById(clientId);
if (!fcsClient.isPresent()) {
throw new UsernameNotFoundException("This client do not exists!");
}
fcsBarcodecard.setFcsClient(fcsClient.get());
return fcsBarcodecardMapper
.fromFcsBarcodecardToFcsBarcodecardResponse(
fcsBarcodecardRepository.save(fcsBarcodecard));
}
}
#RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
private static final String VALIDATION_ERROR_CHECK_ERRORS_FIELD_FOR_DETAILS = "Validation error. Check 'errors' field for details.";
/**
* Predefined: A single place to customize the response body of all exception
* types.
*/
#Override
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ResponseEntity<Object> handleExceptionInternal(Exception exception, Object body, HttpHeaders headers,
HttpStatus status, WebRequest request) {
return buildErrorResponse(exception, FCS_EALLTYPES500, status, request);
}
#ExceptionHandler(SQLIntegrityConstraintViolationException.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ResponseEntity<Object> handleSQLIntegrityConstraintViolationException(SQLIntegrityConstraintViolationException ex,
WebRequest request) {
return buildErrorResponse(ex, FCS_EALLTYPES500, HttpStatus.INTERNAL_SERVER_ERROR, request);
}
ResponseEntityExceptionHandler#handleExceptionInternal() is a place to customize the response body of only those exception types that are handled by an #ExceptionHandler defined in ResponseEntityExceptionHandler. As of the latest Spring version those are
#ExceptionHandler({
HttpRequestMethodNotSupportedException.class,
HttpMediaTypeNotSupportedException.class,
HttpMediaTypeNotAcceptableException.class,
MissingPathVariableException.class,
MissingServletRequestParameterException.class,
ServletRequestBindingException.class,
ConversionNotSupportedException.class,
TypeMismatchException.class,
HttpMessageNotReadableException.class,
HttpMessageNotWritableException.class,
MethodArgumentNotValidException.class,
MissingServletRequestPartException.class,
BindException.class,
NoHandlerFoundException.class,
AsyncRequestTimeoutException.class
})
Obviously your SQLIntegrityConstraintViolationException is not among them and requires a separate exception handler to be caught and processed by.
If you'd like to have a single place to handle any expection, you'd have to specifically define an exception handler with e.g. #ExceptionHandler(Exception.class)

How can I make sure exceptions during parsing lead to the same kind of response as the (custom) response returned for validation failures?

I'm using Spring to create an API, but I'm having some trouble introducing custom error reporting on (a part of) the validation of the request body.
When parsing/validation errors occur, I want to give a custom response back to the user.
This works well for fields annotated with #Valid along with validators like #javax.validation.constraints.NotNull by using a custom ResponseEntityExceptionHandler annotated with #ControllerAdvice.
It does not work however if an Exception is thrown while parsing the request body (before the validations even run). In that case I get an html error page with status 500 (Server Error)
How can I make sure the exceptions during parsing lead to the same kind of response as the (custom) one I return for validation failures?
My endpoint's code looks like this:
#RequestMapping(value= "/endpoint"
produces = { "application/json" },
consumes = { "application/json" },
method = RequestMethod.POST)
default ResponseEntity<Object> postSomething(#Valid #RequestBody MyRequestBody requestData){
// ...
}
MyRequestBody class looks like this:
#Validated
public class MyRequestData {
#JsonProperty("stringValue")
private String stringValue = null;
#NotNull
#Valid
public String getStringValue() {
return stringValue;
}
// ...
public enum EnumValueEnum {
VALUE_1("value 1"),
VALUE_1("value 2");
private String value;
EnumValueEnum(String value) {
this.value = value;
}
#Override
#JsonValue
public String toString() {
return String.valueOf(value);
}
#JsonCreator
public static EnumValueEnum fromValue(String text) {
if(text == null){
return null;
}
for (EnumValueEnum b : EnumValueEnum.values()){
if (String.valueOf(b.value).equals(text)) {
return b;
}
}
throw new HttpMessageNotReadableException("EnumValueEnum \"" + text + "\" does not exist");
}
}
#JsonProperty("enumValue")
private EnumValueEnum enumValue = null;
}
The custom validation error handling (and reporting) looks like this:
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class MyValidationHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
// return status(BAD_REQUEST).body(new ValidationResponse(ex.getBindingResult().getFieldErrors()));
}
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
// return status(BAD_REQUEST).body(new ValidationResponse((JsonMappingException) ex.getCause()));
}
}
In this code, if a user sends a request with an enum value that doesn't exist, an HttpMessageNotReadableException is thrown. I would like to catch that somewhere and replace it with a custom response that is consistent with the other exception handling I do. Where/How can I do that?
I found a solution to my own problem.
You can actually use Spring MVC's normal exception handling:
Annotating a method with #ExceptionHandler will make Spring try to use it for exception handling for the exception type specified (in the annotation's value field or the method's argument). This method can be placed in the controller or even in the ResponseEntityExceptionHandler I use for the other validation response handling.
#ExceptionHandler
public ResponseEntity handle(HttpMessageConversionException e){
// return status(BAD_REQUEST).body(new ValidationResponse((JsonMappingException) e.getCause()));
}
Mind which type of exception you handle:
The catch here was that the exception thrown while parsing is wrapped in (some subtype of) a JsonMappingException which in turn is wrapped again in a HttpMessageConversionException.
e instanceof HttpMessageConversionException
e.getCause() instanceof JsonMappingException
e.getCause().getCause() // == your original exception
The #ExceptionHandler should therefor accept HttpMessageConversionException instead of the originally thrown exception (which in my case was HttpMessageNotReadableException)
It will not work if you write an #ExceptionHandler that only accepts your original Exception!

setExpectedResponseType() method in HttpRequestExecutingMessageHandler

Below is the configuration of HttpRequestExecutingMessageHandler
#ServiceActivator(inputChannel = "rtpRequestChannel")
#Bean
public MessageHandler httResponseMessageHandler(MessageChannel rtpResponseChannel) {
HttpRequestExecutingMessageHandler handler = new HttpRequestExecutingMessageHandler(
"http://localhost:8080/rtp");
handler.setHttpMethod(HttpMethod.POST);
handler.setOutputChannel(rtpResponseChannel);
handler.setShouldTrack(true);
handler.setStatsEnabled(true);
return handler;
}
Below is the POST method in the REST controller class:
#RequestMapping(value = "/rtp", method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<RTPResponse> persistRTP(#RequestBody RTPRequest request) {
System.out.println("In post method " + request);
if (request != null) {
return new ResponseEntity<RTPResponse>(new RTPResponse("12:12:2017", "Test", null, "100", "100"), HttpStatus.OK);
}
return new ResponseEntity<RTPResponse>(new RTPResponse("12:12:2017", "Dummy", null, "Dummy", "Dummy"), HttpStatus.OK);
}
Below is the config of the service activator method:
#Override
#ServiceActivator(inputChannel="rtpResponseChannel")
public void makeCall(ResponseEntity<RTPResponse> message) {
System.out.println("Message: " + message.getBody());
System.out.println(message.getClass().getCanonicalName());
}
I am receiving null in the body of the ResponseEntity object. Which configuration am I missing?
Edit 1:
When I use the setExpectedResponseType(), with the same controller configuration as above.
#ServiceActivator(inputChannel = "rtpRequestPostOperationRequestChannel")
#Bean
public MessageHandler httResponseMessageHandler(MessageChannel rtpRequestPostOperationResponseChannel) {
HttpRequestExecutingMessageHandler handler = new HttpRequestExecutingMessageHandler(
"http://localhost:8080/rtp");
handler.setHttpMethod(HttpMethod.POST);
handler.setOutputChannel(rtpRequestPostOperationResponseChannel);
handler.setExpectedResponseType(RTPResponse.class);
return handler;
}
The RTPResponse object is not wrapped in the ResponseEntity.
I get the error as below:
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1004E: Method call: Method makeCall(rtp.model.RTPResponse) cannot be found on rtp.RTPRequestServiceClient type
Edit 2:
In other words, what configuration should I use on the HttpRequestExecutingMessageHandler to get hold of the message object so that I have the extracted body in the message payload and all the headers to the MessageHeaders, including status.
I tried using GenericMessage being passed to the setExpectedResponseType method of HttpRequestExecutingMessageHandler class.
But it gave me the error as below which is understandable:
Can not construct instance of org.springframework.messaging.support.GenericMessage: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)
But you said yourself - setExpectedResponseType().
You really miss exactly this configuration.
In that case the body of response entity is empty:
private class ResponseEntityResponseExtractor<T> implements ResponseExtractor<ResponseEntity<T>> {
#Nullable
private final HttpMessageConverterExtractor<T> delegate;
public ResponseEntityResponseExtractor(#Nullable Type responseType) {
if (responseType != null && Void.class != responseType) {
this.delegate = new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
}
else {
this.delegate = null;
}
}
#Override
public ResponseEntity<T> extractData(ClientHttpResponse response) throws IOException {
if (this.delegate != null) {
T body = this.delegate.extractData(response);
return ResponseEntity.status(response.getRawStatusCode()).headers(response.getHeaders()).body(body);
}
else {
return ResponseEntity.status(response.getRawStatusCode()).headers(response.getHeaders()).build();
}
}
}
If you don't like to provide a Class<?> for that option, you can consider to use:
/**
* Specify the {#link Expression} to determine the type for the expected response
* The returned value of the expression could be an instance of {#link Class} or
* {#link String} representing a fully qualified class name.
* #param expectedResponseTypeExpression The expected response type expression.
* Also see {#link #setExpectedResponseType}
*/
public void setExpectedResponseTypeExpression(Expression expectedResponseTypeExpression) {
instead. In this case you really can resolve the target expected response type against a requestMessage and also get access to the whole BeanFactory for some other beans calls.

Error page in Spring Boot application with CookieLocaleResolver

I Have a Spring Boot application which has the org.springframework.web.servlet.i18n.CookieLocaleResolver for locale resolver. If there is a invalid language cookie like !en then there will be an exception java.lang.IllegalArgumentException: Locale part "!en" contains invalid characters.
The problem is this exception is not handled by Spring Boot instead it is forwarded to Servlet container. So the default error page of the the container is shown (In my case it is JBoss EAP 6) which will show the stacktrace.
Other exceptions from the controllers are handled properly. For example I have a controller mapping which will throw / by zero error which is handled properly.
I have tried error page configuration in web.xml as follows.
<error-page>
<location>/500</location>
</error-page>
And mapped both /error and /500 to a MVC controller as follows.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.AbstractErrorController;
import org.springframework.boot.autoconfigure.web.ErrorAttributes;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
#Controller
public class CustomErrorController extends AbstractErrorController {
public static final String ERROR_500 = "/500";
private static final String ERROR_PATH= "/error";
#Autowired
public CustomErrorController(ErrorAttributes errorAttributes) {
super(errorAttributes);
}
/**
* Responsible for handling all errors and throw especial exceptions
* for some HTTP status codes. Otherwise, it will return a map that
* ultimately will be converted to a json error.
*/
#RequestMapping({ERROR_PATH,ERROR_500})
public ResponseEntity<?> handleErrors(HttpServletRequest request) {
return ResponseEntity.status(getStatus(request)).body(getErrorAttributes(request, false));
}
#Override
public String getErrorPath() {
return ERROR_PATH;
}
}
But still I'm getting the container's default error page. How to resolve this.
The FrameworkServlet, which processes the request, determines the Locale prior to sending the request through the dispatcher as such an exception thrown when resolving the Locale doesn't get caught in the processDispatchResult and as such doesn't get handled like a normal WebMvc error. For context the FrameworkServlet is extended by the DispatcherServlet which overrides the buildLocaleContext(request) and that in turn calls the CookieLocaleResolver intance.
/**
* Process this request, publishing an event regardless of the outcome.
* <p>The actual event handling is performed by the abstract
* {#link #doService} template method.
*/
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
// Here the locale is determined
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
// here is where the WebMvc processing happens
doService(request, response);
}
catch (ServletException ex) {
failureCause = ex;
throw ex;
}
catch (IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
if (logger.isDebugEnabled()) {
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
}
else {
if (asyncManager.isConcurrentHandlingStarted()) {
logger.debug("Leaving response open for concurrent processing");
}
else {
this.logger.debug("Successfully completed request");
}
}
}
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
DispatcherServlet method for buildLocaleContext()
/**
* Build a LocaleContext for the given request, exposing the request's primary locale as current locale.
* <p>The default implementation uses the dispatcher's LocaleResolver to obtain the current locale,
* which might change during a request.
* #param request current HTTP request
* #return the corresponding LocaleContext
*/
#Override
protected LocaleContext buildLocaleContext(final HttpServletRequest request) {
if (this.localeResolver instanceof LocaleContextResolver) {
return ((LocaleContextResolver) this.localeResolver).resolveLocaleContext(request);
}
else {
return new LocaleContext() {
#Override
public Locale getLocale() {
return localeResolver.resolveLocale(request);
}
};
}
}
I think you also need to mention error-code in the params of your web.xml like below. It works for me.
<error-page>
<error-code>500</error-code>
<location>/500</location>
</error-page>
and then catch it in the controller
#RequestMapping(value = "/500", method = RequestMethod.GET)
public String error500(Model model) {
//System.out.println("Error 500 ");
return "500"; //will return the 500.jsp
}

Create Spring 3 annotation that check Session for object and redirect the user

I'm used to implement custom HandlerMethodArgumentResolverComposite for my projects, but now in some methods I have the repetitive code block
...
if (param != null){
return SiteMap.withRedirect(HOME); // resolves to "redirect:/home"
}
...
Is there a lean way to do this block outside from the controller methods?
Thanks in advance.
Answer to that:
HandlerMethod.getMethodAnnotation(Class<T>) will help a lot :D
https://gist.github.com/dgomesbr/5657473
public class UserRequiredAnnotationInterceptor extends HandlerInterceptorAdapter
{
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
{
if (handler == null)
{
return true;
}
if (((HandlerMethod) handler).getMethodAnnotation(RequiredUser.class) != null)
{
final Object userkey = request.getSession().getAttribute(LoginFilter.CURRENT_LOGGED_USER_ATTRIBUTE);
if (userkey == null)
{
response.sendRedirect(SiteMap.HOME_REDIRECT);
return false;
}
}
return true;
}
}
You could implement an interceptor with a preHandle() method.
The preHandle(..) method returns a boolean value. You can use this
method to break or continue the processing of the execution chain.
When this method returns true, the handler execution chain will
continue; when it returns false, the DispatcherServlet assumes the
interceptor itself has taken care of requests (and, for example,
rendered an appropriate view) and does not continue executing the
other interceptors and the actual handler in the execution chain.
However, since preHandle() doesn't return a String like your controller method does the interceptor implementation would be along the lines of
if (request.getParameter(yourParam) == true) {
return true;
} else (
response.sendRedirect(redirectPath);
return false;
}
Alternatively, you can of course put nearly the same code into a Servlet filter. The main difference is that the interceptor configuration in your MVC .xml files gives you more fine grained control over the "URL patterns" (i.e. your controller methods) the interceptor should be mapped to.
A spring interceptor would be the way to go.

Resources