I have two types of controllers in my spring application.
View controllers that forward to views to generate HTML
API controllers that return JSON directly from the controllers
Both the API and View controllers are part of the same spring dispatcher servlet. Spring 3.2 introduced the #ControllerAdvice annotation to allow for a global location to handle exception.
The documentation implies that #ControllerAdvice will be applied to every controller associated with a Dispatcher Servlet.
Is there a way to configure which controllers #ControllerAdvice will apply to?
For example in my scenario I want a #ControllerAdvice for my View Controllers and separate #ControllerAdvice for my API controllers.
For people that will still find this question:
As from Spring 4 ControlerAdvice's can be limited to Controler's with the specified annotations. Take a look at:
http://blog.codeleak.pl/2013/11/controlleradvice-improvements-in-spring.html
(second half of this article) for more details.
UPDATE
I am using spring 4. You can do one of 2 below options.
(1) You can add the packages you want. (Inside those packages you have controllers that you want to follow #ControllerAdvice).
Ex:
#ControllerAdvice(basePackages={"my.pkg.a", "my.pkg.b"})
(2) You can directly add the controller classes you want.
Ex:
#ControllerAdvice(basePackageClasses={MyControllerA.class, MyControllerB.class})
I do not think this is possible now. If you can make the API and View controllers throw different Exception types, then you could define two different #ExceptionHandlers and achieve what you want.
// For handling API Exceptions
#ExceptionHandler(APIException.class) // Single API Exception
#ExceptionHandler({APIException.class, ..., ,,,}) // Multiple API Exceptions
// For handling View Exceptions
#ExceptionHandler(ViewException.class) // Single View Exception
#ExceptionHandler({ViewException.class, ..., ...}) // Multiple View Exceptions
You could use aop to translate the Exceptions coming out of APIs to a standard APIException. See this thread on spring forums.
Hope it helps.
Your exceptions should not dictate the content-type of your response. Instead check the request's Accept header for what the browser expects.
#ExceptionHandler(Throwable.class)
public #ResponseBody String handleThrowable(HttpServletRequest request, HttpServletResponse response, Throwable ex) throws IOException {
...
String header = request.getHeader("Accept");
if(supportsJsonResponse(header)) {
//return response as JSON
response.setContentType(JSON_MEDIA_TYPE.toString());
return Json.stringify(responseMap);
} else {
//return as HTML
response.setContentType("text/html");
}
#ExceptionHandler(value=Exception.class)
public ModelAndView error(Exception ex) {
return new ModelAndView("redirect:/error/m");
}
...//ErrorController
#RequestMapping(value = "/m", produces="text/html")
public ModelAndView error()...
#RequestMapping(value = "/m", produces="application/json")
#ResponseBody
public Map errorJson()...
Related
I am working on a web application using Spring MVC architecture. I have a controller method that will be called by an ajax post(). The request mapper in the controller has a ".html" (meant for some cleanup task)for which the Spring Internal view resolver is trying to find a matching .JSP file and throws a 404 Not Found error. I donot want to create a .JSP which is not useful in my case. I need some help to determine if there is any setting in Spring Context xml to let the view resolver ignore this url and not to look for its .JSP file.
#RequestMapping(value = "/clearSession.html")
public void unloadDAOSession(HttpServletRequest request) {...}
the InternalViewResolver is looking for clearSession.jsp which throws a 404 Resource Not found. I dont want to create a JSP which is of no use.
Are there any application Context settings in the view resolver to ignore this ".html" file and not to look for its ".jsp"?
Any help is appreciated.
Even though the return type is void it only means that the view name will be resolved based on the URL as you have seen.
One way to avoid view resoluion is to annotate the response with #ResponseBody or
bypass the view resolver by tweaking your return type to something like
#RequestMapping(value = "/clearSession.html")
public ResponseEntity<String> unloadDAOSession(HttpServletRequest request) {
...
return new ResponseEntity<String>("OK",HttpStatus.NO_CONTENT);
}
This way instead of forwarding to a view, your just adding a header and an empty body to the response stream
In case of use of ajax and traditional controller, the best approach is write the your controller for answare to the page rendering as you had done adn then write a rest end-point layer for answare to ajax request.
I just give you some piece of code for clearing what I want say:
Controller layer:
#RequestMapping(value = "/yourPage")
public void yourPage(Model mode) {...}
Rest layer:
#RequestMapping(value = "/yourAjax")
public ResponseEntity<String> yourAjax(#RequstBody YoutDTOClass postBody) {
...
return ResponseEntity.created(...).build();
}
class YoutDTOClass{
// your properties
....
}
with this schema you separate the service layer for your ajax request and the classic web layer for serving the your page.
Just an hint don't use a pattern like /yourPage.html but you should prefare a pattern like /youtPage.This balically for banefit of the view resolver abstraction of Spring, you should let to springMVC to resolve the actual view.
Even for the ajax you should use the accept header for ask the correct rappresentation of your resource and the ResponseEntity should be used for return a 201 Http status for say to your browser that the your resource was created. remember that a better approach should prefere the use of http method in REST world and then for delete a session you should be prefare delte method for delete a session and put http method for update the sate of the session.
I hope that this can help you
I send along every REST call my custom header, which is (for instance) an authorization token. This token remains the same, as I do not need high security in this case. Can I use some simple way how to check every request coming to RestController whether it has this token among headers?
I can see a few ways:
Coding a #ModelAttribute in a #ControllerAdvice class, like this
#ControllerAdvice
public class HeaderFetcher {
#ModelAttribute
public void fetchHeader(#RequestHeader Optional<String> myHeader, Model model) {
if header is present
model.addAttribute("myHeader", the header);
else
throw exception
}
}
Haven't tried this, though.
Using a filter
Using AoP
I am using Spring 4.0.6 in a servlet application. I have an abstract base controller with some general methods for all my controllers to use.
One of these methods is a redirect. I want to have a method with signature
redirect(String path)
To send a redirect, I am using
response.sendRedirect(response.encodeRedirectURL(path));
As I would like to keep method signatures short and clean, I need to get access to the response object inside the superclass method.
In order to do this, I've followed a suggestion found online, and defined a servlet filter with a ThreadLocal HttpServletResponse.
public class ResponseFilter extends OncePerRequestFilter {
private static final ThreadLocal<HttpServletResponse> responses = new ThreadLocal<HttpServletResponse>();
public static HttpServletResponse getResponse() {
return responses.get();
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
responses.set(response);
} finally {
try {
filterChain.doFilter(request, response);
} finally {
responses.remove();
}
}
}
}
As I am using Spring security with a Java configuration, I'm adding this filter in my WebSecurityConfigurerAdapter subclass:
.addFilterAfter(rf, SwitchUserFilter.class)
Note that I have also tried adding the filter as first in the filterchain, and that I have tried using an Interceptor instead. All with the same results.
I have compared hashcodes on the response objects, and near as I can tell, the hashcodes match, but the redirect seems to be ignored. I have also looked at object ids on breakpoints in Eclipse, and there again, I have a match. The symptom is that the spring DispatcherServlet enters processDispatchResult and seems to think it needs to resolve a view. That view does not exist, as I expect to do a redirect:
javax.servlet.ServletException: File "/WEB-INF/views/application/redirecttest.jsp" not found
I have noticed that, if I add the response object back in my requestmapping controller method signature, the superclass redirect seems to work (even though I do not use the controller method response object at all).
Unfortunately, this behavior is reproducible both on a Mac and on Linux. I use Tomcat 7 as container.
Your filter should work just fine, but the problem you're facing is another. If you are using views (as you appear to do in the example) you need to return a redirect view from your controller in order to force a redirect; just instructing the response object to redirect won't work because Spring MVC infrastructure will try to do its thing (i.e. view resolution) before the Response is returned to the Servlet container.
For instance, if you use the convention to return the view name as a String from your controller method, you need to do the following in your controller:
#RequestMapping("/redirectTest")
public String redirectTest() {
return "redirect:http://www.example.com";
}
I would like to implement a generic controller with one or two methods that react to any GET request. I am trying to simplify this to the point where I can return byte (image etc.) or character based (XML, CSS) without having to map each content type and put a RequestMapping in for each.
The app must be abel to handle any request with any content type.
My dispatcher is currently set to handle all requests via /.
The couple of attempts I have made so far throw ambigious handler errors, or the mapping doesn;t work to the point where text is sent back as byte[] or the other way around.
Has anyone made anything like this work ?
Regards,
Andy
You can have a controller like so
#Controller
public class YourController {
#RequestMapping("/*")
public String doLogic(HttpServletRequest request, HttpServletResponse response) throws Exception {
OutputStream out = response.getOutputStream();
out.write(/* some bytes, eg. from an image*/); // write the response yourself
return null; // this is telling spring that this method handled the response itself
}
}
The controller is mapped to every url and every http method. Spring has a set of accepted return types for its handler methods. With String, if you return null, Spring assumes you've handled the response yourself.
As #NilsH commented, you might be better off using a simple servlet for this.
The below is my sample controller.
#RequestMapping(value = "/validate", method = RequestMethod.POST)
public String validatePage1 (#ModelAttribute("page1")
Page1 pg1, BindingResult result) {
System.out.println("Value1:" + pg1.getVal1() +
"Value2:" + pg1.getVal2());
return "page2"; // I don't want to take any action (page navigation) here
}
#RequestMapping("/page1")
public ModelAndView pageShow() {
return new ModelAndView("page1", "command", new Page1());
}
Now the question is, I don't want to take any action in the client side when the method (validatePage1) is called by Spring framework, how to do?
Actually I have loaded all required pages in my client side at loading time (to avoid repeated page load), so I dont want to take any page navigation action in the client side, I just want to do the 'data binding' to complete my business logic in server side.
When I return "" empty string in "validatePage1()", Spring framework throws exception " Request processing failed; nested exception is org.apache.tiles.definition.NoSuchDefinitionException:" since I am using tiles, I have to remove tiles configuration later since I am loading all files at first loading itself.
You can set the method to return void and annotate it with #ResponseBody, as suggested here. No need to deal with HttpServletResponse etc.
Straight from the documentation:
Supported method return types
The following are the supported return types:
[...]
void if the method handles the response itself (by writing the response content directly, declaring an argument of type ServletResponse / HttpServletResponse 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).