retentionpolicy - Escribiendo una anotación java para la llamada del método de tiempo
reflexion java (9)
Quiero escribir una anotación de java que veces el método de llamada. algo como esto:
@TimeIt
public int someMethod() { ... }
y cuando se invoca este método, debe mostrar en la consola cuánto tiempo tomó este método
Sé cómo hacerlo en python, esto es lo que quiero que haga:
from time import time, sleep
def time_it(func):
def wrapper(*args, **kwargs):
start = time()
func(*args, **kwargs)
stop = time()
print "The function", 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?")
¿Así que mis preguntas son? ¿Dónde encuentro alguna documentación para escribir algo como esto? Intenté una documentación apt
, pero no tuve suerte con eso. ¿Puede alguien ayudar a escribir algo como esto?
A partir de 2016, hay una ingeniosa biblioteca de anotaciones jcabi-aspects .
De la documentación:
Anote sus métodos con la anotación @Loggable y cada vez que se llamen, su facilidad de registro SLF4J recibirá un mensaje con los detalles de la ejecución y el tiempo total de ejecución:
public class Resource {
@Loggable(Loggable.DEBUG)
public String load(URL url) {
return url.openConnection().getContent();
}
}
Algo así aparecerá en el registro:
[DEBUG] #load(''http://www.google.com''): returned "<html ..." in 23ms
Lea más sobre @Loggable here .
A pesar de todos los negativistas, puedes hacer esto. Las anotaciones de Java no pueden cambiar los archivos de origen o de clase en los que operan, por lo que sus opciones son:
1) Usa una súper clase. El procesador de anotaciones puede generar una superclase que cronometra un método abstracto. Tu clase actual implementa este método. El inconveniente es que el método que desea cambiar de tiempo debe cambiarse de nombre para que la superclase pueda proporcionar una implementación. El resultado podría verse así
@BenchmarkMe( extend="MySuperClass" )
public class MyClass extends BenchmarkMyClass {
public void normalMethod() { ... }
public void bench_myMethod() { ... }
}
y el proceso de anotación generaría:
public class BenchmarkMyClass extends MySuperClass {
public abstract void bench_myMethod();
public void myMethod() {
benchmarkStart();
try {
bench_myMethod();
} finally { benchmarkStop(); }
}
}
Al usar una convención de nomenclatura para indicar qué métodos deben cronometrarse ya que en mi ejemplo se usó el prefijo "banco_".
2) Utilice un ClassFileTranformer así como una Anotación. El enfoque sería crear una anotación en tiempo de ejecución que se pueda usar para marcar los métodos que está interesado en la sincronización. En el tiempo de ejecución, se especifica un ClassFileTransformer en la línea de comando y transforma el código de byte para insertar el código de tiempo.
A menos que quiera trabajar con el código byte, usar AOP es la mejor opción, pero ES posible.
AFAIK, Tomasz tiene razón al decir que esto no se puede hacer usando anotaciones. Creo que la confusión se debe al hecho de que los decoradores de Python y las anotaciones de Java comparten la misma sintaxis, pero son completamente diferentes en términos del comportamiento que ofrecen.
Las anotaciones son metadatos adjuntos a su clase / métodos / campos. Esta publicación del blog aborda el punto de los métodos de sincronización utilizando AOP. Aunque utiliza la primavera, la premisa básica sigue siendo la misma. Si eres bueno para ir con un compilador AOP, no debería ser demasiado difícil traducir el código. Otra referencia (específica de primavera) here .
EDITAR : Si su objetivo es tener un método de tiempo global para su aplicación sin utilizar perfiladores completos, puede usar hprof para recopilar las estadísticas totales de ejecución.
Como ya se dijo, no puede y AOP o hprof deberían cubrir la mayoría de sus necesidades, pero si insiste, hay una solución alternativa utilizando JSR269. Para su información, apt está obsoleto y se ha incorporado una herramienta y una API de procesamiento de anotaciones en 1.6 (y se llama con el nombre evocador JSR269).
La solución alternativa sería crear un procesador de anotaciones que genere una clase que amplíe la clase que contiene el método con la anotación @TimeIt
. Esta clase generada debe anular el método cronometrado, se verá como Python time_it
pero la línea func(*args, **kwargs)
se reemplazará por super.methodName(arg1, arg2, ...)
.
Sin embargo, hay dos advertencias:
- En otra parte de su código, debe asegurarse de crear instancias de la clase generada en lugar de la clase original. Eso es un problema porque hace referencia a una clase que aún no existe: se creará al final de la primera ronda de procesamiento.
- Tendrá que familiarizarse con los paquetes javax.annotation.processing y javax.lang.model, son un poco incómodos en mi humilde opinión.
Echa un vistazo a la biblioteca de Coda Hale Metrics . Proporciona una anotación @Timed para los métodos que proporcionan esta capacidad. Mientras está en ello, eche un vistazo a Code Hale Dropwizard, que tiene ejemplos de cómo se ha integrado en su marco de servicio.
@GET
@Timed
public Saying sayHello(@QueryParam("name") Optional<String> name) {
return new Saying(counter.incrementAndGet(),
String.format(template, name.or(defaultName)));
}
En pocas palabras: ¡no puedes!
Las anotaciones no son piezas de código que se inician automáticamente junto con su código, son solo anotaciones, piezas de información que pueden usar otros programas que trabajan en su código como cargarlo o ejecutarlo.
Lo que necesitas es AOP: programación orientada a aspectos.
Me pregunté lo mismo varias veces y terminé escribiendo el siguiente inicio:
La anotación:
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 {
}
La interfaz de un objeto:
package main;
public interface Examples {
@Clocking
void thisIsAMethod();
void thisIsAnotherMethod(String something);
@Clocking
void thisIsALongRunningMethod();
}
Un manejador de invocación:
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!");
}
}
}
Finalmente un punto de entrada para probar esto:
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();
}
}
Esto necesita una mejora, ya que requiere Proxy para crear una instancia de nuestro objeto y, por lo tanto, no puede usarlo para el código "genérico ya escrito". Pero podría llevarte a algo más completo.
Me sorprende ver que nadie señaló java.lang.reflect.Proxy. Es un hilo viejo, pero creo que esta información sería útil para alguien.
Proxy tiene una propiedad interesante que da
- Proxy instanceof Foo como cierto.
- Puede tener un método en su controlador de invocación, que imprime la hora primero y luego dispara el método real desde el objeto.
Puede tener este proxy para todos los objetos haciendo que implementen alguna interfaz o puede usar Comparable.
Busque la sección Dynamic proxies como decorador.
No es tan fácil en Java. La idea básica sería esta:
- Crear una anotación que diga "tiempo este método"
- Cree un agente java que use la transformación del código byte para: a. Encontrar métodos con la anotación b. Añadir código de tiempo a ellos
- Configure la opción javaagent cuando ejecute java para usar su nuevo agente
Este artículo lo ayudará a comenzar: http://today.java.net/pub/a/today/2008/04/24/add-logging-at-class-load-time-with-instrumentation.html .
También puede usar BTrace para que esto sea aún más fácil: http://kenai.com/projects/btrace/pages/Home