有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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

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

但现在AOP得到了我的充分关注,所有这些文章和章节节选都非常精彩,但在每一个案例中,它们都由崇高的理论、模糊的UML模型和抽象顺序组成,这对我来说太高了

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

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

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

    a.建议——实际的代码(可能是方面的方法?)实现交叉关注点(即进行实际的日志记录、验证、身份验证等)

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

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

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

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);
}

64000美元的问题:我对基于Java的AOP的理解是否达到了目标,还是远远不够,为什么?如何正确地使用注释来实现方面、建议、连接点、切入点和所谓的方面编织器


共 (4) 个答案

  1. # 1 楼答案

    更改评论

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

  2. # 2 楼答案

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

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

    Debug screen shot

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

    另一侧的AspectJ将更改.class文件。我用AspectJ尝试了这个项目,用jad反编译了操作符类。这导致:

    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();
    }
    
  3. # 3 楼答案

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

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

    定义方面的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" />
    

    希望有帮助

  4. # 4 楼答案

    经过大量的挖掘和努力,我自己找到了答案

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

    这是一个令人毛骨悚然的故事