An interface:
public interface Manager {
Object read(Long id);
}
A class which implements this interface:
#Transactional
Public class ManagerImpl implements Manager {
#Override
public Object read(Long id) {
// Implementation here
}
}
An aspect for ManagerImpl:
#Aspect
public class Interceptor {
#Pointcut("execution(public * manager.impl.*.*(..))")
public void executionAsManager() {
}
#Around("executionAsManager()")
public Object invoke(ProceedingJoinPoint joinPoint) throws Throwable {
// Do some actions
return joinPoint.proceed();
}
}
A controller:
#RestController()
public class Controller {
#Autowired
private Manager manager;
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public Object read(#PathVariable Long id) {
return manager.read(id);
}
#RequestMapping(value = "reflection/{id}", method = RequestMethod.GET)
public Object readViaReflection(#PathVariable Long id) {
return ManagerImpl.class.getMethod("read", Long.class).invoke(manager, id);
}
}
So, when spring injects manager variable within controller proxy created.
When method invoked directly:
manager.read(1L)
aspect is invoked.
However, when I try to do like this (see readViaReflection)
ManagerImpl.class.getMethod("read", Long.class).invoke(manager, 1L);
got java.lang.reflect.InvocationTargetException object is not an instance of declaring class.
Which is reasonable.
The question is: how can I invoke method via reflection on proxy-object created by spring (I have method extracted from target-object and I have instance of proxy created by spring).
Can not do invocation on target because then aspect will not invoke.
As you have noticed, you cannot invoke the method of ManagerImpl on the bean, beacause the Bean is actually implemented by a Proxy.
For me, the solution was to get the invocation handler of the Proxy and call the method.
if (Proxy.isProxyClass(manager.getClass())) {
Method readMethod = ManagerImpl.class.getMethod("read", Long.class);
Proxy.getInvocationHandler(manager).invoke(manager, readMethod, parameter);
} else
info.getMethod().invoke(serviceClass, parameter);
The else part is necessary when the Bean is not a Proxy, but either the bare ManagerImpl class or a CGLib proxy class (which would subclass ManagerImpl in your case).
You must invoke the method from the proxy's class. Try this:
manager.getClass().getMethod("read", Long.class).invoke(manager, 1L);
I don't think java reflection will do the trip, you need to use if() pointcut expression
To do implement it, you can define another boolean argument (named invokeAOP), when your invoke manager with invokeAOP = true, then you'll get your Aspect exectued. Otherwise your Aspect will be omitted.
You can do it without using reflection - it just needs some casting:
((Manager) ((Advised)manager).getTargetSource().getTarget()).read(1L);
The cool thing is that it works with JDK and CGLIB proxies.
If you have to use reflection just use part of this solution:
Manager managerBean = ((Manager) ((Advised)manager).getTargetSource().getTarget());
managerBean.getClass().getMethod("read", Long.class).invoke(managerBean, id)
Related
I am writing a Spring Boot Application. I want to audit methods with my annotation #AuditMetod: For example I have method foo() with the annotation:
#AuditMetod(name = "SomeValue")
foo() {...}
I want to handle and audit such methods like this (the simplest example):
auditMethod(Method method) {
if (method.hasAnnotation(AuditMethod.class)) {
System.out.println (method.getName() + " was called at " + new Date())
}
}
upd
Thanks to #Karthikeyan #Swapnil Khante and #misha2048 I understood, that I need to use AOP. But I have 2 problems:
The only method in Aspect class in not being called and I don't see the inscription "----------ASPECT METHOD IS CALLED-----------" in log
How can I check in aspect method what method it is intercepting. To get an instance of Method class.
Now I have the following code:
Controller:
#PostMapping
#LoggingRest(executor = "USER", method = "CREATE", model = "SUBSCRIPTION")
public ResponseEntity<?> create(#Valid #RequestBody SubscriptionRequestDto dto) {
...
}
Aspect:
`#Aspect
#Slf4j
#Component
public class AuditAspect {
#Pointcut(value = "#annotation(com.aspect.annotations.LoggingRest)")
public void auditMethod(ProceedingJoinPoint proceedingJoinPoint) {
log.info("----------ASPECT METHOD IS CALLED------------");
}`
And annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface LoggingRest {
String executor() default "SYSTEM";
String method() default "";
String model() default "";
}
Auditing is a cross-cutting concern and can be handled using AOP.
Another solution would be to use a low-level solution by writing a custom annotation and using a Spring interceptorto write your business logic.
To use the Spring interceptor you will need to implement the HandlerInterceptor interface
Example of the annotation
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Audit {
boolean active() default true;
}
Interceptor example
#Component
public class AuditInterceptor implements HandlerInterceptor {
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Audit annotation = handlerMethod.getMethodAnnotation(Audit.class);
if (annotation != null && annotation.active()) {
// your business logic
}
}
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
check this interceptor example
I think one of the solutions here, as #Karthikeyan mentioned, is to use Spring AOP.
If you are not aware a brief introduction - spring-aop module implements the aspect oriented programming paradigm. We extract some common functionality, that we generally want to apply to some subset of functions/methods, to an entity called Aspect (see class annotated with #Aspect). This class will contain out cross-cutting functionality - such as auditing, for instance we want to audit the methods execution time, lets say. We just put the code to be executed, the condition, which tell the spring what exact beans methods should be affect by this aspect, see below.
For example, if I can audit the method execution duration with the following very simple example (in my case I said that any public method, returning void inside the Class com.example.stackoverflow.BusinessLogicClass must be inspected by this Aspect):
#SpringBootApplication
#EnableAspectJAutoProxy
public class StackoverflowApplication implements ApplicationRunner {
#Autowired
private BusinessLogicClass businessLogicClass;
public static void main(String[] args) {
SpringApplication.run(StackoverflowApplication.class, args);
}
#Override
public void run(ApplicationArguments args) throws Exception {
businessLogicClass.test();
}
}
#Aspect
#Component
class MyAspectLogicClass {
#Around("execution(public void com.example.stackoverflow.BusinessLogicClass.*(..))")
public Object hangAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long before = System.currentTimeMillis();
Object returnedValue = proceedingJoinPoint.proceed();
long after = System.currentTimeMillis();
System.out.printf("Retruned in '%s' ms %n", (after - before));
return returnedValue;
}
}
#Component
class BusinessLogicClass {
public void test() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
In my case, I will get the time before method execution, then by the means of
proceedingJoinPoint.proceed() call I delegate the execution to the real method, and then, once I get the response back, I will get the current system time and calculate the execution time, fairly simple.
I hope I have at least directed you somewhere, if you are looking for documentation, this are the resources I suggest you should look for:
https://docs.spring.io/spring-framework/docs/2.5.x/reference/aop.html offical spring doc (stale a bit, but there are some valuable things to learn)
https://docs.spring.io/spring-framework/docs/4.3.15.RELEASE/spring-framework-reference/html/aop.html is more fresh doc
Hope it helped :)
The problem was in right annotation. In Aspect class I tried #Around and everything works as I need.
#Aspect
#Slf4j
#Component
public class AuditAspect {
#Around(value = "#annotation(com.aspect.annotations.LoggingRest)")
public void auditMethod(ProceedingJoinPoint proceedingJoinPoint) {
var method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
log.info("----------ASPECT METHOD IS CALLED------------");
}
}
For getting a Method instance I use fallowing code
Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
I have a custom annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Controller {
EventType[] events() default EventType.MESSAGE;
}
And there are methods in class B using them like below:
#Controller(events = {EventType.MESSAGE, EventType.DIRECT_MESSAGE})
public void onMessage(Message msg) { }
#Controller(events = {EventType.STAR_ADDED})
public void onStarAdded(Message msg) { }
Now, I want to invoke the above methods based on the annotation events value from another class A. In other words, when class A receives an event of type STAR_ADDED, I want to invoke all methods in class B with annotation #Controller(events = {EventType.STAR_ADDED}).
I know how to do this in Java but does Spring provide any API to do this? If yes, a code snippet would be helpful too.
Solution 1:
You could also do something like this:
enum EventType {
MESSAGE {
#Override
public void handleMessage(Service service, Message message) {
service.onMessage(message);
}
},
STAR_ADDED {
#Override
public void handleMessage(Service service, Message message) {
service.onStarAdded(message);
}
public abstract void handleMessage(Service service, Message message);
}
}
In your other class, where you know what is the "active" event:
yourEvent.handleMessage(service, message);
Solution 2:
I don't know if spring has anything precisely for that, otherwise you could also use reflection. Here's an example using reflection (I much prefer the solution above => enum without reflection):
for(Method method: Service.class.getDeclaredMethods()){
Controller annotation = m.getAnnotation(Controller.class);
for(EventType event: annotation.events()){
if(event.equals(yourActiveEventType)){
method.invoke(service, message);
}
return ...
}
}
Hint (not a solution) 3:
I really don't think the following applies for your scenario, but I thought I'd mention it... Spring AOP lets you trigger some code when an annotated method is called (it's kind of the opposite of your scenario), check this answer, but it may be worth the read for you: aspectj-pointcut-for-all-methods-of-a-class-with-specific-annotation
#Around("execution(#Controller * com.exemple.YourService.*(..))")
public Object aroundServiceMethodAdvice(final ProceedingJoinPoint pjp)
throws Throwable {
// perform actions before
return pjp.proceed();
// perform actions after
}
Solution 4: (added after comments)
Using org.reflections
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.10</version>
</dependency>
example:
Service service = ...;
Message message = ...;
Set<Method> methods =
ReflectionUtils.getMethods(Service.class, ReflectionUtils.withAnnotation(Controller.class),ReflectionUtils.withParametersAssignableTo(Message.class));
for(Method m: methods){
Controller controller = m.getAnnotation(Controller.class);
for(EventType eventType: controller.value()){
if(EventType.MESSAGE.equals(eventType)){
m.invoke(service, message);
}
}
}
This assumes that you already hold the reference to the Service object (where your methods are).
Since you are using Spring, if your 'Services' are spring managed, you may get the instance from spring's context, you'll have to try it out for yourself, as this is somewhat bound to your design:
#Autowired
private ApplicationContext appContext;
Reflections r = new Reflections(new MethodAnnotationsScanner(), "com.your.package");
Set<Method> methods = r.getMethodsAnnotatedWith(Controller.class);
for(Method m: methods){
Controller controller = m.getAnnotation(Controller.class);
for(EventType eventType: controller.value()){
if(EventType.MESSAGE.equals(eventType)){
String className = m.getDeclaringClass().getSimpleName();
className = className.replaceFirst(className.substring(0,1), className.substring(0,1).toLowerCase());
Object service = appContext.getBean(className);
m.invoke(service, message);
}
}
}
This works if your Class is spring managed and is added to the context using its default camelcase name.
You may simplify the logic, but I believe the principal elements are there.
I have a class like this:
public class MyResource(){
public MyResource(#Context ServletContext context){
context.setAttribute("someAttribute","someVal");
}
#PUT
public void someMethod(){
...
}
}
and I would like to this using annotations (i.e. JAX-RS/Jersey reads the value of the annotation and writes it into ServletContext so that I can access this value somewhere else where I inject the ServletContext in the request scope.)
#MyCustomAnnotation(name="someVal")
public class MyResource(){
}
Annotation needs to be treated by some code.
You need to create a filter, that processes your custom annotation, before your method is called.
see : https://jersey.java.net/documentation/latest/filters-and-interceptors.html
Creating a filter, should be fairly easy, but it is not enough. It will get called, but won't know in what context it will be called. By context, I mean which class / method will be called right after the filter is executed. In this example I assumed your annotation (called MyCustomAnnotation) can be applied to class / method.
For this, you need to create a "Dynamic Feature" that will bind a different instance of the filter, for each possible context.
In details :
For a given JAX-RS class :
#MyCustomAnnotation(name="someVal")
class MyClass{
#GET
#MyCustomAnnotation(name="someConfig")
public Object myMethod(){
...
}
#GET
#MyCustomAnnotation(name="otherConfig")
public Object myOtherMethod(){
...
}
}
First, create your annotation (I guess you know, but just to be clear) :
#Target({ ElementType.METHOD, ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
public #interface MyCustomAnnotation {
String name() default "";
}
Then, create a filter.
Notice the special constructor. A different instance of the filter will be created for each possible context. The right instance of the filter will be used in a specific context. This way it will know in what context (Class / Method) is is called. This way, using intro-spectation, your filter can behave however you like, based of the annotation you used on your target class and/or method :
#Priority(Priorities.AUTHORIZATION - 1)
public class MyFilter implements ContainerRequestFilter {
private final Class<?> _class;
private final Method method;
private MyCustomAnnotation classAnnotation;
private MyCustomAnnotation methodAnnotation;
public MyFilter(Class<?> _class, Method method) {
this._class = _class;
this.method = method;
this.classAnnotation = _class.getAnnotation(MyCustomAnnotation.class);
this.methodAnnotation = method.getAnnotation(MyCustomAnnotation.class);
}
#Override
public void filter(ContainerRequestContext requestContext) {
// your code goes here!
// based on classAnnotation and/or methodAnnotation,
// add whatever you want to the requestContext
}
}
Ok, so now we have an annotation, a filter that process this annotation, now we need to bind dynamically to class / methods that are annotated
public class MyFilterDynamicFeature implements DynamicFeature {
#Override
public void configure(final ResourceInfo resourceInfo, final FeatureContext configuration) {
//if the class or the method is annotated, bind a new instance of our filter to this method
if(resourceInfo.getResourceClass().getAnnotation(MyCustomAnnotation.class)!=null || resourceInfo.getResourceMethod().getAnnotation(MyCustomAnnotation.class)!=null){
configuration.register(new MyFilter(resourceInfo.getResourceClass(), resourceInfo.getResourceMethod()));
}
}
}
In your JAX-RS configuration... register your new DynamicFeature
public class MyRestConfig extends ResourceConfig {
public RestConfig() {
// your configs...
packages("com.yourpackage.rest");
// ...
// handle #MyCustomAnnotation annotations
register(MyFilterDynamicFeature.class);
// ...
}
}
I hope this is clear. Recap of what you need to do
create your annotation
annotate your JAX-RS class / method with your annotation
create a filter that will process your annotation
create a dynamic feature, that will bind a different instance of the filter for each different context (method / class combination, where at least one or the other is annotated with your annotation)
register the dynamic feature in your rest config
----Update--------
Rather than using the Dynamic Feature, you should be able to inject the ressource info at runtime
#Context
private ResourceInfo resourceInfo;
I am using JAX-RS 2.0 with Jersey 2.6. I was wondering if it was possible to have something like this:
#GET
#Path("/get/{id}")
#MapTo(type = MyObjectDTO.class)
public MyObject getMyObject(#PathParam("id") String id){
MyObject o = ...
return o;
}
In the method above I am returning an instance of MyObject. However, I have defined the MapTo annotation to indicate that I want to map this object to MyObjectDTO. The way I was thinking this could work is to process the response early in a ContainerResponseFilter, detect the annotation MapTo and, assuming no error occurred, replace the entity in the response with an instance of MyObjectDTO created appropriately from the existing entity (of type MyObject).
However, I couldn't find a way to get the Method in the resource that was just called after the request came in, i.e., the getMyObject method, so that I can scan for the MapTo annotation.
Is there a way to achieve this in a JAX-RS-y kind of way?
Is this some serious reason you cannot return dto object? Sounds very strange...You can probably use AOP but I guess it would be bad practive
Here the Spring AOP example
http://docs.spring.io/spring/docs/2.5.4/reference/aop.html
I think I found a solution by reading this SO. I created a class that looks like this:
#Provider // or register in the configuration...
public class DTOMapperFeature implements DynamicFeature {
#Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
for (Annotation annotation : resourceInfo.getResourceMethod().getAnnotations()) {
if (annotation instanceof MapTo) {
MapTo mapTo = (MapTo) annotation;
// Note: additional validation (return type shouldn't be void,
// collections are out etc.) is required before creating this,
// or should be pushed in the DTOMapperFilter.
// You get the gist: this filter will map the entity to an instance
// of the specified class (using a constructor in this case).
context.register(new DTOMapperFilter(
resourceInfo.getResourceMethod().getReturnType(),
mapTo.getResponseType());
}
}
}
#Priority(/* appropriate priority here! */)
public final static class DTOMapperFilter implements ContainerResponseFilter {
public DTOMapperFilter(Class<?> declaredReturnType, Class<?> responseType) {
// implementation omitted: find DTO constructor etc.
// throw if responseType does NOT have a constructor that takes an instance
// of declaredReturnType: catch errors at application bootstrap!
}
#Override
public void filter(
ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException {
// implementation omitted: create instance of DTO class using constructor
}
}
}
Given sensible exceptions will be thrown from either the constructor of DTOMapperFilter or the configure method above, this should be pretty robust and errors detectable at test time.
As far as I know, in Spring AOP when we want some method call to be intercepted, we configure an Aspect having such a pointcut configuration that matches wanted method invocation. In other words, we configure interception on the Aspect side.
Is there a way to configure it entirely from the opposite side, that is, on method which invocation is to be intercepted? I expect that something like this is possible:
#Component
class MyClass {
#Intercept(interctptor="myInterceptor", method="invoke")
Object methodThatWillBeIntercepted(Object arg) {
// ..
}
}
#Component(value="myInterceptor")
class MyInterceptor {
Object invoke(MethodInvocation mi) {
// ...
if (someCondition) {
return mi.proceed();
} else {
return someOtherValue;
}
}
}
You can, at least if you're using it with AspectJ. You can use the syntax #annotation(com.mycompany.MyAnnotation) in your pointcut declaration to target elements that are annotated with your annotation. You can read more about it in section 9.2.3 of the Spring reference documentation
If you're not using AspectJ, but a generic proxy based interceptor, a "brute force" approact would be to proxy all the object you want to check and then check the method invocation argument to see if the method is annotated with your annotation, something like this:
class MyInterceptor {
public Object invoke(MethodInvocation mi) {
if(mi.getMethod().getAnnotation(MyAnnotationClass.class) != null) {
// Do the interception
}
else {
return mi.proceed();
}
}
}
Don't remember the exact API for MethodInvocation, but something like that.