I recently added AOP with aspectJ and spring-aop to my existent spring project. The goal was to actually intercept controller calls to modify the response they send back, in order to bind some values to this response I didn't want to add manually to each and everyone of my controllers, for example the expiration date of the actual token used by the end-user (which I wasn't even able to showcase within my controller in any case). I actually managed to get it working until I started my unit tests :
In my unit tests I call directly my controller methods using Reflection feature from java, then replicate usual process (calling the filter chain, pre handler and post handlers, and the controller method itself which is first manually validated using spring validator when annotation #Valid is present on one of my parameters. All this process works fine and gets executed properly). The problem is that now that the controller method is intercepted by spring-aop, it's mentionned as coming from the proxy controller created, and all of my parameters annotations disapear. Here is a controller example :
#Override
public ResponseEntity<Object> editPassword(#Valid #RequestBody PasswordEditForm passwordEditForm, HttpServletRequest request) {
return factorizedUserBaseController.editPassword(passwordEditForm, request, User.class);
}
the parameter PasswordEditForm has the annotation #Valid so in my test cases it was first validated before any other step, but now as I double checked it, the #Valid annotation is not present on the proxy method, and therefore the parameter doesn't get validated, any clue for how to fix this and make my parameters annotation still understandable from my test point of view?
Note : when running the spring through mvn spring-boot:run, parameters with #Valid annotation gets correctly validated and then goes to my error handler method properly.
Problem Solved : from several other stackoverflow posts I understand that CGLIB (aop proxy lib used by Spring) doesn't support annotations. ( see Retain annotations on CGLIB proxies?). But my problem wasn't here, I was literally sure I was finding the method using the controller class itself (the one I coded) but what I was wrong about is that I was giving the controller instance as a parameter to some other parts of my code which in turn would use this controller class to find the method which of course wasn't working because thanks to Spring proxies, it wasn't anymore my controller itself but a proxy class extending my own controller class. Instead, I just had to replace :
Class<?> controllerClass = controllerInstanciationContainer
.getController()
.getClass();
with
Class<?> controllerClass = controllerInstanciationContainer
.getController()
.getClass()
.getSuperclass();
Related
I'm on learning phase of Spring boot
I've code where its written like below to handle exception in whole application. Not sure how its working, but I have NoDataFoundException class in code and its being used at place where no data found issues are happening.
#ControlAdvice
class ControllerAdvisor {
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(NoDataFoundException.class)
#ResponseBody
public ResponseEntity<Object> handleNodataFoundException(
NoDataFoundException ex, WebRequest request) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("timestamp", LocalDateTime.now());
body.put("message", "No cities found");
return new ResponseEntity<>(body, HttpStatus.NOT_FOUND);
}
Want to know, how and when handleNodataFoundException method automatically gets called when NoDataFoundException instance gets created ?
Does spring calls this method handleNodataFoundException on the basis of #ExceptionHandler(NoDataFoundException.class) which is bind to method itself and moreover irrespective of name of the method ?
how spring looks for parameters required for above method ? what if it has more parameters in it ?
This is done by proxying (Proxy.class).
Proxying is a mechanism in which a kind of pseudo class is created dynamically and mimics your own class (same methods) and is used to intercept all the method calls. This way it can act before and after the method calls as it please, and in the middle call the real method that you developed.
When you create a #Service in Spring, or a #Stateless in EJB, you never actually create the instances, you delegate the instance creation on the framework (Spring or EJB). Those frameworks proxy your classes and intercept every call.
In your case, and putting it simple, the proxy has a catch around the real call to your method, that catch captures exceptions, and acts upon the framework configuration built based on all the annotations that you created (#ControlAdvice, #ExceptionHandler and so on). And so it can call the handleNodataFoundException(...) in the cases that you defined with annotations.
Update visualization via stacktrace
For instance, if you have two Spring #Component (or #Service, #Repository or whatever), and one calls the other one in a plain call, and you get an exception, in the stacktrace you see plenty of method calls (involving all kind of different classes) between your two component classes, all those are proxies and framework classes that take care of proxying, invoking, configuration and all the magic that the framework does. And everything is triggered by the proxy of your second component just before calling the real code that you developed, because the first component, at execution time, doesn't really call an instance of your class, but an instance of the proxy of your class that the framework created.
If you run a plain main, with two classes calling one to the other, instantiated by you with new, you will only see 3 lines in the stacktrace, because there the instances are plain instances created by you.
I am building a service with RestAPI's. I want to put a custom annotation as mentioned below.
#CustomAnnnotation
public APIResponse apiMethod(APIRequest request) {
}
Functionality of this custom annotation :
Whenever there is a request to this apiMethod, before the execution of this method , i want to call an API in different server with some of the request parameters from this function. Example mentioned below. Basically for every method invocation i want to call a different server.
Instead of doing this
public APIResponse apiMethod(APIRequest request) {
newServiceClient.newAPI(request.getName())
}
I want to do this functionality by using a custom annotation. I know that i can use interceptors to intercept this request and call the API. Is there any other way ?
Edit :
To summarise this question. There is a method(This might not be API start point. It can also be a normal method in your application) in my java code. Whenever i annotate this method, for every invocation of this method in application, i want do some functionality. I want to have this in annotation because i am thinking of providing a library for this annotation so that any function can be annotated
I want that all my rest services has as an input parameter HttpServletRequest httpRequest that I need later for some loggin purposes. This parameter sometimes is forgotten to be added and some methods are not logged. As are all rest services, and I am using Spring, all of them has some very specific annotations. I was thinking on using checkstyles to force the parameter to be present.
A little more of explanation of want I want to achieve. I am developing some rest servicies, and I am interested on logging some header that are sent to the rest services with some extra information. For this purpose, I have added HttpServletRequest request to each rest services as follows:
#GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
public Status get(HttpServletRequest request, #PathVariable("id") Integer id) {
....
}
This paremeter is correctly retrieved and I can read the headers correctly (everything automated using AspectJ). My problem now is that is for a new rest service, I forgot to add the parameter, no logs will be shown. As the parameter is optional (you can or cannot add to the rest service without any error) and all logging is automated by AspectJ, is possible that I can forget it for future rest services and no notice the miss until late.
The scope is to ensure that always is present in all my rest services. My first thought was using checkstyle as I am already using for other different purposes.
Is it possible using checkstyle or any similar tool to force that a parameter is present on any method that has an annotation? If not, there is any other different way to achive my objective?
lately I was trying to understand how spring processes #Valid annotation. For example look at the following controller's method:
#RequestMapping(value = "/create", method = RequestMethod.POST)
public ModelAndView createEmployee(#Valid EmployeeForm form, Errors errors) {
if(errors.hasErrors()) {
//validation errors
}
//method code
}
I am struggling to understand how errors instance is getting populated with validation errors in real-time. Does Spring, during compilation of the controller, inject code responsible for validation at the beginning of the createEmployee method? If so how this code would look?
I really tried to find an example of how this validation is performed in real life but it's just impossible. Help me please.
Everything happens at runtime. See the reference for more details on doing validation or this post for extra explanations.
Basically this is part of how Spring works internally. When you start your application Spring registers some beans, bean processors, can scan your classpath for annotated classes, registers those found annotated classes, builds proxies for some of them etc and uses all of them to build a context.
When handling a request, the request is handled on some predetermined execution path that starts with the DispatcherServlet, picking up other beans from the context as needed to handle the request (like validation for example) then forwarding to you controller in the createEmployee (which was registered as startup because Spring found your #RequestMapping annotations on your controller). When you return from the method the flow continues by building a model, selecting a view to display and then generating the response to the client.
For your example, Spring basically finds the #Valid annotation, looks for an already configured validator (configured by you or by a provided implementation for e.g. JSR-303), runs the validator and stores the validation result inside the Errors object. It does this when the request is processed, as mentioned above, it does not generate code.
If your question is to know exactly how Spring does this, in all it's details, you could take the Spring source code and have a look/debug it.
I'm having a couple of issues related to interceptors since upgrading to 3.1. In version 3.0.x and earlier I used the following pattern to intercept Spring MVC controllers:
Create an interface called something like RoleAware which defines one or more setters.
Have one or more controllers implement the interface
Register a new global interceptor which does a "handler instanceof RoleAware" check in the preHandle
If the interceptor is an instanceof RoleAware, then set one or more objects on the implementing controller
The first issue is that something changed in 3.1 so the instanceof check fails. I've fixed this by using the new explicit elements in my servlet context configuration. Not a big deal and a bit cleaner approach than the instanceof check.
The second issue is that when I attempt to cast the handler (Controller) to my RoleAware interface I get a ClassCastException.
I'd like a solution that will enable me to continue to arbitrarily set objects on the intercepted controllers. For example, set a Role object on any Controllers that are intercepted. Also, I'd like to know more about the changes in 3.1 that is causing this to break.
The reference doc says:
When using the RequestMappingHandlerMapping the actual handler is an
instance of HandlerMethod which identifies the specific controller
method that will be invoked.
So I guess that you should just cast the handler to HandlerMethod, call its getBean() method, and check is the returned bean is an instance of RoleAware.
Not tested though. You could try using a debugger to inspect the handler argument and see what it is if it is neither the handler itself, nor the HandlerMethod.