How to create an invocation counter annotation in Spring? - java

Is it possible to create a custom annotation that simply tracks invocations of some methods, without having to add a service method call in every method explicit?
#InvocationCounter(path = "/test1") //I'm looking for this
#GetMapping("/person/{id}")
public Person getPerson(Long id) {
//...
}
On every getPerson() call, I want an invocation counter to record the invocation, like:
#Service
public class InvocationCounterService {
Map<String, AtomicInteger> counter;
public void count(String path) {
if (counter.get(path) == null) counter.put(path, new AtomicInteger()));
counter.get(path).incrementAndGet();
}
#Scheduled(fixedRate = 60000)
public void persist() {
//optionally process or persist the invocations
}
}
Question: how could I possibly tell Spring to invoke the count() service method on each annotated controller method?

The annotation InvocationCounter:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface InvocationCounter {
String path();
}
The Aspect InvocationCounterAspect:
#Aspect
#Component
public class InvocationCounterAspect {
#Autowired
InvocationCounterService invocationCounterService;
#Around("#annotation(InvocationCounter)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
InvocationCounter invocationCounter = signature.getMethod().getAnnotation(InvocationCounter.class);
final String path = invocationCounter.path(); //retrieve path from the annotation
invocationCounterService.count(path); //call the counter service
return joinPoint.proceed(); //proceed executing the annotated method
}
}

You should be looking at micrometer.io, Spring boot application natively supports metrics collection, including simple counter.

An annotation is a form of syntactic metadata that can be added to Java source code.
So you can add informations to your source with an annotation but you can't add code to be executed.
You can do with aspects as mentioned by pleft in the comments.
Or you can create a countExecutions function that takes your Function as a lambda expression to count the number of executions as follow:
public R countExecutions(Function<R, T> fun, T data) {
// invoke counter
return fun.apply(data);
}
and rewriting your code as
#GetMapping("/person/{id}")
public Person getPerson(Long id) {
return countExecutions( /* code previously in the getPerson as a Function */ , id);
}

Related

Incorporating Guice and AOP

I'm building a package that is trying to intercept a function's return value based on a flag. My design involves some AOP. The idea is that a class FirstIntercept intercepts a call firstCall and stores parameters in a Parameters object. Then later, a second class SecondIntercept intercepts another call secondCall and does some logic based on what is populated in Parameters:
// pseudoish code
public class FirstIntercept {
private Parameters param;
#AfterReturning(pointcut = "execution(* ...firstCall(..))", returning = "payload")
public void loadParam(Joinpoint joinPoint, Object payload) {
// logic handling payload returned from firstCall()
// logic provides a Boolean flag
this.param = new Parameters(flag);
}
}
public class Parameters {
#Getter
private Boolean flag;
public Parameters(Boolean flag) {
this.flag = flag;
}
}
public class SecondIntercept {
private static Parameters params;
#Around("execution(* ...secondCall(..))")
public void handleSecondCallIntercept(ProceedingJoinPoint joinPoint) {
// want to do logic here based on what params contains
}
}
What I want to achieve is that the Parameters object is loaded once and for all when FirstIntercept.loadParam is invoked through AOP. I'm not too sure how I can go about with this persistence. I looked online and Google guice seems to be promising. I believe a first step would to use dependency injection on the Parameters, but I'm really not sure. Can someone help point me in the right direction?
edit:
So I tried this setup:
public class FirstIntercept implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("invoked!");
return invocation.proceed();
}
#AfterReturning(pointcut = "execution(* ...firstCall(..))", returning = "payload")
public void loadParam(Joinpoint joinPoint, Object payload) {
// do stuff
}
public String firstCall() {
return "hello";
}
}
public class InterceptionModule extends AbstractModule {
protected void configure() {
FirstIntercept first = new FirstIntercept();
bindInterceptor(Matchers.any(), Matchers.annotatedWith(AfterReturning.class), first);
}
}
public class FirstIterceptTest {
#Test
public void dummy() {
Injector injector = Guice.createInjector(new InterceptionModule());
FirstIntercept intercept = injector.getInstance(FirstIntercept.class);
intercept.firstCall();
}
}
When I do .firstCall(), I can see the #AfterReturning running but the invoke is not being called.
If you expand upon the documentation for AOP https://github.com/google/guice/wiki/AOP you should get something close to:
public class FirstInterceptor implements MethodInterceptor {
#Inject Parameters parameters; // Injected with singleton Parameter
public Object invoke(MethodInvocation invocation) throws Throwable {
Object result = invocation.proceed();
// your logic based on result to set parameters.setFlag()
return result;
}
}
Then the second:
public class SecondInterceptor implements MethodInterceptor {
#Inject Parameters parameters; // Injected with singleton Parameter
public Object invoke(MethodInvocation invocation) throws Throwable {
boolean flag = parameters.getFlag();
// your logic here
return invocation.proceed(); // maybe maybe not?
}
}
Your parameters is the key, you'll need to ensure it's thread safe, which is another topic. But to inject these you need:
public class InterceptionModule extends AbstractModule {
protected void configure() {
// Ensure there is only ever one Parameter injected
bind(Parameter.class).in(Scopes.SINGLETON);
// Now inject and bind the first interceptor
FirstInterceptor firstInterceptor = new FirstInterceptor();
requestInjection(firstInterceptor );
bindInterceptor(Matchers.any(), Matchers.annotatedWith(AfterReturning.class),
firstInterceptor);
// Now inject and bind the second interceptor
SecondInterceptor SecondInterceptor = new SecondInterceptor ();
requestInjection(firstInterceptor);
bindInterceptor(Matchers.any(), Matchers.annotatedWith(AfterReturning.class),
SecondInterceptor);
}
}
Edit
Look at what you're doing.
You're telling Guice to wrap a method with #AfterReturn with the FirstInterceptor
Then you're calling interceptor.firstCall()
First call does not have #AfterReturn annotation, so why would it be matched against that configuration?
I'm guessing if you called:
intercept.loadParam();
you would see the invoke method. Also, this is great for a test, but in real life you want to have a Service level class have the #AfterReturn which is then Injected into another Api/Job/Etc that will call LoadParam.
edit
Oh no. Take a look at this line
bindInterceptor(Matchers.any(), // a class with this matcher
Matchers.annotatedWith(AfterReturning.class), // a method with this
firstInterceptor);
This means that the injector only fires on the loadParams. You need to annotate the method of the class youw ish to cause the interception with #AfterReturning. And you want the loadParams to be the invoke method.

Annotation works only for methods from interface

I have annotation:
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Inherited
public #interface Loggable { }
and aspect:
#Aspect
public class AspectLogger {
#Around("#annotation(aspects.Loggable)")
public void aroundLogging(ProceedingJoinPoint joinPoint) {
System.out.println("aroundLogging()");
throw new AuthentificationFailException();
}
}
Also I have interface and class:
public interface IAuthInteractor {
public User authorization(String login, String password);
}
public class AuthInteractor implements IAuthInteractor {
private EntityDAO<User> userDAO;
private ITokenGenerator tokenGenerator;
public AuthInteractor(EntityDAO<User> userDAO,
ITokenGenerator tokenGenerator) {
this.userDAO = userDAO;
this.tokenGenerator = tokenGenerator;
}
#Loggable
public User authorization1(String login, String password) {
return null;
}
#Loggable
public User authorization(String login, String password) {
return null;
}
}
For first method (authorization1) annotation doesn't work. For method authorization (that was described in interafce) annotation works.
Why does it work this way? and how to work without interface?
First of all, the aspect's advice has a void return type, i.e. it will never kick in for methods returning other types such as User in your example. The aspect should not even compile. It does not for me in any case. The AspectJ compiler says:
applying to join point that doesn't return void: method-execution(de.scrum_master.app.User de.scrum_master.app.AuthInteractor.authorization(java.lang.String, java.lang.String))
So, assuming you change your advice to
#Around("#annotation(aspects.Loggable)")
public Object aroundLogging(ProceedingJoinPoint joinPoint) {
System.out.println("aroundLogging()");
throw new AuthentificationFailException();
}
it will compile and also kick in. I tested it locally.
Now let me just quickly change the advice to actually proceed to the original method instead of always throwing an exception so we can test a bit more without catching exceptions all the time. I also want to print the actual joinpoint signature, so we can see what is going on:
#Around("#annotation(aspects.Loggable)")
public Object aroundLogging(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println(joinPoint);
//throw new AuthentificationFailException();
return joinPoint.proceed();
}
If then you add this main method to your interface implementation class:
public static void main(String[] args) {
System.out.println("Interface object");
IAuthInteractor iAuthInteractor = new AuthInteractor(null, null);
iAuthInteractor.authorization("user", "pw");
System.out.println("\nImplementation object");
AuthInteractor authInteractor = new AuthInteractor(null, null);
authInteractor.authorization("user", "pw");
authInteractor.authorization1("user", "pw");
}
The console log should print something like this, assuming you use AspectJ and not just "AOP lite" via Spring AOP which does not support call() joinpoints:
Interface object
execution(User de.scrum_master.app.AuthInteractor.authorization(String, String))
Implementation object
call(User de.scrum_master.app.AuthInteractor.authorization(String, String))
execution(User de.scrum_master.app.AuthInteractor.authorization(String, String))
call(User de.scrum_master.app.AuthInteractor.authorization1(String, String))
execution(User de.scrum_master.app.AuthInteractor.authorization1(String, String))
As you can see, executions are always caught, but calls are not for interface type instances because the interface method is not annotated, only the implementation.
BTW, method annotations are not inherited anyway, so your #Inherited meta annotation for an annotation type with #Target({ElementType.METHOD}) is kinda useless.

AOP for inner and private methods Java

I am trying to log the execution time for methods annotated with custom interface.
I am using Spring AOP.
But this does not seems to work for inner methods.
I think it is the limitation in Spring AOP
#Aspect
public class BusinessProfiler {
private static Log log = LogFactory.getLog(BusinessProfiler.class);
#Around("execution(* *(..)) && #annotation(TimeLog)")
public Object profile(ProceedingJoinPoint point) throws Throwable {
long start = System.currentTimeMillis();
Object result = point.proceed();
String format =
String.format("%s#%s: took [%s msec]", point.getTarget().getClass().getSimpleName(),
MethodSignature.class.cast(point.getSignature()).getMethod().getName(),
System.currentTimeMillis() - start);
log.info(format);
return result;
}
}
Are there any alternatives than Spring AOP
If you think about the way AOP annotations are dealt with by Spring this will be clear:
Spring takes your class and wraps it in a proxy with the extra code generated on the fly by the AOP annotation added. So only code called via the proxy (i.e from outside your class will be included).
Example
#Service
public class Foo {
public void doSomething() {
doSomethingInternal();
}
public void doSomethingInternal() {
}
}
If from another Spring bean I do this:
#Service
public class Bar {
#Autowired
private Foo foo;
public void execute() {
foo.doSomething();
}
}
Only doSomething will be called via the proxy which wraps your class, not doSomethingInternal, that will be called by your class.

Why can't the aop execute when a method is called from anonymous class' method?

Here is my custom annotation AnnoLogExecTime and class AOP:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface AnnoLogExecTime {
}
#Aspect
#Service
public class AOP {
Logger logger = LoggerFactory.getLogger(AOP.class);
#Around("execution(#com.judking.general.aop.AnnoLogExecTime * *(..))")
public Object calExecTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
Method method = signature.getMethod();
long t1 = System.currentTimeMillis();
Object obj = proceedingJoinPoint.proceed();
long t2 = System.currentTimeMillis();
logger.info("method `"+method.getName()+"` takes "+(t2-t1)+"ms");
return obj;
}
}
And the test case is as below:
#Service
class A {
public void go() {
B b = new B() { //Anonymous class B
#Override
public void exec() {
aopMethod();
}
};
b.exec();
}
#AnnoLogExecTime
public void aopMethod() {
System.out.println("aopMethod");
}
}
#Service
class B {
public void exec() {
System.out.println("exec");
}
}
When I call a.aopMethod(), the AOP.calExecTime is hooked up to a.aopMethod().
But if I call a.go(), which is using anonymous class B instance to call a.aopMethod(), then the AOP.calExecTime is NOT hooked up to a.aopMethod().
Could anyone give me an explanation to this phenomenon? And please give me a way to resolve this problem in the case of anonymous class. Thanks a lot!
This is not exactly because it is an anonymous inner class. What you are experiencing is a limitation of AOP proxies.
When you have
A a = ...; // get proxy
The proxy itself wraps the actual instance in a wrapper instance. When you interact with this wrapper instance by calling
a.aopMethod();
the proxy interceptor intercepts the call and can execute the advice.
This would apply to you calling
a.go()
if there was a joinpoint. Instead nothing intercepts that call, and the call to go() goes through the interceptor and the method is called on the actual instance
actualA.go();
When you create the anonymous inner class and have
#Override
public void exec() {
aopMethod();
}
it's implicitly doing
#Override
public void exec() {
A.this.aopMethod();
}
which goes around the proxy because you are calling it on the actual instance, not the wrapper.
You might not be using Spring to generate your proxies, but their documentation explains this pretty well.

Spring AOP, pointcut expressions : annotation with specific param

I have Aspect class with method clear().
#Aspect
public class Clear
{
#After("#annotation(org.springframework.transaction.annotation.Transactional)")
public void clear()
{
// do smth
}
}
now I want call this aspect after each execution of method with annotation #Transactional with readOnly = true like
#Transactional(readOnly = true)
public void someMethod()
{
//...
}
is there way to do it without custom annotations?
I think you are quite close.
In your clear method, you should take in a parameter of type JoinPoint. This parameter will be auto populated by Spring at runtime, and with it, you can get details of your specific joinpoint, including the java.lang.reflect.Method, which will contain the annotation you are after.
I am thinking something like this:
#Aspect
public class Clear
{
#After("#annotation(org.springframework.transaction.annotation.Transactional)")
public void clear(final JoinPoint joinPoint)
{
final Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
final Transactional txAnnotation = methood.getAnnotation(Transactional.class);
final boolean isReadOnly = txAnnotation.readOnly();
//do conditional work based on isReadOnly
}
}

Categories

Resources