通过自定义注释进行代码注入

时间:2013-10-29 15:36:49

标签: java annotations

这是我的用例:

我需要在给定类的每个方法之前和之后执行一些泛型操作,这是基于方法的参数。例如:

void process(Processable object) {
    LOGGER.log(object.getDesc());
    object.process();
}

class BaseClass {

    String method1(Object o){ //o may or may not be Processable(add process logic only in former case)
        if(o intstanceof Prcessable){
            LOGGER.log(object.getDesc());
            object.process();
        }
        //method logic
    }
}

我的BaseClass有很多方法,我知道相同的功能将在未来添加到几个类似的类中。 有类似以下内容吗?

@MarkForProcessing
String method1(@Process Object o){ 
    //method logic
}

PS:可以使用AspectJ / guice吗?还想知道如何从头开始实现这一点以便理解。

编辑:忘记提及,我尝试了什么。(不完整或正在工作)

public @interface MarkForProcessing {
    String getMetadata();
}


    final public class Handler {
        public boolean process(Object instance) throws Exception {
            Class<?> clazz = instance.getClass();
            for(Method m : clazz.getDeclaredMethods()) {
                if(m.isAnnotationPresent(LocalSource.class)) {
                    LocalSource annotation = m.getAnnotation(MarkForProcessing.class);

                    Class<?> returnType = m.getReturnType();
                    Class<?>[] inputParamTypes = m.getParameterTypes();
                    Class<?> inputType = null;

                    // We are interested in just 1st param
                    if(inputParamTypes.length != 0) {
                        inputType = inputParamTypes[0];
                    }
                   // But all i have access to here is just the types, I need access to the method param.
                }
                return false;
            }
            return false;
        }

1 个答案:

答案 0 :(得分:3)

是的,可以做到。是的,您可以使用AspectJ。不,Guice只与这个问题切线相关。

传统的方面方法创建了一个代理,它基本上是您给它的类的子类(例如BaseClass的子类),但该子类是在运行时创建的。子类委托所有方法的包装类。但是,在创建此新子类时,您可以指定在对包装类的调用之前或之后(或两者)添加一些额外行为。换句话说,如果你有:

public class Foo() {

  public void doFoo() {...}

}

然后动态代理将是在运行时创建的Foo的子类,类似于:

public class Foo$Proxy {

  public void doFoo() {
    //Custom pre-invocation code
    super.doFoo();
    //Custom post-invocation code
  }

}

实际上,创建动态代理是一个神奇的过程,称为字节码操作。如果您想自己动手,可以使用cglibasm等工具。或者您可以使用JDK dynamic proxies。 JDK代理的主要缺点是它们只能包装接口。

AspectJ这样的AOP工具在原始字节码操作之上提供了一个抽象,用于执行上述操作(您可以使用字节码操作做很多事情,在所有方面允许的方法之前和之后添加行为)。通常,他们定义'Aspect',它们是具有称为'advice'的特殊方法的类,以及定义何时应用该建议的'切入点'。换句话说,你可能有:

@Aspect
public class FooAspect {

    @Around("@annotation(MarkForProcessing)")
    public void doProcessing(final ProceedingJoinPoint joinPoint) throws Throwable 
    {
        //Do some before processing
        joinPoint.proceed(); //Invokes the underlying method
        //Do some after processing
    }

}

方面是FooAspect,建议是doProcessing,切入点是“@annotation(MarkForProcessing)”,它匹配所有使用@MarkForProcessing注释的方法。值得指出的是,ProceedingJoinPoint将引用实际参数值(与java.lang.reflect.Method不同)

最后一步实际上是将您的方面应用于您班级的实例。通常,这可以用容器(例如Guice或Spring)完成。大多数容器都有一些方法可以了解方面的集合以及何时将它们应用于由该容器构造的类。您也可以通过编程方式执行此操作。例如,使用AspectJ,您可以:

AspectJProxyFactory factory = new AspectJProxyFactory(baseClassInstance); 
factory.addAspect(FooAspect.class);
BaseClass proxy = factory.getProxy();

最后,但并非最不重要的是,有一些AOP实现使用编译时“编织”,这是对应用方面的类文件运行的第二个编译步骤。换句话说,您不必执行上述操作或使用容器,方面将被注入到类文件本身。