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.
Related
Is it possible to add some custom validation message to path variable?
I have some GET
#GetMapping("/v2/tw/{id}")
public TwDto getTw(Authentication auth, #PathVariable Long id) {
In case of /v2/tw/someString I'd like to catch error and add some custom error message like "invalid tw ID"... How to do that? In ControllerAdvice add some ExceptionHandler?
For your particular use case, you can use #ExceptionHandler in the Controller or in a #ControllerAdvice class as shown here. For example, I am returning NOT_FOUND error for the sake of it.
#ExceptionHandler({MethodArgumentTypeMismatchException.class})
#ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "this is the reason")
public void handle() {
}
You may not see the reason in the actual error response, until you enable
server:
error:
include-message: always
If you think your #ExceptionHandler is only needed in a Controller class you can keep the method inside the controller. Alternatively you can create a #ControllerAdvice class and put the method there, so that you can reuse across multiple controllers in your application.
However, if you want a more complex validation, I will suggest to keep the id type to String and then cast manually into Long and perform the validation. Doing so you can throw your own RuntimeException and handle different cases.
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.
I am currently setting up a Spring MVC application (version 4.1.4.RELEASE) and I want the application to return a JSON string on a 404 error rather than the default html response. I am using Tomcat 8 as my server. I have what I think should be correct, however it isn't behaving in the manner that I expect. What I'm trying to do is based off of this answer.
public class SpringWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer{
...
#Override
protected void customizeRegistration(ServletRegistration.Dynamic registration){
registration.setInitParameter("throwExceptionIfNoHandlerFound","true");
}
}
and then I have an exception controller (which is different than the question I based my solution off of, however I don't believe that is an issue as I am under the impression that #ControllerAdvice is an acceptable way to manage this based off of the Spring Docs. It looks something like:
#ControllerAdvice
public class GlobalExceptionController{
#ResponseStatus(value=HttpStatus.NOT_FOUND)
#ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public Message handleMethodNotSupported(HttpServletRequest request){
...
}
#ResponseStatus(value=HttpStatus.NOT_FOUND)
#ExceptionHandler(NoSuchRequestHandlingMethodException.class)
public Message handleBadRequest(HttpServletRequest request){
...
}
#ResponseStatus(value=HttpStatus.NOT_FOUND)
#ExceptionHandler(NoHandlerFoundException.class)
public Message requestHandlingNoHandlerFound(HttpServletRequest request){
...
}
...
}
It continues to send back the default response. I know for a fact that it is hitting my customizeRegistration() function because breakpoints stop it, however, any breakpoints that I have in my GlobalException class are not hit. Also, the GlobalException class is within a package that is hit by a #ComponentScan() annotation, so I am fairly confident that it is also being handled by spring.
I assume I'm missing something obvious, any help would be greatly appreciated.
I don't think the return type you're trying to use is supported. Have you tried changing your return value to ResponseEntity or adding a #ResponseBody annotation?
From the docs:
A ModelAndView object (Servlet MVC or Portlet MVC).
A Model object, with the view name implicitly determined through a RequestToViewNameTranslator.
A Map object for exposing a model, with the view name implicitly determined through a RequestToViewNameTranslator.
A View object.
A String value which is interpreted as view name.
#ResponseBody annotated methods (Servlet-only) to set the response content. The return value will be converted to the response stream
using message converters.
An HttpEntity or ResponseEntity object (Servlet-only) to set response headers and content. The ResponseEntity body will be
converted and written to the response stream using message converters.
void if the method handles the response itself (by writing the response content directly, declaring an argument of type
ServletResponse / HttpServletResponse / RenderResponse for that
purpose) or if the view name is supposed to be implicitly determined
through a RequestToViewNameTranslator (not declaring a response
argument in the handler method signature; only applicable in a Servlet
environment).
I am building a Spring boot web app and am using annotations for controller/url mapping.
I have several controllers annotated with #RequestMapping with the url value set (both empty strings and specific URLs) which are working fine e.g.
#Controller
#RequestMapping("/accounts")
class SignInController {
#Autowired PartyService partyService
#RequestMapping(value="", method = RequestMethod.GET )
public String signinPage( Model model) {
Navigating to /accounts renders the sign-in page correctly.
However, if I add a controller with no RequestMapping values e.g.
#Controller
class CustomController {
#RequestMapping
public String transform( Model model ) {
Then any URL I enter that doesn't match any other specific controller is getting handled by this controller (so pages I would expect to 404 all just renderthis page). Is this expected behaviour? I was not expecting this, and as the RequestMapping value defaults to empty and is an antMatcher I wouldn't have thought it would handle all other URLs.
The reason I have this controller with out RequestMapping defined is because I want to also have a SimpleUrlMappingHandler defined with some explicit URLs going to that controller, and if I don't include the #Controller & #RequestMapping annotations to that controller then I get an error about not being able to find the handler method (maybe the problem is that I have mis-understood the implementation details of that).
Should my custom controller be handling all URLs? If so, is there something I can do so it doesnt and only gets called for the explicit SimpleUrlMappingHandler I have defined?
As mentioned in the comments - I just removed the #Controller annotation from the class, and then explicitly defined the controller as a #Bean in my config class and explicitly assigned that to the mapping in the SimpleUrlMappingHandler configuration
I want to register a custom HandlerMethodArgumentResolver that could handle the following #Controller handler method definition
#RequestMapping(method = RequestMethod.POST)
public String createDomain(#Valid Domain domain, BindingResult errors, #RequestParam("countryId") Long countryId) {
I can register my resolver, which just creates a Domain object through request parameters, by overriding addArgumentResolver() from WebMvcConfigurerAdapter. When Spring tries to resolve the Domain parameter, it goes through its list of HandlerMethodArgumentResolver (there are a lot) and picks the first one that supports() it.
In the above example, although my resolver will get called and my Domain argument will get initialized, the #Valid annotation won't have been processed and the resolver for BindingResult, an ErrorsMethodArgumentResolver will fail because it requires a #ModelAttribute, #RequestBody or the #RequestPart argument in the handler method, which I don't have.
If I try to fix it by adding #ModelAttribute
#RequestMapping(method = RequestMethod.POST)
public String createDomain(#Valid #ModelAttribute Domain domain, BindingResult errors, #RequestParam("countryId") Long countryId) {
a HandlerMethodArgumentResolver implementation, ModelAttributeMethodProcessor, will get checked first with supports() and resolve the argument (with #ModelAttribute and #Valid) before my custom resolver. The BindingResult won't fail, but I won't have my custom creation behavior on the Domain instance.
I could just copy-paste the code for validation and adding to model that's in ModelAttributeMethodProcessor, but I was hoping there was an easier way to resolve my parameters and perform validation without adding an object to the model. Is there such a way?
Nice description of the issue that you are facing.
I checked out the code that you have outlined and have come to the same conclusion that you have - there is no built-in way to have both a custom HandlerMethodArgumentResolver as well as #Valid related validation applied at the same time, the only choice is to do what the ModelAttributeMethodProcessor does which is to check if the parameter has a #Valid annotation and call the validation logic related code.
You can probably derive your HandlerMethodResolverArgumentResolver from ModelAttributeMethodProcessor and call super.validateIfApplicable(..) atleast this way the existing code is leveraged.
It's may be too late, but your HandlerMethodArgumentResolver gets WebDataBinderFactory object as last argument, then, to hook up the validation, simply add this to your resolver implementation:
Object resolvedObject = // your logic
if(parameter.hasParameterAnnotation(Valid.class){
binderFactory.createBinder(webRequest,resolvedObject,"resolvedObjectLogicalName").validate ();
}