【问题标题】:Modify a method using Annotations使用注解修改方法
【发布时间】:2011-01-31 13:34:45
【问题描述】:

如何更改 Java 中的方法正在执行的操作?

我的意思是,我正在尝试使用注释来制作以下代码

@Anno1(Argument = "Option1")
public class TestClass
{       
    @Anno2
    public void test()
    {
    }

}

进入

public class TestClass
{
    private static StaticReference z;

    public void test()
    {
           z.invokeToAll();
    }

}

这是我正在尝试做的一个非常简化的示例。 Anno1 会有很多可能的组合,但到目前为止这不是我的问题。我的问题是如何将代码添加到方法test()

如果可能,我正在寻找更通用的解决方案。例如。一种在方法中添加各种代码的方法(不仅仅是.invokeToAll()的一种方法)

到目前为止,我正在使用import javax.annotation.processing.*;,并且我有以下代码,但我不知道如何从那里继续

private void processMethodAnnotations(RoundEnvironment env)
{
    for (Element e : env.getElementsAnnotatedWith(Anno2.class))
    {
        //If it is a valid annotation over a method
        if (e.getKind() == ElementKind.METHOD) 
        {
            //What to do here :S
        }else
        {
            processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,"Not a method!", e);               
        }           
    }
}

我找到了一些关于 Java 反射的东西,但我没有找到任何资源来帮助我做我正在做的事情。

显然我在我的代码中extends AbstractProcessor

我找到了本教程 (http://www.zdnetasia.com/writing-and-processing-custom-annotations-part-3-39362483.htm) 但这涉及创建一个新类,而不仅仅是更改方法.并且javax.lang.model.elements 不提供任何编辑该元素的方式(在我的例子中代表一个方法)。

我希望我的问题是明确的并且符合规则。如果不是,请发表评论,我会澄清。谢谢。

【问题讨论】:

    标签: java reflection annotations


    【解决方案1】:

    注释处理不适合你,来自Wikipedia

    编译Java源代码时, 注释可以被处理 编译插件称为注解 处理器。处理器可以生产 信息性消息或创建 额外的 Java 源文件或 资源,这反过来可能是 编译处理,但注解 处理器不能修改带注释的 代码本身。

    人们建议你正确的方式 - AOP。具体来说,您可以使用 AspectJ。 “快速结果”方式是(如果您使用 Eclipse):

    1) 安装AJDT(AspectJ 开发工具)
    2) 创建 AspectJ 项目并在其中添加您的类和注释
    3)创建方面:

    public aspect Processor {
    
        private StaticReference z;
    
        pointcut generic()
                // intercept execution of method named test, annotated with @Anno1
                // from any class type, annotated with @Anno2
            : execution(@Anno2 * (@Anno1 *).test())
                // method takes no arguments
            && args ();
    
        // here you have write what you want method actually does
        void around () : generic()  {
            z.invokeToAll();
        }
    
    
    }
    

    现在你可以执行一个测试,你会看到它可以工作;)AJDT会自动为你编译代码,所以不需要任何手动工作,希望这就是你所说的“魔法”;)

    更新:

    如果您在 test() 方法中的代码依赖于 Anno1 注释值,那么在内部方面,您可以获得以这种方式执行的类注释:

    void around () : generic()  {
    
        Annotation[] classAnnotations = thisJoinPoint.getThis().getClass().getAnnotations();
    
        String ArgumentValue = null;
        for ( Annotation annotation : classAnnotations ) {
            if ( annotation instanceof Anno1 ) {
                ArgumentValue = ((Anno1) annotation).Argument(); 
                break;
            }
        }
    
        if ( ArgumentValue != null && ArgumentValue.equals("Option1")) {
            z.invokeToAll();
        }
    
    }
    

    其中thisJoinPoint 是一个特殊的引用变量。

    更新 2:

    如果你想在你的方面添加System.out.println( this ),你需要在那里写System.out.println( thisJoinPoint.getThis() ),刚刚测试过,它可以工作。 thisJoinPoint.getThis() 返回“这个”但不完全是;事实上,这是 Object 变量,如果你想获得任何属性,你需要强制转换或使用反射。并且thisJoinPoint.getThis() 不提供对私有属性的访问。

    好吧,现在看来您的问题已得到解答,但如果我遗漏了什么,或者您通过这种方式遇到了其他问题/问题 - 请随时提问;)

    【讨论】:

    • 你的解决方案似乎是我想要的,不幸的是由于奇怪的原因我可能不会使用它。我在其他地方找到了解决方案 (Javassist)。无论如何,我已经给了你+1,现在已经接受了努力和好的答案。非常感谢!
    • @Muggen hm,你可以告诉我“奇怪的原因”,这样我就可以看看它们是否真的重要;也许还有另一种坚持使用 AOP 的方法(它看起来更容易支持..)
    • 那些奇怪的原因是Academic Restrictions ;)。不过感谢您的帮助。
    • @Muggen,我明白,但无论如何有趣的是什么可以限制。真的,只是想知道 - 您应该只使用 Java(因此 AspectJ 被视为“不同的”语言),还是您不必寻找简单的解决方案? ;)
    【解决方案2】:

    完全可以按照您的要求去做,尽管有一个警告:依赖私有编译器 API。听起来很吓人,但实际上并非如此(编译器实现往往是稳定的)。

    有一篇论文解释了这个过程:The Hacker's Guide to Javac

    值得注意的是,Project Lombok 使用它来提供自动 getter/setter 生成(除其他外)。 following article 解释了它是如何做到的,基本上是在重复上述论文所说的内容。

    【讨论】:

      【解决方案3】:

      好吧,您可能会看看以下样板代码是否有用:

      public void magic(Object bean, String[] args) throws Exception {
          for (Method method : bean.getClass().getDeclaredMethods()) {
              if (method.isAnnotationPresent(Anno2.class)) {
                  // Invoke the original method
                  method.invoke(bean, args);
                  // Invoke your 'z' method
                  StaticReference.invokeAll();
              }
          }
      }
      

      作为替代方案,您可以使用面向方面的编程,例如您有AspectJ 项目。

      【讨论】:

      • 嗯,很聪明,但是如果我想代替z.invokeToAll(); 做一些类似System.out.println(this); 的事情,如果可能的话,我正在寻找更通用的解决方案。
      • 打印this表示调用TestClass.toString()。如果您愿意,您仍然可以这样做。此外,通过反射,您可以访问带注释的类的所有属性。
      【解决方案4】:

      我完全不确定是否可以通过注释更改源代码或字节码。从您的描述来看,aspect oriented programming 似乎可以为您的问题提供解决方案。

      您的注释非常类似于 pointcut 概念(它们标记需要插入代码的位置)并且插入的代码接近 advice 概念。

      另一种方法是将 java 源文件解析为抽象语法树,修改此 AST 并序列化为 java 编译器输入。

      【讨论】:

      • AOP 是通往这里的道路 (+1)。修改 AST 是可能的(我已经成功完成了),但这是一个肮脏的 hack,它依赖于可能会更改的未记录的内部 API
      【解决方案5】:

      如果您的类扩展了合适的接口,您可以将其包装在 DynamicProxy 中,它将所有调用委托给原始方法,除了对测试的调用。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-08-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多