I'm developing a REST webservice with Spring MVC and I've implemented a custom annotation in order to annotate controller methods with it. This annotation may include a SpEL expression which I must evaluate considering controller method argument values. So, my idea is to implement a Spring MVC interceptor for this but the parameter HandlerMethod in the preHandle method is just a way to identify the method and does not provide access to controller method argument values. So, the only approach I can think of is to develop a Spring AOP aspect and intercept all the calls to annotated methods. By the way, I need access to the request, so if I go by the AOP way, all the annotated methods should include an argument with the request.
So, my question is: Is there any way to access the method argument values from thr Spring MVC interceptor or should I go the Spring AOP way?.
Thanks in advance.
You cannot use the controller method parameter in the preHandle method of an interceptor, because at the time of calling it, the parameters of the controller method have not been constructed (except for request and response).
So you will have to go the AOP way (do not forget to implement a method in your controllers ...) like explained in JavaBond answer. But thanks to spring framework, you can avoid that all the annotated methods should include an argument with the request. RequestContextHolder.getRequestAttributes() gives you a RequestAttributes object. If you know that your request is a HttpServletRequest, you can cast it to a ServletRequestAttributes and then access the native request via the getRequest() method :
RequestAttributes reqAttr = RequestContextHolder.getRequestAttributes();
HttpServletRequest req = ((ServletRequestAttributes) reqAttr).getRequest();
You should go the AOP way.
Write an Around advice against your custom annotation. The around advice should have a ProceedingJoinPoint argument. Using this you can get the annotated methods arguments values via proceedingJoinPoint.getArgs()
Sample advice shown below
#Around("#annotation(yourCustomAnnotation)")
public Object arooundAdvice(ProceedingJoinPoint joinpoint,
YourCustomAnnotation yourCustomAnnotation) throws Throwable {
Object args[] = joinpoint.getArgs();
// iterate over the args[] array to get the annotated method arguments
return joinpoint.proceed();
}
Related
I have a method annotated with #PreAuthorize(...) with some logic that goes away and queries an API for some information about what the user can view. However, I have this endpoint that I need to add this #PreAuthorize annotation into which receives in a more "complex" object which I want to transform slightly (the object contains an array that is some cases I want to add/remove data from).
#PostMapping("/search")
#PreAuthorize("#Service.isAuth(#searchParam)")
public ResponseEntity<Response> search(SearchParams searchParam) {
return service.getSearchResult(searchParam);
}
Is there a way I can modify searchParam inside the #PreAuthorize annotation then have it passed into the method body, I know that this is probably is not the correct way to do this and maybe isn't something that #PreAuthorize wasn't designed for but is there any way of doing this even with a different type of annotation. Obviously worst case I can move the logic into the method body but I would prefer to use an annotation-based solution like #PreAuthorize offers if possible. Thanks for any help even links to other relevant things would be useful I've not found much on google related to this.
I think the best solution is to make a handler/interceptor and then annotate it with #PreAuthorize. So I think you are in the right track but you need to make sure that you modify your code to implement the HandlerMapping interface to create the interceptor and then override the prehandle method. After you need to annotate it with #PreAuthorize programatically. The last thing will be to use a wrapper to modify the HttpWrapper, it cannot be done manually. Here links to the relevant resources in order:
Creating a Handler/Interceptor: https://www.baeldung.com/spring-mvc-handlerinterceptor
Using PreAuthorise in the interceptor: How can I require that all request handlers in my Spring application have #PreAuthorize
To modify the HttpServlet request you will need a wrapper: How to modify HttpServletRequest body in java?
Have a try, hopefully that works.
Snippet of code taken from second link uses a programatic PreAuthorize rather than annotation:
public class PreAuthorizeChecker implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod hm = (HandlerMethod) handler;
PreAuthorize annotation = AnnotationUtils.findAnnotation(hm.getMethod(), PreAuthorize.class);
//TODO use the technique shown on the third link to wrap and modify the HttpServletRequest
if (annotation == null) {
// prevent access to method wihout security restrictions
throw new RuntimeException("Rights are not defined for this handler");
}
}
return true;
}
.....
I recently stumbled upon some code that I had not seen in this form before. Maybe someone here can help me understand better what's going on.
Namely, I found a method annotated both with #RequestMapping and #ExceptionHandler. I thought that the former were for handling requests, while the latter were for handling exceptions, so I would have thought one normally uses either of both annotations, but not both at the same time.
I found the code snippet here: https://github.com/shopizer-ecommerce/shopizer/blob/2.5.0/sm-shop/src/main/java/com/salesmanager/shop/store/api/exception/RestErrorHandler.java#L24
The code snippet is:
#RequestMapping(produces = "application/json")
#ExceptionHandler(Exception.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public #ResponseBody ErrorEntity handleServiceException(Exception exception) {
log.error(exception.getMessage(), exception);
ErrorEntity errorEntity = createErrorEntity(null, exception.getMessage(),
exception.getLocalizedMessage());
return errorEntity;
}
I have two questions:
According to the Spring documentation on #RequestMapping, un-annotated method parameters (that are not of some special type) of a #RequestMapping method are implicitly annotated with #ModelAttribute (see "Any other argument" at the end of the table under the above link). So in the above code snippet, is the Exception parameter implicitly annotated with #ModelAttribute as well? And if yes, does that make sense?
Can it generally make sense to annotate a method with both #RequestMapping and #ExceptionHandler (e.g., to handle both requests and exceptions), or would that be bad form?
good question.
I would say try this. on a controller, take two methods. on one method use just RequestMethod and write a code by accepting a model attribute from page.
On this method, create a scenario for a NullPointerException.
On method 2, annotate both RequestMapping and ExceptionHandler. And you can see whether you are getting the request, response with ModelAttributes from method one to method 2.
if yes, then this would help us evaluate the exception and handle invalid scenarios where we would need the model attribute values.
Also as per the explanation that you have pasted above, ModelAttribute is implicit for RequestMapping, not for all annotations on a controller method.
Please let us know.
I have a Spring RestController with an endpoint consuming JSON. It converts the JSON to an object, and validates the fields (using bean validation):
#RequestMapping(method = RequestMethod.POST, consumes = "application/json")
public ResponseEntity<?> myMethod(#Valid #RequestBody MyEntity e) {
...
}
I'd like to intercept the invocation of this method only after the conversion and validation has taken place to have access to MyEntity and possibly abort execution.
Using Spring interceptors, I can only intercept the request before it reaches the method - so I don't have access to the validated bean. I can use Spring AOP and add a pointcut, but is there a way of aborting the execution nicely – ideally without throwing an exception?
This functionality is outside of the business logic of this method, and is temporary – so I want to separate it.
A possible solution is to create a bean proxy between the Spring's proxy object and an object of your original class. To do that you need:
Implement your own BeanPostProcessor.
Spring will call its postProcessBeforeInitialization() method right after it has instantiated an object of your class, but before wrapping and initialising it. In this method, identify which beans must be provided with your functionality. It's often done by means of your custom annotations on methods or classes.
Create your own proxy for the beans from step 2. You can use cglib or something more modern for that. Your proxy will make all the checks you need and then call the parent's method if everything is ok, or just silently return from the method if something is wrong.
Return your proxy from postProcessBeforeInitialization(), it will instruct Spring to use it instead of the original bean.
As the result, Spring will create proxy of your proxy, not of the original object. When a request arrives, Spring will do the validation and send it to your proxy. Your proxy will make all your custom checks and decide on whether to send it further to your original class or to return immediately.
We've got a couple of #RequestMapping annotated controllers methods that do stuff. They each have more than 1 argument that we would like to be bound. For example:
#RequestMapping(value = "/a/b", method = RequestMethod.GET)
#ResponseBody
public Response getAB(ARequest aRequest, BRequest bRequest) throws ServiceException {
...
}
This obviously works fine. However, one quirk in our requirements is that we get request parameters that contain underscores. For example; on the endpoint above we'd get a request that looks like: http://x:8000/a/b?req_param=1. This causes default Spring binding to fail, because in our objects we'd like to have bound we define req_param camel-cased (reqParam) rather than use underscores in our Java code.
To solve this we have implemented our own annotation (#Camelize) and our own custom WebArgumentResolver, which we've registered in the Spring context. We then annotate our controller method arguments with #Camelize, which causes our custom WebArgumentResolver to step in and correct the binding. This also works fine.
Now the issues arise, more specifically it would appear that when we want to also validate our arguments this fails. For example, continuing with the example above:
#RequestMapping(value = "/a/b", method = RequestMethod.GET)
#ResponseBody
public Response getAB(#Camelize #Valid ARequest aRequest, Errors aRequestErrors, BRequest bRequest) throws ServiceException {
...
}
This fails with the exception: java.lang.IllegalStateException: Errors/BindingResult argument declared without preceding model attribute. Check your handler method signature!.
It would appear that we cannot use our custom WebArgumentResolver and validate using #Valid at the same time. We have narrowed it down to the custom WebArgumentResolver, in the sense that as soon as this doesn't return UNRESOLVED, Spring MVC will just not bother with validation and throw the exception above.
Can anyone confirm that this is the expected behaviour?
Is there a work-around for this? Currently we're thinking about removing the custom WebArgumentResolver and replacing it with a filter, but this seems very intrusive for what we need.
Thanks!
Edit: we're using Spring 3.0.5.
I have a library method Common.addTheUsualStuffToTheModel(model) that needs to add various attributes to the model in every controller method in my app.
#RequestMapping(value = "/everypath", method = RequestMethod.GET)
public final String everyHandler(ModelMap model)
{
model = Common.addTheUsualStuffToTheModel(model);
return "everyPage";
}
So far I have been adding this same line to every handler method:
model = Common.addTheUsualStuffToTheModel(model);
But I'm afraid this is not consistent with the principle of "write once, use everywhere".
How do I avoid repeating this code in every handler?
You can use an interceptor and <mvc:interceptors> to do that
In your interceptor you can add anything as request attribute (which is in fact where the model attributes go). The interceptor code is executed before or after each method (that matches the interceptor mapping).
If you don't necessarily need the model to be populated before the controller method, in the postHandle method you get the ModelAndView object.
What about specifying #ModelAttribute annotated reference data provider methods. If you had a base class for all of your controllers, and that base class had #ModelAttribute annotated methods then I believe that data would be available in the model for all views handled by those controllers. Have a look at 15.3.2.8 in the Spring docs.