带有注解的 Java 面向方面编程

Posted

技术标签:

【中文标题】带有注解的 Java 面向方面编程【英文标题】:Java Aspect-Oriented Programming with Annotations 【发布时间】:2011-06-17 06:39:17 【问题描述】:

在题为"AOP Fundamentals" 的帖子中,我要求国王英语解释什么是AOP,以及它的作用。我收到了一些非常有用的答案和文章链接,这些文章帮助我了解了所有理论。

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

这是我对 AOP 理论的理解,只是为了澄清一下,所以如果你看到看起来不对的地方,请告诉我!:

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

    AOP 定义了 aspects(类/方法),通过使用 join pointsadviceadvice 来抽象这些横切关注点strong>切入点。

    一个。 建议 - 实现横切关注点的实际代码(可能是方面的方法?)(即执行实际的日志记录、验证、身份验证等)

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

    c。 切入点 - 本质上是连接点(触发事件)到建议执行的映射

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

公共类 LoggingAspect // ... 公共无效日志(字符串味精)... 公共类 ExceptionHandlingAspect // .. 公共无效句柄(异常除外) ... 公共类 NonAOPCode // ... @LoggingAspect @ExceptionHandlingAspect 公共无效 foo() // 做一些事情... // 现在在驱动程序中 公共静态 int 主要无效(字符串 [] 参数) NonAOPCode nonAOP = new NonAOPCode(); 非AOP.foo(); // AspectWeaver *magically* 可能会编织方法调用,因此 main 现在变为: NonAOPCode nonAOP = new NonAOPCode(); 日志(一些信息); 非AOP.foo(); 句柄(someExc);

64,000 美元的问题:我对基于 Java 的 AOP 的理解是达到目标还是偏离目标,为什么?如何正确使用注解来实现切面、建议、连接点、切入点和这种所谓的切面编织器?

【问题讨论】:

【参考方案1】:

假设您想使用@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();
    

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

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

现在有了魔法。正如我确实使用 Spring AOP 而不是 AspectJ weaver 一样,神奇之处在于使用代理机制在运行时发生。所以.class 文件保持不变。例如,如果我调试这个程序并在operate 中放置一个断点,您将看到 Spring 是如何发挥作用的:

由于 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();

【讨论】:

您的示例代码目前使用@Around(value = "@annotation(annotation)"),但我认为应该是@Around(value = "@annotation(LogExecTime)") (Spring AOP documentation)。 对我来说,它看起来像是观察者模式 (en.wikipedia.org/wiki/Observer_pattern) 的一个奇特实现。不是吗?【参考方案2】:

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

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

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

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

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

【讨论】:

Besides different compile-time weaving strategies, AspectJ also allows load-time weaving (LTW)。使用 LTW,当这个特定的类第一次被它的class loader 加载时,一个类会被修改(== 与方面交织在一起)。 LTW 可以被视为“在运行时编织”,尽管它发生在与类交互的最早点(......而不是在运行时的某个其他时间点)。【参考方案3】:

经过大量挖掘和肘部油脂,我自己找到了答案......

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

这是一个笨蛋。

【讨论】:

【参考方案4】:

更改评论

// 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

【讨论】:

请不要使用 html <code> 标签。只需将您的代码缩进 4 个空格或单击带有花括号的图标【参考方案5】:

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

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

用于定义我们方面的 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" />

希望对你有帮助!

【讨论】:

以上是关于带有注解的 Java 面向方面编程的主要内容,如果未能解决你的问题,请参考以下文章

面向切面编程及其注解

java怎么运用切面编程生成日志

Java中的面向方面编程

Spring 依赖注入怎么回事,还有面向方面编程是怎么回事

springboot 面向切面编程之使用自定义注解

如何理解spring中的切面和过滤