dimanche 23 avril 2023

Reflexion not working has expected - What is wrong?

I try to get statitics on method execution using a annotation on my code.

Here is the code for the annotation

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.function.Supplier;

import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;

public class StatsMethodCall {
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Stats {
    }
    
    public static class StatsRegistry {
        private MetricRegistry metricRegistry;

        public StatsRegistry(final MetricRegistry metricRegistry) {
            this.metricRegistry = metricRegistry;
        }

        public void addInvocation(final String methodName, final long duration) {
            Timer timer = metricRegistry.timer(methodName);
            timer.update(duration, java.util.concurrent.TimeUnit.MILLISECONDS);
        }

        public Timer getTimer(final String methodName) {
            return metricRegistry.timer(methodName);
        }
    }
    /**
     * Get the statistics based on the method name in the Stack.
     * This is a work around because it doesn't require the @Stats Annotation. 
     * Check Method statsMethod2.
     * @param <T>
     * @param method
     * @param statsRegistry
     * @return
     */
    public static <T> T statsMethod(final Supplier<T> method, final StatsRegistry statsRegistry) {
        final String methodName = Thread.currentThread().getStackTrace()[2].getMethodName();
        T result;
        final Timer.Context timerContext = statsRegistry.getTimer(methodName).time();
        try {
            result = method.get();
        } finally {
            timerContext.stop();
        }
        return result;
    }
    /**
     * Get the statistics based on the method with @Stats Annotation.
     * Doesn't work has expected
     * @param <T>
     * @param method
     * @param statsRegistry
     * @return
     */
    public static <T> T statsMethod2(final Supplier<T> method, final StatsRegistry statsRegistry) {
        String methodName = null;
        for (final StackTraceElement element : Thread.currentThread().getStackTrace()) {
            try {
                final Method m = Class.forName(element.getClassName()).getDeclaredMethod(element.getMethodName());
                if (m.getAnnotation(Stats.class) != null) {
                    methodName = m.getName();
                    break;
                }
            } catch (final ClassNotFoundException | NoSuchMethodException e) {
                // do nothing
            }
        }
        if (methodName == null) {
            throw new IllegalStateException("Cannot find method annotated with @" + Stats.class.getSimpleName());
        }

        T result;
        final Timer.Context timerContext = statsRegistry.getTimer(methodName).time();
        try {
            result = method.get();
        } finally {
            timerContext.stop();
        }
        return result;
    }
}

And I try to test the code with JUnit


import java.util.function.Supplier;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;

class StatsMethodCallTest {

    static MetricRegistry metricRegistry = new MetricRegistry();
    static StatsRegistry registry = new StatsRegistry(metricRegistry);
    static MyClass myClass = new MyClass();
    

    public static class MyClass {
        @Stats
        public String myMethod(final String arg1, final int arg2, final double arg3) {
            try {
                Thread.sleep(10);
            } catch (final InterruptedException ie) {
                ie.printStackTrace();
            }
            final String result = "Hello " + arg1 + " " + arg2 + " " + arg3;
            System.out.println(result);
            return result;
        }
    }

    @BeforeAll
    static void setUpBeforeClass() throws Exception {
        metricRegistry = new MetricRegistry();
        registry = new StatsRegistry(metricRegistry);
    }

    @AfterAll
    static void tearDownAfterClass() throws Exception {
    }

    @BeforeEach
    void setUp() throws Exception {
        
    }

    @AfterEach
    void tearDown() throws Exception {
    }

    /**
     * Workaround - I'm not fan of this
     */
    @Test
    final void test() {
        
        for(int i =0; i <100; i++) {
            //Here is the correct call
            StatsMethodCall.statsMethod(() -> myClass.myMethod("foo", 42, 3.14), registry);
        }
        
        //I tried without Lambda Expression just in case it was related to this
        for(int i = 0; i < 100; i++) {
            StatsMethodCall.statsMethod(new Supplier<Object>() {
        @Override
        public Object get() {
            return myClass.myMethod("foo", 42, 3.14);
        }
            }, registry);
        }       
        
        //This works but is weird code
        Timer timer = registry.getTimer("test");
        System.out.println("Invocation count: " + timer.getCount());
        System.out.println("Min duration: " + timer.getSnapshot().getMin());
        System.out.println("Max duration: " + timer.getSnapshot().getMax());
        System.out.println("Avg duration: " + timer.getSnapshot().getMean());
        
        //This doesn't work
        Timer timer2 = registry.getTimer(MyClass.class.getDeclaredMethods()[0].getName());
        System.out.println("Invocation count: " + timer2.getCount());
        System.out.println("Min duration: " + timer2.getSnapshot().getMin());
        System.out.println("Max duration: " + timer2.getSnapshot().getMax());
        System.out.println("Avg duration: " + timer2.getSnapshot().getMean());
    }
    
    /**
     * This is how I would like it works
     * Note: I'm calling statsMethod2 here !
     */
    @Test
    final void test2() {
        
        for(int i =0; i <100; i++) {
            //Here is the correct call
            StatsMethodCall.statsMethod2(() -> myClass.myMethod("foo", 42, 3.14), registry);
        }
                
        Timer timer = registry.getTimer(MyClass.class.getDeclaredMethods()[0].getName());
        System.out.println("Invocation count: " + timer.getCount());
        System.out.println("Min duration: " + timer.getSnapshot().getMin());
        System.out.println("Max duration: " + timer.getSnapshot().getMax());
        System.out.println("Avg duration: " + timer.getSnapshot().getMean());
    }

}

Help and explanation highly appreciated.

Thanks Kind regards

I expect that the Java Reflexion ill find the method with an anntation @Stats.

But there is no such method with @Stats annotation in the stack Very strange





Aucun commentaire:

Enregistrer un commentaire