tutorial - java execute code on annotation




Annotazione Java per il wrapping di un metodo (3)

Ho un sacco di codice boilerplate che fondamentalmente segue questo modello:

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

Mi piacerebbe creare la mia annotazione per pulire un po 'il mio codice:

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

Le firme del metodo variano in modo selvaggio (a seconda dell'implementazione effettiva del metodo), ma il boilerplate try / catch / finally part è sempre lo stesso.

L'annotazione che ho in mente avvolgerebbe automaticamente il contenuto del metodo annotato con l'intero try...catch...finally hoopla.

Ho cercato in alto e in basso per un modo semplice per farlo, ma non ho trovato nulla. Non so, forse non riesco a vedere i boschi per tutti gli alberi annotati.

Qualsiasi suggerimento su come implementare questa annotazione sarebbe molto apprezzato.


È possibile utilizzare i proxy dinamici per implementare questo. Ci vuole un po 'di configurazione, ma una volta fatto, è piuttosto semplice.

Innanzitutto, definisci un'interfaccia e posiziona l'annotazione sull'interfaccia.

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

Ora, quando si desidera fornire un'implementazione dell'interfaccia a un consumatore, non fornire con lui l'effettiva implementazione, ma piuttosto un Proxy ad esso.

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

Quindi implementa 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
}

è possibile implementare autonomamente annotazioni e processori di annotazione e codice strumento ogni volta che si esegue la compilazione ( javac -processor ). L'altro modo è usare AOP, ad esempio AspectJ o Spring AOP (se usi Spring).


Per fare ciò, è necessario un framework AOP che utilizzi un proxy attorno al proprio metodo. Questo proxy catturerebbe l'eccezione ed eseguirà il blocco finale. Francamente, se non usi già un framework che supporti AOP, non sono sicuro che ne userei uno solo per salvare queste poche righe di codice.

Puoi usare il seguente schema per farlo in un modo più elegante, però:

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

Ho appena usato Callable<Void> come interfaccia, ma potresti definire la tua interfaccia di Command :

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

e quindi evitare la necessità di usare un Callable<Void> generico Callable<Void> e restituire null dal Callable.

EDIT: nel caso in cui si desideri restituire qualcosa dai propri metodi, quindi rendere generico il metodo logAndCleanup() . Ecco un esempio completo:

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: E con Java 8, il codice spostato può essere un po 'più carino:

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




annotations