AspectJ 简单介绍

发布于 2023-08-11 12:55:29 字数 7837 浏览 57 评论 0

AspectJ 是 Java 中流行的 AOP(Aspect-oriented Programming)编程扩展框架,是 Eclipse 托管给 Apache 基金会的一个开源项目。俗话说得好,要学编程先写个 HelloWorld,下面我们来通过一个简单的例子来了解 AspectJ。

在动手前先准备下环境,目前国内的互联网公司的开发环境标配为:

  • JDK(最好是 1.8)
  • Maven
  • IntelliJ Idea

我们的实验环境也是如此。好,现在来进入实验流程。

假设有一个 Boy 类,它的定义如下:

public class Boy {
public void watchBasketball() {
System.out.println("Watching basketball!");
}
}​

假如我们想在调用 Boy.watchBasketball 前后打印日志,应该怎么办?最简单的办法是修改 Boy 的代码,在方法前后加入打印日志的代码。但是如果有上百个方法的话,这种办法效率太低。下面我们用 AspectJ 来实现这个功能。

首先,我们需要通过 Maven 来引入 AspectJ 的两个包:

<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>​

然后在 Maven 的 Build 过程增加编译期的 AspectJ 相关插件:

<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>aspectj-maven-plugin</artifactId>
            <version>1.7</version>
            <configuration>
                <complianceLevel>1.8</complianceLevel>
                <source>1.8</source>
                <target>1.8</target>
                <showWeaveInfo>true</showWeaveInfo>
                <verbose>true</verbose>
                <Xlint>ignore</Xlint>
                <encoding>UTF-8 </encoding>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <!-- use this goal to weave all your main classes -->
                        <goal>compile</goal>
                        <!-- use this goal to weave all your test classes -->
                        <goal>test-compile</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

最后,增加下面这个 aspect:

public aspect BoyAspect {
    // 指定执行 Boy.watchBasketball() 方法时执行下面代码块
    void around():call(void Boy.watchBasketball()){
        System.out.println("Begin watching basketball!");
        proceed();
        System.out.println("End watching basketball!");
    }
}

大功告成!我们来测试下:

public class Main {
    public static void main(String[] args) throws Exception {
        Boy boy = new Boy();
        boy.watchBasketball();
    }
}

输出结果如下所示:

Begin watching basketball!
Watching basketball!
End watching basketball!

可以看到,虽然我们还是调用 Boy.watchBasketball()方法,但是前后已经加上了打印日志,而且源代码没有任何改动!这简直太神奇了!

AspectJ 是怎么做到的呢?我们可以通过对 Main 方法进行字节码反编译一探究竟。Java 字节码反编译成源代码可以使用 http://www.javadecompilers.com/ 这个在线反编译网站,我们将 Main 方法的 class 文件反编译,得到如下源代码:

public class Main{
private static final void watchBasketball_aroundBody1$advice(Boy target, BoyAspect ajc$aspectInstance, AroundClosure ajc$aroundClosure){
System.out.println("Begin watching basketball!");
AroundClosure localAroundClosure = ajc$aroundClosure;watchBasketball_aroundBody0(target);
System.out.println("End watching basketball!");
}
private static final void watchBasketball_aroundBody0(Boy paramBoy) {
paramBoy.watchBasketball();
}
public static void main(String[] args) throws Exception{
Boy boy = new Boy();
Boy localBoy1 = boy;
watchBasketball_aroundBody1$advice(localBoy1, BoyAspect.aspectOf(), null);
}
public Main() {}
}​

咦?这不是我写的代码啊,怎么多了些奇奇怪怪的方法和代码?没错,这些代码是 AspectJ 框架生成的代码。在代码编译时,AspectJ 根据我们定义的 aspect 信息,使用字节码修改技术进行了代码增强。可以看到,在 AspectJ 修改后,在调用原始方法前后加入了打印日志的代码。

在 AOP 编程中,有如下概念:

  • JoinPoint:表示代码执行过程中的一个点,例如方法调用或者属性访问。
  • Pointcut:用来匹配多个 JoinPoint。
  • Advice:将 Pointcut 与功能增强代码联系起来,使得在程序执行过程中到达特定 JoinPoint 时执行相应的功能增强代码(例如打印日志)。
  • Aspect:将 JoinPoint、Pointcut 与 Advice 包装起来。

在上面的例子中,我们定义了 BoyAspect 这个 aspect:

public aspect BoyAspect {
    // 指定执行 Boy.watchBasketball() 方法时执行下面代码块
    void around():call(void Boy.watchBasketball()){
        System.out.println("Begin watching basketball!");
        proceed();
        System.out.println("End watching basketball!");
    }
}
  • Pointcut:call(void Boy.watchBasketball())定义了一个 Pointcut,它匹配了代码执行过程中 Boy 类执行 watchBasketball 方法的这个 JoinPoint。AspectJ 的 Pointcut 定义语法非常强大,我们可以正则表达式来匹配多个 JoinPoint,例如 call(void Boy.watch*())匹配了 Boy 类所有以 watch 开头的方法执行。
  • Advice:上面代码中使用了 aroundAdvice,也就是在对原始方法调用前后都加上了代码增强;除此之外,我们还可以用 before()、after()等这些 Advice,分别对应于只在原始调用前或者原始调用后进行代码增强。

通过使用 AspectJ,我们可以非常方便的对原始代码进行切面式的代码增强,例如对于所有类的方法调用前后都打印日志。使用 AspectJ AOP 的好处是,我们不用一个个的修改原始代码类,只用写一个 aspect,使用 Pointcut 来匹配多个类的方法执行点,再实现代码增强即可。

通过使用 AspectJ,我们还可以动态的增加原始类的方法或者字段。在下面代码中,我们对原始的 Boy 类增加了 observers 字段,并且增加了 addObserver(observer)和 removeObserver(observer)方法,使用 advice 来在调用 Boy.setxx 方法时通知观察者:

public aspect BoyAspect {
// 对 Boy 类增加 observers 字段
private Vector<Observer> Boy.observers = new Vector();
// 对 Boy 类增加 addObserver 方法
public void Boy.addObserver(Observer observer) {
observers.add(observer);
}
// 对 Boy 类增加 removeObserver 方法
public void Boy.removeObserver(Observer observer) {
observers.remove(observer);
}
// 使用 after Advice, 指定执行 Boy.setxx 方法返回时, 通知 Boy 中的所有观察者
after(Boy boy): target(boy) &amp;&amp; call(void Boy.set*(int)) {
Iterator&lt;Observer&gt; iterator = boy.observers.iterator();
while (iterator.hasNext()) {
Observer observer = iterator.next();
observer.observe(boy);
}
}
}​

为了匹配 Boy.setxx 方法,我们在 Boy 类增加一个 age 字段,并且增加 set/get 方法:

public class Boy {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}​

现在来试试效果如何:

public class Main {
public static void main(String[] args) throws Exception {
Boy boy = new Boy();
boy.addObserver(new Observer());
boy.setAge(10);
}
}​

输出结果如下所示:

Boy is changing!

鹅妹子嘤!我们并没有对原始的 Boy 类做代码修改,但是居然对于 Boy 增加了字段修改的观察者并且实现了通知!

我们对 Boy.class 进行反编译看下代码:

public class Boy { 
public static Vector ajc$get$observers(Boy paramBoy){
return observers;
}
public static void ajc$set$observers(Boy paramBoy, Vector paramVector){
observers = paramVector;
}
public void addObserver(Observer paramObserver){
BoyAspect.ajc$interMethod$AOP_BoyAspect$AOP_Boy$addObserver(this, paramObserver);
}
public void removeObserver(Observer paramObserver){
BoyAspect.ajc$interMethod$AOP_BoyAspect$AOP_Boy$removeObserver(this, paramObserver);
}
private int age;
private Vector<Observer> observers;
public Boy() {
BoyAspect.ajc$interFieldInit$AOP_BoyAspect$AOP_Boy$observers(this);
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}​

可以看到,字节码文件中的确增加了相应的 observers 字段以及 addObserver、removeObserver 方法。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

帅气称霸

暂无简介

0 文章
0 评论
23 人气
更多

推荐作者

qq_E2Iff7

文章 0 评论 0

Archangel

文章 0 评论 0

freedog

文章 0 评论 0

Hunk

文章 0 评论 0

18819270189

文章 0 评论 0

wenkai

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文