Java-аннотация для переноса метода - PullRequest
27 голосов
/ 28 декабря 2011

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

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 шумихой.

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

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

Ответы [ 4 ]

21 голосов
/ 28 декабря 2011

Чтобы сделать это, вам понадобится некоторая 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.

РЕДАКТИРОВАТЬ: если вы хотите вернуть что-то из ваших методов, то сделайте метод logAndCleanup() универсальным. Вот полный пример:

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

РЕДАКТИРОВАТЬ: И с Java 8, упакованный код может быть немного красивее:

public String doSomething(final boolean throwException) {
    return logAndCleanup(() -> {                
        if (throwException) {
            throw new Exception("you asked for it");
        }
        return "hello";                
    });
}
17 голосов
/ 28 декабря 2011

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

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

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 голосов
/ 28 декабря 2011

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

0 голосов
/ 28 декабря 2011

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...