简体   繁体   中英

Java annotation for wrapping a method

I have a lot of boilerplate code that basically follows this pattern:

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

I'd love to create my own annotation to clean my code up a bit:

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

The method signatures vary wildly (depending on the actual implementation of the method), but the boilerplate try/catch/finally part is always the same.

The annotation I have in mind would automatically wrap the contents of the annotated method with the whole try...catch...finally hoopla.

I've searched high and low for a straightforward way to do this, but have found nothing. I don't know, maybe I just can't see the woods for all the annotated trees.

Any pointers on how I might implement such an annotation would be greatly appreciated.

To do this, you would need some AOP framework that would use a proxy around your method. This proxy would catch the exception and execute the finally block. Quite frankly, if you don't use a framework supporting AOP already, I'm not sure I would use one just to save these few lines od code.

You could use the following pattern to do this in a more elegant way, though:

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

I just used Callable<Void> as an interface, but you could define your own Command interface:

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

and thus avoid the need to use a generic Callable<Void> and return null from the Callable.

EDIT: in case you want to return something from your methods, then make the logAndCleanup() method generic. Here's a complete example:

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 : And with Java 8, the wrapped code can be a bit prettier :

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

You could use dynamic proxies to implement this. It takes a bit of setting up, but once done, is pretty straightforward.

First, you define an interface and place the annotation on the interface.

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

Now, when you want to provide an implementation of the interface to a consumer, dont provide with him with the actual implementation, but instead a Proxy to it.

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

Then implement 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
}

you can implement annotation and annotation processor yourself and instrument code everytime when you do compilation ( javac -processor ). Other way is to use AOP, say AspectJ or Spring AOP (If you use Spring).

Another way is using javassist processing class file after building, your need searching methods with specified annotation in your classes. And add bridge methods for calling between wrapped method and original method. It look like, calling bridgeMethod() -> wrapperMethod() -> originalMethod() . I make a simple project for implementing that approach. Your can reference from https://github.com/eshizhan/funcwraps .

afaik you would have to monitor each method call for the @TryCatchWithLoggingAndCleanUp annotation, which would be very tedious. basically you could get each methods annotations by reflection and then do your exception handling and logging. but im not sure you would want to do that.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM