面向Java面向方面的注释编程

时间:2011-01-28 14:07:16

标签: java aop

在一篇名为"AOP Fundamentals"的帖子中,我要求 King's English 解释AOP是什么,以及它做了什么。我收到了一些非常有用的答案和链接到有助于我了解所有理论的文章。

但现在AOP得到我的全力关注,所有这些文章和章节摘录都很精彩,但在每一个案例中,它们都是由崇高的理论,模糊的UML模型和抽象的顺序组成的对我来说太高了。

以下是我对AOP理论的理解,只是为了澄清,所以如果你看到错误的东西,请告诉我!:

  1. 日志记录,身份验证,同步,验证,异常处理等交叉问题在非AOP系统中变得高度耦合,因为它们几乎被代码库中的每个组件/模块使用。 / p>

  2. AOP定义方面(类/方法),通过使用加入点建议来抽象这些横切关注点,和切入点

    一个。 建议 - 实现跨领域关注(即进行实际记录,验证,验证等)的实际代码(方面的方法,可能?)

    加入点 - 在非AOP代码中触发的事件,导致特定方面的建议被执行(“编织”到非AOP代码中)

    ℃。 Pointcut - 基本上,连接点(触发事件)到建议执行的映射

  3. 所有方面都已模块化(LoggingAspect,AuthenticationAspect,ValidationAspect等)到组件中并使用 AspectWeaver 注册。当非AOP / POJO代码遇到连接点时,AspectWeaver会围绕非AOP代码“编织”(集成)映射的建议:

  4. public class LoggingAspect
    {
        // ...
    
        public void log(String msg) { ... }
    }
    
    public class ExceptionHandlingAspect
    {
        // ..
    
        public void handle(Exception exc) { ... }
    }
    
    public class NonAOPCode
    {
        // ...
    
        @LoggingAspect @ExceptionHandlingAspect
        public void foo()
        {
            // do some stuff...
        }
    }
    
    // Now in the driver
    public static int main void(String[] args)
    {
        NonAOPCode nonAOP = new NonAOPCode();
        nonAOP.foo();
    }
    
    // The AspectWeaver *magically* might weave in method calls so main now becomes:
    {
        NonAOPCode nonAOP = new NonAOPCode();
    
        log(someMsg);
        nonAOP.foo();
        handle(someExc);
    }
    
    

    $ 64,000问题:我对基于Java的AOP的理解是针对目标还是关闭,为什么?一个正确如何使用注释来实现方面,建议,连接点,切入点和这个所谓的方面编织器?

5 个答案:

答案 0 :(得分:63)

假设您想要使用@LogExecTime注释记录一些带注释方法所花费的时间。

我首先创建一个注释LogExecTime

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogExecTime {

}

然后我定义一个方面:

@Component  // For Spring AOP
@Aspect
public class LogTimeAspect {
    @Around(value = "@annotation(annotation)")
    public Object LogExecutionTime(final ProceedingJoinPoint joinPoint, final LogExecTime annotation) throws Throwable {
        final long startMillis = System.currentTimeMillis();
        try {
            System.out.println("Starting timed operation");
            final Object retVal = joinPoint.proceed();
            return retVal;
        } finally {
            final long duration = System.currentTimeMillis() - startMillis;
            System.out.println("Call to " + joinPoint.getSignature() + " took " + duration + " ms");
        }

    }
}

我创建了一个用LogExecTime注释的类:

@Component
public class Operator {

    @LogExecTime
    public void operate() throws InterruptedException {
        System.out.println("Performing operation");
        Thread.sleep(1000);
    }
}

主要使用Spring AOP:

public class SpringMain {

    public static void main(String[] args) throws InterruptedException {
        ApplicationContext context = new GenericXmlApplicationContext("applicationContext.xml");
        final Operator bean = context.getBean(Operator.class);
        bean.operate();
    }
}

如果我运行这个类,我在stdout上得到以下输出:

Starting timed operation
Performing operation
Call to void testaop.Operator.Operate() took 1044 ms

现在使用 magic 。正如我使用Spring AOP而不是AspectJ weaver,魔术是在运行时使用proxy-ish机制发生的。因此,.class文件保持不变。例如,如果我调试这个程序并在operate中放置一个断点,你就会看到Spring如何表现出魔力:

Debug screen shot

由于Spring AOP实现是非侵入式并且使用Spring机制,您需要添加@Component注释并使用Spring上下文而不是普通new创建对象。

另一方面,AspectJ将更改.class个文件。我用AspectJ尝试了这个项目,用jad反编译了Operator类。导致:

public void operate()
    throws InterruptedException
{
    JoinPoint joinpoint = Factory.makeJP(ajc$tjp_0, this, this);
    operate_aroundBody1$advice(this, joinpoint, LogTimeAspect.aspectOf(), (ProceedingJoinPoint)joinpoint, (LogExecTime)(ajc$anno$0 == null && (ajc$anno$0 = testaop/Operator.getDeclaredMethod("operate", new Class[0]).getAnnotation(testaop/LogExecTime)) == null ? ajc$anno$0 : ajc$anno$0));
}

private static final void operate_aroundBody0(Operator ajc$this, JoinPoint joinpoint)
{
    System.out.println("Performing operation");
    Thread.sleep(1000L);
}

private static final Object operate_aroundBody1$advice(Operator ajc$this, JoinPoint thisJoinPoint, LogTimeAspect ajc$aspectInstance, ProceedingJoinPoint joinPoint, LogExecTime annotation)
{
    long startMillis = System.currentTimeMillis();
    Object obj;
    System.out.println("Starting timed operation");
    ProceedingJoinPoint proceedingjoinpoint = joinPoint;
    operate_aroundBody0(ajc$this, proceedingjoinpoint);
    Object retVal = null;
    obj = retVal;
    long duration = System.currentTimeMillis() - startMillis;
    System.out.println((new StringBuilder("Call to ")).append(joinPoint.getSignature()).append(" took ").append(duration).append(" ms").toString());
    return obj;
    Exception exception;
    exception;
    long duration = System.currentTimeMillis() - startMillis;
    System.out.println((new StringBuilder("Call to ")).append(joinPoint.getSignature()).append(" took ").append(duration).append(" ms").toString());
    throw exception;
}

private static void ajc$preClinit()
{
    Factory factory = new Factory("Operator.java", testaop/Operator);
    ajc$tjp_0 = factory.makeSJP("method-execution", factory.makeMethodSig("1", "operate", "testaop.Operator", "", "", "java.lang.InterruptedException", "void"), 5);
}

private static final org.aspectj.lang.JoinPoint.StaticPart ajc$tjp_0; /* synthetic field */
private static Annotation ajc$anno$0; /* synthetic field */

static 
{
    ajc$preClinit();
}

答案 1 :(得分:12)

几个月前,我写了一篇文章,其中有一个例子,说明我如何实现将Aspect / J方面与Java注释相结合的实际案例,您可能会发现它很有用:

http://technomilk.wordpress.com/2010/11/06/combining-annotations-and-aspects-part-1/

我相信应用于注释的方面是一个很好的组合,因为它们使代码中的方面更加明确,但是以一种干净的方式,您可以在注释中使用参数以获得更大的灵活性。

顺便说一句,Aspect / J的工作方式是在编译时修改类,而不是在运行时修改类。您可以通过Aspect / J编译器运行源和方面,并创建修改后的类文件。

Spring AOP,据我所知,它以不同的方式编织(操作类文件以包含方面处理),通过创建代理对象,我相信在实例化时(但不要接受我的话)对于它)。

答案 2 :(得分:5)

经过多次挖掘和肘部油脂后,我自己找到了答案......

是的,AOP应该是基于注释的Java世界,但是您无法处理与常规(元数据)注释等方面相关的注释。要在它之前/之后拦截标记的方法调用和“编织”建议方法,您需要一些非常漂亮的以AOP为中心的引擎(如AspectJ)的帮助。 @Christopher McCann在另一个与注释相关的主题中提供了一个非常好的解决方案,他建议将AOP联盟与Google Guice结合使用。在阅读了Guice关于AOP支持的文档之后,这正是我正在寻找的:一个易于理解的框架,用于编织交叉问题的“建议”(方法调用),例如日志记录,验证,缓存,等

这个是个傻瓜。

答案 3 :(得分:0)

更改评论

// The AspectWeaver *magically* might weave in method calls so main now becomes

// The AspectWeaver *magically* might weave in method calls so main now
// becomes effectively (the .class file is not changed)

我喜欢AOP的春季写作。查看Chapter 7

答案 4 :(得分:0)

这是我对这个非常有用的帖子的贡献。

我们将举一个非常简单的例子:我们需要对某些方法采取措施。处理。 它们使用自定义注释进行注释,其中包含要处理的数据。给定这些数据我们想要引发一个异常,或者让该过程继续进行,就像没有注释该方法一样。

用于定义方面的Java代码:

package com.example;

public class AccessDeniedForCustomAnnotatedMethodsAspect {

public Object checkAuthorizedAccess(ProceedingJoinPoint proceedingJointPoint)
throws Throwable {

    final MethodSignature methodSignature = (MethodSignature) proceedingJointPoint
                                            .getSignature();

    // how to get the method name
    final String methodName = methodSignature
                                            .getMethod()
                                            .getName();

    // how to get the parameter types
    final Class<?>[] parameterTypes = methodSignature
                                            .getMethod()
                                            .getParameterTypes();

    // how to get the annotations setted on the method
    Annotation[] declaredAnnotations = proceedingJointPoint
                                            .getTarget()
                                            .getClass()
                                            .getMethod(methodName, parameterTypes)
                                            .getDeclaredAnnotations();

    if (declaredAnnotations.length > 0) {

        for (Annotation declaredAnnotation : Arrays.asList(declaredAnnotations)) {

            // I just want to deal with the one that interests me
            if(declaredAnnotation instanceof CustomAnnotation) {

                // how to get the value contained in this annotation 
                (CustomAnnotation) declaredAnnotation).value()

                if(test not OK) {
                    throw new YourException("your exception message");
                }

                // triggers the rest of the method process
                return proceedingJointPoint.proceed();
           }
        }
    }
}

xml配置:

<aop:config>
    <aop:aspect id="accessDeniedForCustomAnnotatedMethods"
               ref="accessDeniedForCustomAnnotatedMethodsAspect">
        <aop:around pointcut="execution(@xxx.zzz.CustomAnnotation * *(..))"
               method="checkAuthorizedAccess" />
    </aop:aspect>
</aop:config>

<bean id="accessDeniedForCustomAnnotatedMethodsAspect"
   class="xxx.yyy.AccessDeniedForCustomAnnotatedMethodsAspect" />

希望它有所帮助!