Your situation is somewhat complex because you have several problems:
- You use Spring AOP, an "AOP lite" framework based on dynamic proxies (JDK proxies for interfaces, CGLIB proxies for classes). It only works for Spring beans/components, but from what I see your
LoanClient
is not a Spring @Component
.
- Even if it was a Spring component, Feign creates its own JDK dynamic proxies via reflection. They are outside the control of Spring. Probably there is a way to wire them into Spring manually, either programmatically or via XML configuration. But there I cannot help you because I do not use Spring.
- Spring AOP only supports a subset of AspectJ pointcuts. Specifically, it does not support
call()
but only execution()
. I.e. it only weaves into the place where a method is executed, not the place where it is called.
- But the execution takes place in a method implementing an interface and annotations on interface methods such as your
@MeteredRemoteCall
are never inherited by their implementing classes. In fact, method annotations are never inherited in Java, only class-level annotations from class (not interface!) to respective subclass. I.e. even if your annotation class had an @Inherited
meta annotation, it would not help for @Target({ElementType.METHOD})
, only for @Target({ElementType.TYPE})
. Update: Because I have answered this question several times before, I have just documented the problem and also a workaround in Emulate annotation inheritance for interfaces and methods with AspectJ.
So what can you do? The best option would be to use full AspectJ via LTW (load-time weaving) from within your Spring application. This enables you to use a call()
pointcut instead of execution()
which is implicitly used by Spring AOP. If you use @annotation()
pointcuts on methods in AspectJ, it will match both calls and executions, as I will show you in a stand-alone example (no Spring, but the effect is the same as AspectJ with LTW in Spring):
Marker annotation:
package de.scrum_master.app;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MeteredRemoteCall {}
Feign client:
This sample client grabs full StackOverflow question pages (HTML source code) as strings.
package de.scrum_master.app;
import feign.Param;
import feign.RequestLine;
public interface StackOverflowClient {
@RequestLine("GET /questions/{questionId}")
@MeteredRemoteCall
String getQuestionPage(@Param("questionId") Long questionId);
}
Driver application:
This application uses the Feign client interface in three different ways for demonstration purposes:
- Without Feign, manual instantiation via anonymous subclass
- Like #1, but this time with an extra marker annotation added to the implementing method
- Canonical usage via Feign
package de.scrum_master.app;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import feign.Feign;
import feign.codec.StringDecoder;
public class Application {
public static void main(String[] args) {
StackOverflowClient soClient;
long questionId = 41856687L;
soClient = new StackOverflowClient() {
@Override
public String getQuestionPage(Long loanId) {
return "StackOverflowClient without Feign";
}
};
System.out.println(" " + soClient.getQuestionPage(questionId));
soClient = new StackOverflowClient() {
@Override
@MeteredRemoteCall
public String getQuestionPage(Long loanId) {
return "StackOverflowClient without Feign + extra annotation";
}
};
System.out.println(" " + soClient.getQuestionPage(questionId));
// Create StackOverflowClient via Feign
String baseUrl = "http://stackoverflow.com";
soClient = Feign
.builder()
.decoder(new StringDecoder())
.target(StackOverflowClient.class, baseUrl);
Matcher titleMatcher = Pattern
.compile("<title>([^<]+)</title>", Pattern.CASE_INSENSITIVE)
.matcher(soClient.getQuestionPage(questionId));
titleMatcher.find();
System.out.println(" " + titleMatcher.group(1));
}
}
Console log without aspect:
StackOverflowClient without Feign
StackOverflowClient without Feign + extra annotation
java - How to use AOP with Feign calls - Stack Overflow
As you can see, in case #3 it just prints the question title of this very StackOverflow question. ;-) I am using the regex matcher in order to extract it from the HTML code because I did not want to print the full web page.
Aspect:
This is basically your aspect with additional joinpoint logging.
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import de.scrum_master.app.MeteredRemoteCall;
@Aspect
public class MetricAspect {
@Around(value = "@annotation(annotation)", argNames = "joinPoint, annotation")
public Object meterRemoteCall(ProceedingJoinPoint joinPoint, MeteredRemoteCall annotation)
throws Throwable
{
System.out.println(joinPoint);
return joinPoint.proceed();
}
}
Console log with aspect:
call(String de.scrum_master.app.StackOverflowClient.getQuestionPage(Long))
StackOverflowClient without Feign
call(String de.scrum_master.app.StackOverflowClient.getQuestionPage(Long))
execution(String de.scrum_master.app.Application.2.getQuestionPage(Long))
StackOverflowClient without Feign + extra annotation
call(String de.scrum_master.app.StackOverflowClient.getQuestionPage(Long))
java - How to use AOP with Feign calls - Stack Overflow
As you can see, the following joinpoints get intercepted for each of the three cases:
- Only
call()
because even with manual instantiation the implementing class does not have the interface method's annotation. So execution()
cannot be matched.
- Both
call()
and execution()
because we manually added the marker annotation to the implementing class.
- Only
call()
because the dynamic proxy created by Feign does not have the interface method's annotation. So execution()
cannot be matched.
I hope this helps you understand what happened and why.
Bottom line: Use full AspectJ in order to let your pointcut match against call()
joinpoints. Then your problem is solved.