Написание аннотации Java для вызова метода синхронизации

#java #annotations #timing

#java #аннотации #синхронизация

Вопрос:

Я хочу написать аннотацию 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 документацию, с ней не повезло.
может кто-нибудь помочь с написанием чего-то подобного?

Комментарии:

1. Я создаю простые инструменты обработки аннотаций декоратора, похожие на python, они преобразуют ваш метод с помощью метода-оболочки. вы можете найти на github.com/eshizhan/funcwraps .

Ответ №1:

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

Аннотации — это метаданные, прикрепленные к вашему классу / методам / полям. В этом сообщении в блоге рассматривается вопрос о методах синхронизации с использованием AOP. Хотя в нем используется Spring, основная предпосылка остается той же. Если вы хорошо разбираетесь в компиляторе AOP, перевести код не должно быть слишком сложно. Здесь еще одна ссылка (на spring).

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

Ответ №2:

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

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

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

Комментарии:

1. итак, какой самый простой способ узнать время, затраченное на какой-либо метод?

2. похоже, не сокращайте, как вы и Санджай предлагаете, изучите AOP с spring

3. другой вариант: использовать профилировщик, это намного проще, чем изучать AOP, и делает именно это: измеряет время выполнения метода (ну, не только это, он также может измерять использование памяти, находить утечки памяти, взаимоблокировки потоков и т.д.)

4. Я только что увидел, что Санджай уже предложил использовать profiler, в этом он определенно прав.

Ответ №3:

По состоянию на 2016 год существует отличная библиотека аннотаций аспектов jcabi-aspects.

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

Аннотируйте свои методы аннотацией @Loggable, и при каждом их вызове средство ведения журнала 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 здесь.

Ответ №4:

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

Аннотация:

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

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

Ответ №5:

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

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

Ответ №6:

Несмотря на все возражения, вы можете это сделать. Аннотации 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 — лучший выбор, но это возможно.

Ответ №7:

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

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

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

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

Найдите раздел Динамические прокси в качестве декоратора.

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

Ответ №8:

В Java это далеко не так просто. Основная идея была бы такой:

  1. Создайте аннотацию с надписью «время для этого метода»
  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

Ответ №9:

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

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

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

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

Комментарии:

1. да, я знаю, что apt устарел и теперь является частью 1.6.