How to achieve the method of controller that will be requested by client in interceptor of Spring MVC - servlets

I have a requirement of achieving the method of controller in interceptor and the object returned by the method in interceptor.
why?
Because I want to declare the datatype which will return to client using annotation annotated on the method. for example :
#Controller
#Scope("prototype")
#RequestMapping("/hello/")
public class HelloWorld {
#ResponseType(DataType.JSON)
#RequestMapping(value="/{username}")
public UserInfo hellowUser(#PathVariable("username") String username) {
UserInfo userInfo = new UserInfo();
userInfo.setUsername(username);
return userInfo.
}
}
then interceptor:
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throw Exception {
Method method = getRequestedMethod();
Object result = getResultReturnedByTheMethod();
ResponseType responseType = method.getAnnotation(ResponseType.class);
DataType type = responseType.value();
swich(type) {
case DataType.JSON : writeJson(result);
case .......
...
}
}
So, in another words, how can I implement the "getRequestedMethod" and "getResultReturnedByTheMethod" correctly?

Have you tried the Jackson processor? http://jackson.codehaus.org/
It automatically converts JSON to and from the controller. And is supported by Spring MVC.

Related

spring mvc application to navigate flow from controller to jsp or servlet of another application

I am using spring mvc application. I want to redirect from controller or jsp to my second application that was developed in plain servlet and jsps.
How can I navigate flow from one apps servlet/jsps to another apps jsp.
I have used following lines in my controller to navigate:
First:
return new ModelAndView("redirect:/http://localhost:9090/MarathiInput/test.jsp");
Second:
response.sendRedirect("http://localhost:9090/MarathiInput/test.jsp");
Currently my controller is :
#RequestMapping(value = "/transferCertificate", method = RequestMethod.GET)
public ModelAndView get(ModelMap map ,HttpServletResponse response) {
response.sendRedirect("localhost:9090/MarathiInput/test.jsp");
}
and in my jsp i am calling :
Generate TC this link
You have small errors in both tries, but both can be used.
Assuming method controller is declared to return a ModelAndView you can use :
return new ModelAndView("redirect:http://localhost:9090/MarathiInput/test.jsp");
If it is declared to return a String :
return "redirect:http://localhost:9090/MarathiInput/test.jsp";
Alternatively provided the controller method has the response as parameter, you can do the redirect in controller, but as you have already processed the response, the method must return null :
response.sendRedirect("http://localhost:9090/MarathiInput/test.jsp");
return null;
So you could use :
#RequestMapping(value = "/transferCertificate", method = RequestMethod.GET)
public ModelAndView get(ModelMap map ,HttpServletResponse response) {
response.sendRedirect("http://localhost:9090/MarathiInput/test.jsp");
return null;
}
or simply :
#RequestMapping(value = "/transferCertificate", method = RequestMethod.GET)
public String get(ModelMap map ,HttpServletResponse response) {
return "redirect:http://localhost:9090/MarathiInput/test.jsp");
}
But make sure that the link includes the servlet context and servlet path :
Generate TC this link

advise controller method *before* #Valid annotation is handled

I am adding rate-limiting to a restful webservice using Spring MVC 4.1.
I created a #RateLimited annotation that I can apply to controller methods. A Spring AOP aspect intercepts calls to these methods and throws an exception if there have been too many requests:
#Aspect
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class RateLimitingAspect {
#Autowired
private RateLimitService rateLimitService;
#Before("execution(* com.example..*.*(.., javax.servlet.ServletRequest+, ..)) " +
"&& #annotation(com.example.RateLimited)")
public void wait(JoinPoint jp) throws Throwable {
ServletRequest request =
Arrays
.stream(jp.getArgs())
.filter(Objects::nonNull)
.filter(arg -> ServletRequest.class.isAssignableFrom(arg.getClass()))
.map(ServletRequest.class::cast)
.findFirst()
.get();
String ip = request.getRemoteAddr();
int secondsToWait = rateLimitService.secondsUntilNextAllowedAttempt(ip);
if (secondsToWait > 0) {
throw new TooManyRequestsException(secondsToWait);
}
}
This all works perfectly, except when the #RateLimited controller method has parameters marked as #Valid, e.g.:
#RateLimited
#RequestMapping(method = RequestMethod.POST)
public HttpEntity<?> createAccount(
HttpServletRequest request,
#Valid #RequestBody CreateAccountRequestDto dto) {
...
}
The problem: if validation fails, the validator throws MethodArgumentNotValidException, which is handled by an #ExceptionHandler, which returns an error response to the client, never triggering my #Before and therefore bypassing the rate-limiting.
How can I intercept a web request like this in a way that takes precedence over parameter validation?
I've thought of using Spring Interceptors or plain servlet Filters, but they are mapped by simple url-patterns and I need to differentiate by GET/POST/PUT/etc.
I eventually gave up on trying to find an AOP solution and created a Spring Interceptor instead. The interceptor preHandles all requests and watches for requests whose handler is #RateLimited.
#Component
public class RateLimitingInterceptor extends HandlerInterceptorAdapter {
#Autowired
private final RateLimitService rateLimitService;
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (HandlerMethod.class.isAssignableFrom(handler.getClass())) {
rateLimit(request, (HandlerMethod)handler);
}
return super.preHandle(request, response, handler);
}
private void rateLimit(HttpServletRequest request, HandlerMethod handlerMethod) throws TooManyRequestsException {
if (handlerMethod.getMethodAnnotation(RateLimited.class) != null) {
String ip = request.getRemoteAddr();
int secondsToWait = rateLimitService.secondsUntilNextAllowedInvocation(ip);
if (secondsToWait > 0) {
throw new TooManyRequestsException(secondsToWait);
} else {
rateLimitService.recordInvocation(ip);
}
}
}
}
Add the following controller advice in your application.
#ControllerAdvice
public class ApplicationControllerAdvice {
#InitBinder
#RateLimited
protected void activateBeanPropertyAccess(DataBinder dataBinder) {
dataBinder.initBeanPropertyAccess();
}
}
The #RateLimited should call the class RateLimitingAspect. So, after this all the constraints validator will be called.
See if it's feasible for you to implement similar logic for ##AfterThrowing advice as well which will have similar pointcut.

Spring MVC - PropertyEditor not called during ModelAttribute type conversion

Using Spring 3.2.3, I'm trying to implement a simple CRUD controller that handles REST-ful URLs. It relies on a PropertyEditor to convert a path variable to a BusinessService entity by loading it from an application service. Code is as follows:
#Controller
public class BusinessServiceController {
#Autowired
private BusinessServiceService businessSvcService;
public BusinessServiceController() {
}
#InitBinder
public void initBinder(final WebDataBinder binder) {
binder.registerCustomEditor(BusinessService.class, new BusinessServicePropertyEditor(businessSvcService));
}
#RequestMapping(value = "/ui/account/business-services/{businessSvc}", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public ModelAndView update(#ModelAttribute("businessSvc") #Valid final BusinessService businessSvc, final BindingResult result,
final RedirectAttributes redirectAttribs) throws UnknownBusinessServiceException {
ModelAndView mav;
if (result.hasErrors()) {
mav = new ModelAndView("/business-service/edit");
}
else {
businessSvcService.updateBusinessService(XSecurity.principal().getId(), businessSvc);
mav = new ModelAndView("redirect:/ui/account/business-services");
redirectAttribs.addFlashAttribute("message", Message.info("businessService.updated", businessSvc.getTitle()));
}
return mav;
}
}
public class BusinessServicePropertyEditor extends PropertyEditorSupport {
private final BusinessServiceService businessSvcService;
public BusinessServicePropertyEditor(final BusinessServiceService businessSvcService) {
this.businessSvcService = businessSvcService;
}
#Override
public String getAsText() {
final BusinessService svc = (BusinessService) getValue();
return Long.toString(svc.getId());
}
#Override
public void setAsText(final String text) {
final BusinessService svc = businessSvcService.getBusinessService(Long.parseLong(text));
setValue(svc);
}
}
According to SPR-7608, starting from Spring 3.2, #ModelAttribute method argument resolution checks if a path variable by the same name exists (it does here), in which case it tries to convert that path variable's value to the target parameter type through registered Converters and PropertyEditors. This is not what I'm experiencing. When I inspect what ServletModelAttributeMethodProcessor does, it clearly uses the request DataBinder's ConversionService to perform type conversion, which does not consider registered PropertyEditors, and hence BusinessServicePropertyEditor#setAsText is never called.
Is this a configuration problem or an actual bug?
Thanks for your help!
Spring's ConversionService and Converters are replacement for standard Java Beans PropertyEditors.
You need to implement Converter instead of PropertyEditor if this feature is based purely on conversion service.
To register your custom converters in WebDataBinder you might use ConfigurableWebBindingInitializer or #InitBinder method.

In spring mvc 3, how to write a cookie while returning a ModelAndView?

My controller method is returning a ModelAndView, but there is also a requirement to write a cookie back to client. Is it possible to do it in Spring? Thanks.
If you add the response as parameter to your handler method (see flexible signatures of #RequestMapping annotated methods – same section for 3.2.x, 4.0.x, 4.1.x, 4.3.x, 5.x.x), you may add the cookie to the response directly:
Kotlin
#RequestMapping(["/example"])
fun exampleHandler(response: HttpServletResponse): ModelAndView {
response.addCookie(Cookie("COOKIENAME", "The cookie's value"))
return ModelAndView("viewname")
}
Java
#RequestMapping("/example")
private ModelAndView exampleHandler(HttpServletResponse response) {
response.addCookie(new Cookie("COOKIENAME", "The cookie's value"));
return new ModelAndView("viewname");
}
Not as part of the ModelAndView, no, but you can add the cookie directly to the HttpServletResponse object that's passed in to your controller method.
You can write a HandlerInterceptor that will take all Cookie instances from your model and generate the appropriate cookie headers. This way you can keep your controllers clean and free from HttpServletResponse.
#Component
public class ModelCookieInterceptor extends HandlerInterceptorAdapter {
#Override
public void postHandle(HttpServletRequest req, HttpServletResponse res, Object handler, ModelAndView modelAndView) throws Exception {
if (modelAndView != null) {
for (Object value : modelAndView.getModel().values()) {
if (value instanceof Cookie)
res.addCookie((Cookie) value);
}
}
}
}
NB . Don't forget to register the interceptor either with <mvc:interceptors> (XML config) or WebMvcConfigurer.addInterceptors() (Java config).
RustyX's solution in Java 8:
#Component
public class ModelCookieInterceptor extends HandlerInterceptorAdapter {
#Override
public void postHandle(HttpServletRequest req, HttpServletResponse res, Object handler, ModelAndView modelAndView) throws Exception{
if (modelAndView != null) {
modelAndView.getModel().values().stream()
.filter(c -> c instanceof Cookie)
.map(c -> (Cookie) c)
.forEach(res::addCookie);
}
}
}

Spring Web MVC: Pass an object from handler interceptor to controller?

Currently I am using request.setAttribute() and request.getAttribute() as a means to pass an object from a handler interceptor to a controller method. I don't view this as an ideal technique, because it requires that I take HttpServletRequest as an argument to my controller methods. Spring does a good job hiding the request object from controllers, so I would not need it except for this purpose.
I tried using the #RequestParam annotation with the name I set in setAttribute(), but of course that did not work because request attributes are not request params. To my knowledge, there is no #RequestAttribute annotation to use for attributes.
My question is, is there some better way to hand off objects from interceptors to controller methods without resorting to setting them as attributes on the request object?
Use the interceptor prehandle method and session like this:
Interceptor:
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HttpSession session = request.getSession();
String attribute = "attribute";
session.setAttribute("attributeToPass", attribute);
return true;
}
Controller:
#RequestMapping(method = RequestMethod.GET)
public String get(HttpServletRequest request) {
String attribute = (String)request.getSession().getAttribute("attribteToPass");
return attribute;
}
Just to save time for those visiting this page: since Spring 4.3 #RequestAttribute annotation is a part of Spring MVC, so there is no need to create your own #RequestAttribute annotation.
An example using #RequestAttribute:
Interceptor
#Component
public class ExampleRequestInterceptor
implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// Logic to verify handlers, you custom logic, etc.
// Just for illustration, I'm adding a `List<String>` here, but
// the variable type doesn't matter.
List<String> yourAttribute = // Define your attribute variable
request.setAttribute("yourAttribute", yourAttribute);
return true;
}
}
Controller
public ResponseEntity<?> myControllerMethod(#RequestParam Map<String, String> requestParams, #RequestAttribute List<String> yourAttribute) {
// `yourAttribute` will be defined here.
}

Resources