开发者

Writing a java annotation for timing method call

开发者 https://www.devze.com 2023-02-28 07:08 出处:网络
I want to write a java annotation which times the method call. something like this: @TimeIt public int someMethod() { ... }

I want to write a java annotation which times the method call. something like this:

@TimeIt
public int someMethod() { ... }

and when this method is invoked, it should output on console how long this method took

I know how to do it in python, this is what I want it to do:

from time import time, sleep

def time_it(func):
    def wrapper(*args, **kwargs):
        start = time()
        func(*args, **kwargs)
        stop = time()
        print "The f开发者_JS百科unction", func.__name__, " took %.3f" % (stop - start)
    wrapper.__name__ = func.__name__
    return wrapper

@time_it
def print_something(*args, **kwargs):
    print "before sleeping"
    print args, kwargs
    sleep(3) # wait 3 seconds
    print "after sleeping"

print_something(1, 2, 3, a="what is this?")

So my questions are? Where do I find some documentation to write something like this, I tried apt documentation, had no luck with it. can someone help with writing something like this?


AFAIK, Tomasz is right in saying that this can't be done using annotations. I think the confusion stems from the fact that Python decorators and Java annotations share the same syntax but are completely different in terms of the behavior they offer!

Annotations are metadata attached to your class/methods/fields. This blog post addresses the point of timing methods using AOP. Though it uses Spring, the basic premise remains the same. If you are good to go with an AOP compiler, it shouldn't be too difficult to translate the code. Another reference (spring specific) here.

EDIT: If your aim is to have a overall method timing for your application without using full blown profilers, you can use hprof for collecting total execution statistics.


Simply put: you can't!

Annotations are not pieces of code that get automatically started together with your code, they are just annotation, pieces of information that can be used by other programs working on your code like loading or running it.

What you need is AOP: aspect oriented programming.


As of 2016, there's a nifty aspect annotation library jcabi-aspects.

From the docs:

Annotate your methods with @Loggable annotation and every time they are called, your SLF4J logging facility will receive a message with the details of execution and the total execution time:

public class Resource {
  @Loggable(Loggable.DEBUG)
  public String load(URL url) {
    return url.openConnection().getContent();
  }
}

Something like this will appear in the log:

[DEBUG] #load('http://www.google.com'): returned "<html ..." in 23ms

Read more about @Loggable here.


I wondered the same thing several time and ended by writting the following start:

The annotation:

package main;
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 Clocking {

}

An object's interface:

package main;

public interface Examples {
    @Clocking
    void thisIsAMethod();

    void thisIsAnotherMethod(String something);

    @Clocking
    void thisIsALongRunningMethod();
}

An invocation handler:

package main;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.time.Duration;
import java.time.Instant;

public class ExamplesInvocationHandler implements InvocationHandler {
    // ******************************
    // Fields
    // ******************************
    private Examples examples = new ExamplesImpl();

    // ******************************
    // Public methods
    // ******************************
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // If the annotation is not present, just redirect the method call to its origin...
        if(!method.isAnnotationPresent(Clocking.class)) {
            return method.invoke(examples, args);
        }

        // ... otherwise log the execution time of it.
        Instant start = Instant.now();
        Object returnObj = method.invoke(examples, args);
        Instant end = Instant.now();

        // TODO: This is for demonstration purpose only and should use the application's logging system.
        System.out.println("Method " + method.getName() + " executed in " + Duration.between(end, start) + ".");

        return returnObj;
    }

    // ******************************
    // Inner classes
    // ******************************
    private static class ExamplesImpl implements Examples {
        @Override
        public void thisIsAMethod() {
            System.out.println("thisIsAMethod called!");
        }

        @Override
        public void thisIsAnotherMethod(String something) {
            System.out.println("thisIsAnotherMethod called!");
        }

        @Override
        public void thisIsALongRunningMethod() {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("thisIsALongRunningMethod called!");
        }
    }
}

An finally an entry point to test this:

package main;
import java.lang.reflect.Proxy;

public class Main {
    public static void main(String[] args) {
        Examples examples = (Examples) Proxy.newProxyInstance(Examples.class.getClassLoader(), new Class[]{Examples.class}, new ExamplesInvocationHandler());

        examples.thisIsAMethod();
        examples.thisIsAnotherMethod("");
        examples.thisIsALongRunningMethod();
    }
}

This needs an improvement, since it requires Proxy to instantiate our object and so you can't really use it for "generic already written" code. But it might leads you to something more complete.


Check out the Coda Hale Metrics library. It provides a @Timed annotation for methods that provides this capability. While you're at it check out Code Hale Dropwizard which has examples for how its been integrated into their service framework.

@GET
@Timed
public Saying sayHello(@QueryParam("name") Optional<String> name) {
    return new Saying(counter.incrementAndGet(),
                      String.format(template, name.or(defaultName)));
}


Despite all the nay-sayers, you can do this. Java annotations cannot change the source or class files they operate on, so your options are:

1) Use a super class. The annotation processor can generate a super-class that times an abstract method. Your actual class implements this method. The downsides is that the method you want to time has to be renamed so that the super-class can provide an implementation. The result might look like this

@BenchmarkMe( extend="MySuperClass" )
public class MyClass extends BenchmarkMyClass {
    public void normalMethod() { ... }
    public void bench_myMethod() { ... }
}  

and the annotation process would generate:

public class BenchmarkMyClass extends MySuperClass {
    public abstract void bench_myMethod();
    public void myMethod() {
       benchmarkStart();
       try {
          bench_myMethod();
       } finally { benchmarkStop(); }
    }
}

By using a naming convention to indicate which methods should be timed as the prefix "bench_" was used in my example.

2) Use a ClassFileTranformer as well as an Annotation The approach would be to create a runtime annotation that can be used to mark the methods you are interested in timing. At runtime a ClassFileTransformer is specified on the command line and it transforms the byte code to insert the timing code.

Unless you like working with byte code, using AOP is the better bet, but it IS possible.


I am surprised to see that no one pointed out java.lang.reflect.Proxy. Its an old thread, but I think this information would be helpful to someone.

Proxy has an interesting property which gives

  1. proxy instanceof Foo as true.
  2. You can have a method in your invocation handler, which prints the time first and then fires the actual method from the object.

You can have this proxy for all objects by making them implement some interface or you can use Comparable.

Look for section Dynamic proxies as decorator.

http://www.ibm.com/developerworks/library/j-jtp08305/


It isn't nearly as easy in Java. The basic idea would be this:

  1. Create annotation that says "time this method"
  2. Create a java agent that uses byte code transformation to: a. Find methods with the annotation b. Add timing code to them
  3. Set the javaagent option when you run java to use your new agent

This article would get you started: http://today.java.net/pub/a/today/2008/04/24/add-logging-at-class-load-time-with-instrumentation.html .

You might also be able to use BTrace to make this even easier: http://kenai.com/projects/btrace/pages/Home


As already stated you can't and AOP or hprof should cover most of your needs, but if you insist there's a workaround using JSR269. FYI, apt is obsolete and an annotation processing API and tool has been incorporated into 1.6 (and it is called with the evocative name JSR269).

The workaround would be to create an annotation processor that generates a class that extends the class that contains the method with the @TimeIt annotation. This generated class must override the timed method, it will look like the Python time_it but the line func(*args, **kwargs) would be replaced by super.methodName(arg1, arg2, ...).

There are however two caveats:

  1. Elsewhere in your code you must be sure that you create instances of the generated class instead of the original class. That is a problem because you reference a class that does not exist yet: it will be created at the end of the first processing round.
  2. You will need to get familiar with the javax.annotation.processing and javax.lang.model packages, they are a bit awkward IMHO.
0

精彩评论

暂无评论...
验证码 换一张
取 消