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

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

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

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

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

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

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

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

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

  3. 所有方面(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 的理解是达到目标还是偏离目标,为什么?如何正确使用注解来实现切面、建议、连接点、切入点和这种所谓的切面编织器?

【问题讨论】:

    标签: 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();
    }
    

    【讨论】:

    【解决方案2】:

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

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

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

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

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

    【讨论】:

    【解决方案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" />
      

      希望对你有帮助!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-06-13
        • 2010-11-24
        • 2010-09-18
        • 2011-06-29
        • 2010-09-08
        • 2015-04-28
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多