Spring GET and POST mappings in separate classes - java

We are attempting to separate our GET and POST #RequestMapping methods in our Spring controllers into two separate classes.
The reason is that we want the POST calls to have an exception handler which serializes responses as JSON payloads, while the GET calls should be bubbled up through the Spring stack.
However, when we attempt to separate these, we receive errors suggesting that the mappings are being registered twice:
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping#0' defined in OSGi resource[classpath:/dispatcher-servlet.xml|bnd.id=21|bnd.sym=com.company.application]: Initialization of bean failed; nested exception is java.lang.IllegalStateException: Cannot map handler 'settingsController' to URL path [/settings.html]: There is already handler of type [class com.company.application.controller.SettingsModelAndViewController$$EnhancerBySpringCGLIB$$54324809] mapped.
Is it possible to separate GET and POST request mappings into two different classes? Basically we want (excuse the pseudo-naming conventions):
class PostHandler {
#ExceptionHandler
public void handleException(...) { // Serialize to JSON }
#RequestMapping(value = "/settings.html", method = RequestMethod.POST)
public void saveChanges() { ... }
}
class GetHandler {
#RequestMapping(value = "/settings.html", method = RequestMethod.GET)
public ModelAndView getSettings() { ... }
}
But currently are unable to find a way around Spring's double-mapping complaints.

Looking at the design and code for DispatcherServlet that routes a URL to a Controller (actually to a HandlerAdapter interface), it certainly seems possible, but not easy and not by existing HandlerMapping classes (look at existing classes implementing this interface at https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/HandlerMapping.html). You would have to write a HandlerMapping class (existing handler mappings' code can guide you with this), which would return the right controller based on the URL and HTTP method and configure it (this link should help with HandlerMapping configuration: http://www.baeldung.com/spring-handler-mappings). None of the current HandlerMapping classes look at the HTTP method when choosing a controller for a URL.
You may be able to tweak the GET and POST request mapping, by adding let's say a wildcard to one of the HTTP method handlers (e.g. How do I set priority on Spring MVC mapping?), but not by using the exact same URL in 2 different controllers.

Related

How the Spring MVC #Controller's handler methods' parameters are automatically resolved during run-time?

How does the DispatcherServlet (or any other bean, supporting Spring MVC infrastructure), dynamically, resolve the signature of the handler method of the #Component instance, and how does it know, what parameters/types are expected, in which order, in that method?
If I have a "#RequestMapping-ed" handler method, in my #Controller instance, defining any version of its signature:
public String f() {...}
public String f(Model model) {...}
public String f(HttpServletRequest, Model model) {...}
public String f(Model model, HttpServletRequest) {...}
public String f(SomeEntity se, Model model, HttpServletRequest, AnotherModel am) {...}
//so on..
would work fine, disregarding of parameter number, types, and order, which, eventually, are supplied with corresponding arguments, again - disregarding of their number, order and types.
There should be quite a work going underneath, to correctly instantiate expected arguments, pass them in a proper order, maybe even do some type-casting, if needed.. an so on, but I don't grasp the fundamentals of this.
I looked up the corresponding sources from spring-webmvc module, and sources of the DispatcherServlet; however, haven't got a firm grasp of the underlying mechanism.
I can guess, that some BeanPostProcessors could be involved.. doing reflective accesses, and so on.. but even in this case, as I'm not certain on what's happening, I would very much appreciate any valuable input.
In Spring MVC there are three implementations of HandlerMapping: BeanNameUrlHandlerMapping, SimpleUrlHandlerMapping and ControllerClassNameHandlerMapping.
You can read more about this subject here:
https://www.baeldung.com/spring-handler-mappings
I've not used Spring in years, but this post gives a pretty good step-by-step outline of the spring mvc request lifecycle
When the DispatcherServlet receives a request, it iterates over this list until it finds a matching handler object for the request in question. For simplicity, let's consider only RequestMappingHandlerMapping.
A bean of this type stores a mapping of #RequestMapping annotated methods (the actual Method object retrieved with reflection) stored as a HandlerMethod instances and wrapped in RequestMappingInfo objects that hold mapping data for matching the request, ie. URL, headers, and request parameters.
The DispatcherServlet retrieves the best matching HandlerMethod from these and any corresponding HandlerInterceptor instances which you may have registered. It retrieves these as a HandlerExecutionChain object. It will first apply any pre-handling by HandlerInterceptors. It will then try to invoke your HandlerMethod. This will typically (but not always) be a #RequestMapping annotated method inside a #Controller annotated class. This produces what Spring calls a dispatch result. The DispatcherServlet then applies post-handling by the HandlerInterceptors.

Why doesn't DispatcherServlet invoke my HandlerInterceptor?

I know that in JavaEE, filters can intercept any request to a servlet. But Interceptors in Spring MVC are not exactly the same. If you look at the diagram below, you will see that Interceptors come after Dispatcher Servlet.
Let me give you an example before I ask my question.
I have a controller, which has 2 methods in it that are mapped to two different requests. One accepts GET requests and other accepts POST requests. Now if I add an interceptor in my web application, that interceptor will sit before Controller. Meaning that before controller method is hit, first a request will hit my interceptor's preHandle method.
Now say that in my app, two controllers methods look like this:
#Controller
public class myController{
#RequestMapping(value = "/test", method = RequestMethod.GET)
public String test1(){
return "abc";
}
#RequestMapping(value = "/login", method = RequestMethod.POST)
public String test1(){
return "xyz";
}
And lets say I have a simple interceptor like this:
public class URLInterceptors extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("REQUESTED SERVLET PATH IS: " + request.getServletPath());
return true;
}
}
Now, if I make a GET request to /test, my interceptor is hit and it prints the servlet path, but when I make a GET request to /login, I know it will fail because my method that handles /login mapping accepts only POST requests, however before it throws '405 Request method 'GET' not supported' error, it should at least hit first my interceptor? It doesn't. I I don't want to change POST to GET. So the question is why?
Part of this is explained in
Why does Spring MVC respond with a 404 and report "No mapping found for HTTP request with URI [...] in DispatcherServlet"?
In summary, the DispatcherServlet attempts to find an appropriate handler for your request by using a HandlerMapping (see your graphic). These handlers are actually adapters that wrap the actual handler method (a #RequestMapping annotated method in this case) with the interceptors you've registered. If this handler is found, then the DispatcherServlet can proceed, invoke interceptors, and, if required, invoke your handler method.
In your case, because your #RequestMapping is restricted to POST requests and your request is a GET, the DispatcherServlet fails to find an appropriate handler and therefore returns an error before it's had a chance to invoke any interceptors.
Note that the javadoc states
A HandlerInterceptor gets called before the appropriate HandlerAdapter
triggers the execution of the handler itself.
but your DispatcherServlet never found a handler to begin with.
You might want to consider using a Servlet Filter instead.

what's difference between Controller and Handler in Spring MVC?

The documentation of Spring MVC sometimes says about "handlers" or "request handlers". For instance, http://docs.spring.io/autorepo/docs/spring/4.0.4.RELEASE/javadoc-api/org/springframework/web/servlet/handler/SimpleUrlHandlerMapping.html says:
Implementation of the HandlerMapping interface to map from URLs to request handler beans
And sometimes it says about controllers. For instance, there is an interface called org.springframework.web.servlet.mvc.Controller ( http://docs.spring.io/spring-framework/docs/2.5.x/api/org/springframework/web/servlet/mvc/Controller.html ).
My question is: are Controllers and Handlers the same?
Generally speaking, a Controller is Handler, but a Handler doesn't have to be a Controller.
For example, HttpRequestHandler, WebRequestHandler, MessageHandler are all handlers which can work with the DispatcherServlet. ( (#)Controller is a handler for executing a web request and returning a view.)
Shortly, Handler is just a term, it is neither a class nor interface. And it is responsible for executing the Mapping.
A Controller is a specific type of Handler but not all Handlers are Controllers.
To execute a type of Handler there is a HandlerAdapter and for each type of Handler there is a different HandlerAdapter. You have Controller and #Controller, HttpRequestHandler and also a plain Servlet can be a Handler. Or if you have some custom things you can even implement your own.
Handler is a inclusive i.e. covering all the services details.
Controller is an an exclusive implementation.
In Spring we have the following different types of handlers:
HandlerMapping: The HandlerMapping strategy is used to map the HTTP client request to some handler controller(or controllers) and/or method. This is done based on the request URL and the HTTP method, but may also include the request parameters, request headers, or other custom factors.
For example: DefaultAnnotationHandlerMapping, SimpleUrlHandlerMapping, BeanNameUrlHandlerMapping.
HandlerAdapter: The DispatcherServlet uses a HandlerAdapter to invoke a method. Which is decouples the DispatcherServlet from controller implementations classes.
For example: AnnotationMethodHandlerAdapter, HttpRequestHandlerAdapter, RequestMappingHandlerAdapter, SimpleControllerHandlerAdapter, SimpleServletHandlerAdapter

Spring MVC custom authorization/security for controller methods

Looking to create a custom implementation to authorize requests.
I am evaluating options to avoid using annotations on every method in the controller.
Instead, I am exploring the possibility centralizing this feature via a filter that checks if the logged in user has the required permission for the URL
We are using Spring 3.2.5.RELEASE
I intend to create a database table that has a mapping between a permission and the relevant URL.
I am looking for a way to get the request mapping information for the current URL.
For E-g :
In my database, I have:
URL=/view/{id} , permission=VIEW_USER
If the current URL is :
/app/user/view/1
, a method annotated with
#RequestMapping(value = "/view/{id}", method = RequestMethod.GET)
will be invoked. But before this happens, I need to verify if the logged in user has the permission to view user details.
In my filter, I can get the current URL (/app/user/view/1) , how do I get the corresponding mapping (/view/{id}) ? Is there a mechanism to match a URL to its corresponding MVC request mapping ?
I have looked/am looking at related posts on SO and also looked at Spring code but am yet to find a way.
If you want to do it that way, you could register MVC interceptor instead of servlet filter.
Create a class that extends HandlerInterceptorAdapter and in preHandle method you will have access to controller's method and it's annotation. Prehandle method of your interceptor could look something like this:
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod method = (HandlerMethod) handler;
if (method.getMethod().isAnnotationPresent(RequestMapping.class)) {
RequestMapping rqm = method.getMethodAnnotation(RequestMapping.class);
String[] urlMappings = rqm.value();
//do security logic
}
}
...
}
Then you need to register the interceptor. If you use xml config it's done like this:
<mvc:interceptors>
<bean class="com.example.MySecurityInterceptor" />
...
</mvc:interceptors>
Please note that your approach will be difficult, you'll need to handle all the request mapping cases that spring supports. For example, #RequestMapping that's annotated on class level. Or #RequestMapping annotated on parent class of the controller etc..

Spring #Controller Separating GET and POST mappings

I am using Spring MVC with annotation configuration. I have a controller class for handling HTTP GET calls:
#Controller
#RequestMapping("/form")
public class FormController {
#RequestMapping(value = "/{table}/{identifier}/edit", method = RequestMethod.GET)
public ModelAndView getEditView(ModelMap map, #PathVariable String table, #PathVariable Object identifier) {
//generate the view for this record
}
and a Controller for processing form submits on that URL
#Controller
#RequestMapping("/form")
public class FormSaveController {
#RequestMapping(value = "/{table}/{identifier}/edit", method = RequestMethod.POST)
public ModelAndView saveView(WebRequest request, #PathVariable String table, #PathVariable Object identifier) {
//save the updated values and redirect to view
}
When I attempt to startup my container, spring complains
Caused by: java.lang.IllegalStateException: Cannot map handler 'FormSaveController' to URL path [/form/{table}/{identifier}/edit]: There is already handler of type [class com.company.web.FormController] mapped.
This seems to indicate what I'm trying to do is not supported in Spring. The reason I am trying to separate the controller for generating the form from the controller saving the form because I am using Springs #ExceptionHandler to handle any runtime exceptions that occur, and I would like to handle an exception for displaying the view differently than an exception for saving a record.
Is there a different way of handling what I am trying to do (utilize Springs #ExceptionHandler annotation for specific kinds of request?)
Have you tried using in the same Class? I think that would work. If you wish to use ExceptionHandler then try HandlerExceptionResolver
The reason I am trying to separate the controller for generating the form from the controller saving the form because I am using Springs #ExceptionHandler to handle any runtime exceptions that occur, and I would like to handle an exception for displaying the view differently than an exception for saving a record
I would imagine that your view template engine would throw exceptions of a different type hierarchy than exceptions encountered while saving records in your datastore. It may be easiest to place these methods in the same class, and then just address your #ExceptionResolver concern by mapping exceptions of the view engine's type one way, and DB exceptions another.

Categories