Написание Java-аннотации для вызова метода синхронизации - PullRequest
24 голосов
/ 21 апреля 2011

Я хочу написать аннотацию java, которая будет умножать время вызова метода.что-то вроде этого:

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

и когда этот метод вызывается, он должен вывести на консоль, сколько времени этот метод занял

Я знаю, как это сделать в Python, это то, что я хочуэто сделать:

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?")

Итак, мои вопросы?Где найти документацию, чтобы написать что-то вроде этого, я попробовал документацию apt, но мне не повезло.Может кто-нибудь помочь с написанием что-то вроде этого?

Ответы [ 9 ]

9 голосов
/ 21 апреля 2011

AFAIK, Томаш прав, говоря, что это невозможно сделать с помощью аннотаций. Я думаю, что путаница проистекает из того факта, что декораторы Python и аннотации Java имеют один и тот же синтаксис, но совершенно различаются с точки зрения предлагаемого ими поведения!

Аннотации - это метаданные, прикрепленные к вашему классу / методам / полям. В этом блоге рассматриваются методы определения времени с использованием АОП. Хотя он использует Spring, основная предпосылка остается той же. Если вы хорошо разбираетесь в компиляторе AOP, вам не составит труда перевести код. Другая ссылка (специфическая для пружины) здесь .

РЕДАКТИРОВАТЬ : Если ваша цель состоит в том, чтобы иметь общее время метода для вашего приложения без использования полнофункциональных профилировщиков, вы можете использовать hprof для сбора общей статистики выполнения.

7 голосов
/ 21 апреля 2011

Проще говоря: вы не можете!

Аннотации - это не фрагменты кода, которые автоматически запускаются вместе с вашим кодом, а просто аннотации, фрагменты информации, которые могут использоваться другими программами, работающими с вашим кодом, такими как загрузка или запуск.

Вам нужно AOP: аспектно-ориентированное программирование.

5 голосов
/ 21 марта 2016

Начиная с 2016 года, имеется отличная библиотека аннотаций аспектов jcabi-aspect .

Из документов:

Аннотируйте свои методы с помощью @Записываемая в журнал аннотация и каждый раз, когда она вызывается, ваше средство ведения журнала SLF4J будет получать сообщение с подробной информацией о выполнении и общим временем выполнения:

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

Примерно так будет отображаться в журнале:

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

Подробнее о @Loggable здесь .

3 голосов
/ 08 февраля 2013

Ознакомьтесь с библиотекой Coda Hale Metrics .Он предоставляет аннотацию @Timed для методов, обеспечивающих эту возможность.Пока вы здесь, посмотрите Code Hale Dropwizard , в котором есть примеры того, как он был интегрирован в их сервисную среду.

@GET
@Timed
public Saying sayHello(@QueryParam("name") Optional<String> name) {
    return new Saying(counter.incrementAndGet(),
                      String.format(template, name.or(defaultName)));
}
2 голосов
/ 19 января 2017

Я несколько раз удивлялся одной и той же вещи и закончил записью следующего начала:

Аннотация:

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 {

}

Интерфейс объекта:

package main;

public interface Examples {
    @Clocking
    void thisIsAMethod();

    void thisIsAnotherMethod(String something);

    @Clocking
    void thisIsALongRunningMethod();
}

Обработчик вызова:

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!");
        }
    }
}

Наконец, точка входа для проверки этого:

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();
    }
}

Это требует улучшения, так как требует, чтобы Proxy создавал наш объект, и поэтому вы не можете использовать его для "общего уже написанного" кода. Но это может привести вас к чему-то более полному.

2 голосов
/ 07 апреля 2016

Я удивлен, увидев, что никто не указал на java.lang.reflect.Proxy. Это старая ветка, но я думаю, что эта информация будет кому-то полезна.

Прокси обладает интересным свойством, которое дает

  1. экземпляр прокси Foo как истина.
  2. В обработчике вызовов может быть метод, который сначала печатает время, а затем запускает фактический метод из объекта.

Вы можете использовать этот прокси для всех объектов, заставив их реализовать некоторый интерфейс, или вы можете использовать Comparable.

Ищите раздел Динамические прокси как декоратор.

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

2 голосов
/ 21 апреля 2011

Несмотря на все высказывания, вы можете сделать это. Java-аннотации не могут изменить исходные файлы или файлы классов, с которыми они работают, поэтому вы можете выбрать следующие варианты:

1) Используйте суперкласс. Процессор аннотаций может генерировать суперкласс, который умножается на абстрактный метод. Ваш реальный класс реализует этот метод. Недостатком является то, что метод, который вы хотите время, должен быть переименован, чтобы суперкласс мог обеспечить реализацию. Результат может выглядеть следующим образом

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

и процесс аннотации сгенерирует:

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

Используя соглашение об именах, чтобы указать, какие методы должны быть синхронизированы в качестве префикса "bench_", был использован в моем примере.

2) Используйте ClassFileTranformer, а также аннотацию Подход заключается в создании аннотации времени выполнения, которую можно использовать для обозначения методов, которые вас интересуют во времени. Во время выполнения в командной строке указывается ClassFileTransformer, который преобразует байтовый код для вставки временного кода.

Если вам не нравится работать с байт-кодом, лучше использовать AOP, но это возможно IS .

1 голос
/ 04 августа 2012

Это не так просто в Java.Основная идея заключается в следующем:

  1. Создание аннотации с надписью "time this method"
  2. Создание агента Java, который использует преобразование байтового кода для: a.Найти методы с аннотацией b.Добавьте к ним временной код
  3. Установите параметр javaagent при запуске java для использования нового агента

Эта статья поможет вам начать: http://today.java.net/pub/a/today/2008/04/24/add-logging-at-class-load-time-with-instrumentation.html.

Вы также можете использовать BTrace, чтобы сделать это еще проще: http://kenai.com/projects/btrace/pages/Home

0 голосов
/ 21 апреля 2011

Как уже говорилось, вы не можете, и AOP или hprof должны удовлетворить большинство ваших потребностей, но если вы настаиваете, есть обходной путь, использующий JSR269.К вашему сведению, apt устарел, а API и инструмент для обработки аннотаций были включены в 1.6 (и он вызывается с вызывающим именем JSR269).

Обходной путь может заключаться в создании процессора аннотаций, который генерирует расширяемый класскласс, который содержит метод с аннотацией @TimeIt.Этот сгенерированный класс должен переопределить метод timed, он будет выглядеть как Python time_it, но строка func(*args, **kwargs) будет заменена на super.methodName(arg1, arg2, ...).

Однако есть два предостережения:

  1. В другом месте вашего кода вы должны быть уверены, что создаете экземпляры сгенерированного класса вместо исходного класса.Это проблема, потому что вы ссылаетесь на класс, который еще не существует: он будет создан в конце первого раунда обработки.
  2. Вам нужно будет ознакомиться с javax.annotation.processing и javax.пакеты lang.model, они немного неловкие ИМХО.
...