38

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.

peterh
  • 11,875
  • 18
  • 85
  • 108
TroutKing
  • 817
  • 1
  • 9
  • 15
  • It seems to me an annotation would be unnecessary for this; can you just pass implementations that define `implementationOfDoSomething()` and (optionally, perhaps) `genericCleanUpMethod()` as arguments to `doSomething()`, call them within the `try/catch/finally`, and then just invoke `doSomething()` whenever you need the boilerplate logic? – Rob Hruska Dec 28 '11 at 15:46
  • 1
    Your answer lies in AOP, have you researched this? – smp7d Dec 28 '11 at 15:49
  • My understanding is that he has this same try/catch/finally across multiple methods. So there isn't just a single doSomething, but more like doSomething1, doSomething2, ... all with that same try/catch/finally that he wants to extract to the annotation – jeff Dec 28 '11 at 15:50
  • @jeff - Right, but my argument is that the boilerplate (`doSomething()`) could be generalized into library code that's invoked from, say, `doSomething1()` and `doSomething2()`. – Rob Hruska Dec 28 '11 at 15:51
  • Thanks for the comments so far. I've updated the original question: the methods themselves contain the implementation, they don't just consist of a single method call to the actual implementation. Poor sample code on my part. – TroutKing Dec 28 '11 at 16:14

5 Answers5

22

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";                
    });
}
Pierre Henry
  • 16,658
  • 22
  • 85
  • 105
JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • +1 - This is what I was getting at in my comments on the question. – Rob Hruska Dec 28 '11 at 16:20
  • This is very similar to my own "Plan B" solution, should annotations end up being impractical. Which would seem to be the case. Thanks very much! – TroutKing Dec 28 '11 at 16:33
  • Thanks for this, works great if you're not returning anything, but what if `callable` needs to return something. Do you have to create a `ExecutorService` as outlined here?: http://stackoverflow.com/a/5516955/293280 – Joshua Pinter Jul 18 '14 at 21:49
  • @JoshPinter You can just change the signature of logAndCleanup to `private T logAndCleanup(Callable callable)` – JB Nizet Jul 19 '14 at 05:49
  • @JBNizet Thanks, JB. Do you mind elaborating with a little more code regarding the interface alternative? It looks like a cleaner approach but I'm not quite following with you what you've added in the post. Many Thanks! – Joshua Pinter Jul 19 '14 at 23:12
  • @JoshPinter: See a complete example in my edited answer. – JB Nizet Jul 20 '14 at 07:37
18

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
}
Kal
  • 24,724
  • 7
  • 65
  • 65
3

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).

korifey
  • 3,379
  • 17
  • 17
0

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.

eshizhan
  • 4,235
  • 2
  • 23
  • 23
0

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.