Подтвердить что ты не робот

Аннотация Java для упаковки метода

У меня есть много шаблонов, которые в основном следуют этому шаблону:

function doSomething() {
  try {
    [implementation]
    [implementation]
    [implementation]
    [implementation]
  } catch (Exception e) {
    MyEnv.getLogger().log(e);
  } finally {
    genericCleanUpMethod();
  }
}

Мне бы хотелось создать собственную аннотацию, чтобы немного очистить код:

@TryCatchWithLoggingAndCleanUp
function doSomething() {
  [implementation]
  [implementation]
  [implementation]
  [implementation]
}

Сигнатуры метода сильно различаются (в зависимости от фактической реализации метода), но окончательная часть шаблона try/catch/finally всегда одна и та же.

Аннотации, которые я имею в виду, автоматически обернут содержимое аннотированного метода целым try...catch...finally hoopla.

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

Приветствуются любые указатели на то, как я мог бы реализовать такую ​​аннотацию.

4b9b3361

Ответ 1

Для этого вам понадобится структура AOP, которая будет использовать прокси-сервер вокруг вашего метода. Этот прокси-сервер поймает исключение и выполнит блок finally. Честно говоря, если вы еще не используете фреймворк, поддерживающий AOP, я не уверен, что буду использовать его только для сохранения этих нескольких строк кода.

Вы можете использовать следующий шаблон, чтобы сделать это более элегантным способом:

public void doSomething() {
    logAndCleanup(new Callable<Void>() {
        public Void call() throws Exception {
            implementationOfDoSomething();
            return null;
        }
    });
}

private void logAndCleanup(Callable<Void> callable) {
    try {
        callable.call();
    } 
    catch (Exception e) {
        MyEnv.getLogger().log(e);
    } 
    finally {
        genericCleanUpMethod();
    }
}

Я использовал Callable<Void> как интерфейс, но вы могли бы определить свой собственный интерфейс Command:

public interface Command {
    public void execute() throws Exception;
}

и, таким образом, избежать необходимости использовать общий Callable<Void> и вернуть null из Callable.

EDIT: в случае, если вы хотите что-то вернуть из своих методов, затем создайте метод logAndCleanup() generic. Вот полный пример:

public class ExceptionHandling {
    public String doSomething(final boolean throwException) {
        return logAndCleanup(new Callable<String>() {
            public String call() throws Exception {
                if (throwException) {
                    throw new Exception("you asked for it");
                }
                return "hello";
            }
        });
    }

    public Integer doSomethingElse() {
        return logAndCleanup(new Callable<Integer>() {
            public Integer call() throws Exception {
                return 42;
            }
        });
    }

    private <T> T logAndCleanup(Callable<T> callable) {
        try {
            return callable.call();
        }
        catch (Exception e) {
            System.out.println("An exception has been thrown: " + e);
            throw new RuntimeException(e); // or return null, or whatever you want
        }
        finally {
            System.out.println("doing some cleanup...");
        }
    }

    public static void main(String[] args) {
        ExceptionHandling eh = new ExceptionHandling();

        System.out.println(eh.doSomething(false));
        System.out.println(eh.doSomethingElse());
        System.out.println(eh.doSomething(true));
    }
}

EDIT: И с Java 8 обернутый код может быть немного красивее:

public String doSomething(final boolean throwException) {
    return logAndCleanup(() -> {                
        if (throwException) {
            throw new Exception("you asked for it");
        }
        return "hello";                
    });
}

Ответ 2

Для реализации этого можно использовать динамические прокси. Это требует немного настройки, но как только это сделано, это довольно просто.

Сначала вы определяете интерфейс и размещаете аннотацию на интерфейсе.

public interface MyInterface {
    @TryCatchWithLogging
    public void doSomething();
}

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

MyInterface impl = new java.lang.reflect.Proxy.newProxyInstance(
                         Impl.class.getClassLoader(), 
                         Impl.class.getInterfaces(), YourProxy(new Impl());

Затем реализуем YourProxy.

public class YourProxy implements InvocationHandler {
....

     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         if ( method.isAnnotationPresent(TryCatchLogging.class) ) {
              // Enclose with try catch
}

Ответ 3

вы можете реализовать обработчик аннотации и аннотации самостоятельно и код инструмента каждый раз, когда выполняете компиляцию (javac -processor). Другой способ - использовать AOP, например AspectJ или Spring AOP (если вы используете Spring).

Ответ 4

afaik вам нужно будет следить за каждым вызовом метода для аннотации @TryCatchWithLoggingAndCleanUp, что было бы очень утомительно. в основном вы можете получить каждый метод аннотации путем отражения, а затем выполнять обработку исключений и протоколирование. но я не уверен, что вы захотите это сделать.