Search code examples
javaexceptionannotationstry-catch

Using annotations for exception handling?


Let's say I have a method that throws an Exception of some kind. The exception-throwing code lies in a third-party library that access an external service. I have a few classes that do a good deal of work with external services and there is a LOT of exception handling throughout to deal with potential problems. The issue I'm hitting is that I may have a lot of exceptions, but I may only need to perform one of a few actions if there is one, and there are a ton of try/catch blocks peppered about. The type of exception may not even be relevant, or different methods may throw the same type of exception, but different actions need to be taken depending on the method throwing it.

What I'm looking for is an annotation that can supplant try/catch and simply dictate the behavior to be taken when there is an exception in that method. I know that Spring ApsectJ can do this sort of thing, but I'm not currently able to easily add any new dependencies or modify the pom to adjust existing ones. As such, I'm hoping that this can be accomplished with a custom annotation. For example:

@Catcher(action=SomeEnum.SOME_ACTION)
public void doSomething(ServiceObj obj) throws SomeException {
    ExternalService.makeThingsHappen(obj);
}

I would presume that a separate class would handle exceptions, of course. An additional difficulty is that I would need the ServiceObj that is passed as well. If makeThingsHappen() fails, I may need obj to perform additional actions. The action variable will tell the handler class what to do with obj.

Can this be done without severe muckery, or am I hoping for something that may not exist?


Solution

  • This should be a low-level process. That doesn't mean we cannot have the same thing with the current level, but it may need a bunch of code and would complicate the system a little. However my suggestion would be like this (I hope I got it correct), first define an interface for who wants to process exceptions, something like this:

    interface ExceptionHandler{
      void handleException(Throwable t);
    }
    

    then provide an annotation for the user (API) to mark that its methods may throw some exception.

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    @interface Catch{
      public Class<? extends ExceptionHandler> targetCatchHandler();
      public Class<? extends Throwable> targetException() default Exception.class;
    }
    
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    @interface CatchGroup{
      public Catch[] catchers();
    }
    

    Next we need an interface to start to call the method which may throw an exception, something like this:

    interface Caller{
      void callMethod()throws Throwable;
    }
    

    Then you need a guy who manages the flow of the execution, and calls the possible exception handler.

    class MethodCaller{
      /*
       * @param isntance: instance which implemented the Caller interface
       */
      public static void callMethod(Caller instance)
          throws Exception {
        Method m = instance.getClass().getMethod("callMethod");
        Annotation as[] = m.getAnnotations();
        Catch[] li = null;
        for (Annotation a : as) {
          if (a.annotationType().equals(CatchGroup.class)) {
            li = ((CatchGroup) a).catchers();
          }
          // for(Catch cx:li){cx.targetException().getName();}
        }
        try {
          instance.callMethod();
        } catch (Throwable e) {
          Class<?> ec = e.getClass();
          if (li == null) {
            return;
          }
          for (Catch cx : li) {
            if (cx.targetException().equals(ec)) {
              ExceptionHandler h = cx.targetCatchHandler().newInstance();
              h.handleException(e);
              break;
            }
          }
        }
      }
    }
    

    And finally, let's have an example. It works very well for me, it's cool. The exception handler:

    public class Bar implements ExceptionHandler{//the class who handles the exception
      @Override
      public void handleException(Throwable t) {
        System.out.println("Ta Ta");
        System.out.println(t.getMessage());
      }
    }
    

    and the method caller:

    class Foo implements Caller{//the class who calls the method
      @Override
      @CatchGroup(catchers={ 
          @Catch(targetCatchHandler=Bar.class,targetException=ArithmeticException.class),
          @Catch(targetCatchHandler=Bar.class,targetException=NullPointerException.class)})
      public void callMethod()throws Throwable {
        int a=0,b=10;
        System.out.println(b/a);
      }
      public static void main(String[] args) throws Exception {
        Foo foo=new Foo();
        MethodCaller.callMethod(foo);
      }
    }
    

    As you see, the user HAS TO call the methods by the callMethod() method. You would also omit the Caller interface, and use an annotation to declare more than one method in a class that needs a bunch of extra codez. I hope I was able to give you a hand.