【问题标题】:Java annotation for wrapping a method用于包装方法的 Java 注释
【发布时间】:2012-01-29 06:51:47
【问题描述】:

我有很多基本上遵循这种模式的样板代码:

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 hoopla 包装被注解的方法的内容。

我到处寻找一种直接的方法来做到这一点,但一无所获。我不知道,也许我只是看不到所有带注释的树木的树林。

任何关于我如何实现这种注释的指针将不胜感激。

【问题讨论】:

  • 在我看来,注释是不必要的;您可以将定义implementationOfDoSomething() 和(可能)genericCleanUpMethod() 作为参数的实现传递给doSomething(),在try/catch/finally 中调用它们,然后在需要样板逻辑时调用doSomething()?跨度>
  • 你的答案在于AOP,你研究过这个吗?
  • 我的理解是他在多个方法中都有相同的try/catch/finally。所以不仅仅是一个 doSomething,而是更像 doSomething1、doSomething2、... 都具有相同的 try/catch/finally,他想要提取到注释中
  • @jeff - 对,但我的论点是样板文件 (doSomething()) 可以概括为从 doSomething1()doSomething2() 调用的库代码。
  • 感谢到目前为止的 cmets。我已经更新了原始问题:方法本身包含实现,它们不仅仅包含对实际实现的单个方法调用。我的示例代码很糟糕。

标签: java methods annotations


【解决方案1】:

为此,您需要一些 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&lt;Void&gt;作为接口,但你可以定义自己的Command接口:

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

从而避免使用泛型 Callable&lt;Void&gt; 并从 Callable 返回 null。

编辑:如果你想从你的方法中返回一些东西,那么让 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";                
    });
}

【讨论】:

  • +1 - 这就是我在我的 cmets 中得到的问题。
  • 这与我自己的“B 计划”解决方案非常相似,如果注释最终变得不切实际。情况似乎如此。非常感谢!
  • 谢谢你,如果你不返回任何东西,效果很好,但如果callable 需要返回一些东西怎么办。您是否必须按照此处所述创建ExecutorService?:stackoverflow.com/a/5516955/293280
  • @JoshPinter 你可以把logAndCleanup的签名改成private &lt;T&gt; T logAndCleanup(Callable&lt;T&gt; callable)
  • @JBNizet 谢谢,JB。你介意用更多关于接口替代的代码来详细说明吗?这看起来是一种更简洁的方法,但我并没有完全理解你在帖子中添加的内容。非常感谢!
【解决方案2】:

您可以使用动态代理来实现这一点。这需要一些设置,但一旦完成,就非常简单了。

首先,你定义一个接口并在接口上放置注解。

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】:

    您可以在每次编译时自行实现注释和注释处理器以及仪器代码 (javac -processor)。其他方法是使用 AOP,比如 AspectJ 或 Spring AOP(如果您使用 Spring)。

    【讨论】:

      【解决方案4】:

      afaik,您必须监视 @TryCatchWithLoggingAndCleanUp 注释的每个方法调用,这将非常乏味。基本上你可以通过反射获得每个方法的注释,然后进行异常处理和日志记录。但我不确定你是否愿意这样做。

      【讨论】:

        【解决方案5】:

        另一种方法是在构建后使用javassist 处理类文件,您需要在类中搜索具有指定注释的方法。并添加桥接方法用于在包装方法和原始方法之间进行调用。看起来,打电话给bridgeMethod() -&gt; wrapperMethod() -&gt; originalMethod()。我做了一个简单的项目来实现这种方法。您可以参考https://github.com/eshizhan/funcwraps

        【讨论】:

          猜你喜欢
          • 2013-11-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-11-18
          相关资源
          最近更新 更多