带有注解的 Java 面向切面编程 [英] Java Aspect-Oriented Programming with Annotations

查看:40
本文介绍了带有注解的 Java 面向切面编程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在一篇题为 AOP 基础知识"的帖子中,我要了 King's English 解释什么是 AOP,以及它的作用.我收到了一些非常有用的答案和文章链接,这些文章帮助我了解了所有理论.

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

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

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

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

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

    B.Join Point - 在非 AOP 代码中触发的事件,导致执行特定方面的建议(编织"到非 AOP 代码中)

    c.切入点 - 本质上,连接点(触发事件)到通知执行的映射

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

<前>公共类 LoggingAspect{//...公共无效日志(字符串味精){ ... }}公共类 ExceptionHandlingAspect{//..公共无效句柄(例外除外){ ... }}公共类 NonAOPCode{//...@LoggingAspect @ExceptionHandlingAspect公共无效 foo(){//做一些事情...}}//现在在驱动程序中public static int main void(String[] args){NonAOPCode nonAOP = new NonAOPCode();nonAOP.foo();}//AspectWeaver *神奇地*可能会编织方法调用,所以 main 现在变成:{NonAOPCode nonAOP = new NonAOPCode();日志(someMsg);nonAOP.foo();句柄(someExc);}

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

解决方案

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

我首先创建一个注解LogExecTime:

@Retention(RetentionPolicy.RUNTIME)@目标(元素类型.方法)公共@interface LogExecTime {}

然后我定义一个方面:

@Component//对于 Spring AOP@方面公共类 LogTimeAspect {@Around(value = "@annotation(annotation)")public Object LogExecutionTime(final ProceedingJoinPoint joinPoint, final LogExecTime annotation) throws Throwable {final long startMillis = System.currentTimeMillis();尝试 {System.out.println("开始定时操作");最终对象 retVal = joinPoint.proceed();返回 retVal;} 最后 {final long duration = System.currentTimeMillis() - startMillis;System.out.println("调用" + joinPoint.getSignature() + " 占用了 " + 持续时间 + " 毫秒");}}}

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

@Component公共类操作符{@LogExecTime公共无效操作()抛出 InterruptedException {System.out.println("执行操作");线程睡眠(1000);}}

还有一个使用 Spring AOP 的主程序:

public class SpringMain {public static void main(String[] args) 抛出 InterruptedException {ApplicationContext context = new GenericXmlApplicationContext("applicationContext.xml");final Operator bean = context.getBean(Operator.class);豆.操作();}}

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

开始定时操作执行操作调用 void testaop.Operator.Operate() 耗时 1044 毫秒

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

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

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

public void operation()抛出中断异常{JoinPoint joinpoint = Factory.makeJP(ajc$tjp_0, this, this);operation_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 operation_aroundBody0(Operator ajc$this, JoinPoint joinpoint){System.out.println("执行操作");线程睡眠(1000L);}private static final Object operation_aroundBody1$advice(Operator ajc$this, JoinPoint thisJoinPoint, LogTimeAspect ajc$aspectInstance, ProceedingJoinPoint joinPoint, LogExecTime annotation){long startMillis = System.currentTimeMillis();对象 obj;System.out.println("开始定时操作");ProceedingJoinPointprocedingjoinpoint = joinPoint;operation_aroundBody0(ajc$this,procedingjoinpoint);对象 retVal = null;obj = retVal;long duration = System.currentTimeMillis() - startMillis;System.out.println((new StringBuilder("Call to ")).append(joinPoint.getSignature()).append(" take ").append(duration).append("ms").toString());返回对象;异常异常;例外;long duration = System.currentTimeMillis() - startMillis;System.out.println((new StringBuilder("Call to ")).append(joinPoint.getSignature()).append(" take ").append(duration).append("ms").toString());抛出异常;}私有静态无效 ajc$preClinit(){工厂 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);}私有静态最终 org.aspectj.lang.JoinPoint.StaticPart ajc$tjp_0;/* 合成字段 */私有静态注解 ajc$anno$0;/* 合成字段 */静止的{ajc$preClinit();}

In a post entitled "AOP Fundamentals", I asked for a King's English explanation of what AOP is, and what it does. I received some very helpful answers and links to articles that helped fill me in on all the theory.

But now AOP's got my full attention, and all these articles and chapter excerpts are fantastic, but in every single case they consist of lofty theory, vague UML models, and order of abstraction that are way too high-up for my liking.

Here is my understanding of AOP theory, just to clarify, so if you see something that looks wrong, let me know!:

  1. Cross-cutting concerns such as Logging, Authenticating, Synchronizing, Validating, Exception Handling, etc. become highly-coupled in non-AOP systems as they are used universally by almost every component/module in the codebase.

  2. AOP defines aspects (classes/methods) that abstract these cross-cutting concerns with the use of join points, advice, and pointcuts.

    a. Advice - The actual code (method of an aspect, perhaps?) implementing the cross-cutting concern (i.e. doing the actual logging, validating, authenticating, etc.)

    b. Join Point - An event that is triggered in non-AOP code that causes a particular aspect's advice to be executed ("woven" into the non-AOP code)

    c. Pointcut - Essentially, a mapping of join points (triggering events) to advice execution

  3. All aspects are modularized (LoggingAspect, AuthenticationAspect, ValidationAspect, etc.) into components and registered with an AspectWeaver. When non-AOP/POJO code comes across a join point, AspectWeaver "weaves" (integrates) the mapped advice around the non-AOP code:

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

The $64,000 Question: Is my understanding of Java-based AOP on target, or way off, and why? How could one correctly use annotations to implement aspects, advice, join points, pointcuts and this so-called aspect weaver?

解决方案

Let's imagine you want to log the time taken by some annoted methods using a @LogExecTime annotation.

I first create an annotation LogExecTime:

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

}

Then I define an aspect:

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

    }
}

I create a class annoted with LogExecTime:

@Component
public class Operator {

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

And a main using 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();
    }
}

If I run this class I'm getting the following output on stdout:

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

Now with the magic. As I did use Spring AOP rather than AspectJ weaver, the magic is occurring at run time using proxy-ish mechanisms. So the .class files are left untouched. For instance if I debug this program and put a breakpoint in operate you'll see how Spring has performed the magic:

As Spring AOP implementation is non-intrusive and uses the Spring mechanisms you need to add the @Component annotation and create the object using Spring context rather than plain new.

AspectJ on the other side will change the .class files. I tried this project with AspectJ and decompiled the Operator class with jad. Which lead to:

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

这篇关于带有注解的 Java 面向切面编程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆