AspectJ中的类型间声明(成员注入)

在上一篇博客初窥AspectJ中,咱们提到AspectJ给java提供了三种新的结构,pointcut,advice以及inter-type declaration(ITD),并且咱们经过一个简单的Demo介绍了如何使用pointcut和advice。而本文将介绍inter-type declaration是什么,能够作什么,最后一样会经过一个Demo来介绍如何使用。后文将主要用ITD来表示inter-type declaration。html

本文中Demo的代码能够在github aspect-demo中找到。java

<!--more-->git

ITD与成员注入

inter-type declaration (ITD),翻译成中文是类型间声明。即便看到中文翻译,相信你们仍是一头雾水,不知所云,因此我不是很喜欢对一些英文名字,尤为是技术名字进行生硬的翻译,这只会增长你们的理解负担。其实,换一种说法可能更好理解,member introduction(成员注入),其目的就是经过aspect的方式,在现有的类中注入一些新的成员变量或者成员方法。经过aspect,咱们能够向一个类中注入以下成员:github

  • 成员变量(final或者非final)
  • 方法
  • 构造函数

除了往类里面添加内容,aspect还能够修改java中的interface(接口),实如今现有接口中注入:spring

  • 方法的默认实现
  • 非final的域

经过ITD注入的成员的访问修饰符能够是:框架

  • private: 经过private声明的私有成员属于目标类,可是呢,只对aspect脚本可见,而对目标类不可见;
  • public: 声明为public的成员对全部类和apsect均可见;
  • default package protected:这里的包可见性是相对于aspect所在的包,而不是相对于目标类所在的包。

inter-type declaration示例

在编写aspect以前,先准备一个简单的java类:eclipse

package cc.databus.aspect.intertype;

public class Point {
    private int x;
    private int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }
}

有了这个基础类,下面来看看如何经过aspect修改这个类实现的接口,成员变量以及成员方法。这里是咱们的aspect代码:ide

package cc.databus.aspect.intertype;

public aspect PointAspect {
    // creates a new interface named HasName
    private interface HasName{}
    // make class Ppint implements HashName
    declare parents: Point implements HasName;
    // make HasName has a field named name
    private String HasName.name;
    // make HasName has a method getName() and default implemented
    public String HasName.getName() {
        return name;
    }

    // make HasName has a method named setName and default
    public void HasName.setName(String name) {
        this.name = name;
    }

    // add a field named created to class Point
    // with default value 0
    long Point.created = 0;


    // add a field named lastUpdated to class Point
    // with default value 0
    private long Point.lastUpdated = 0;


    // add a private method setUpdated()
    private void Point.setUpdated() {
        this.lastUpdated = System.currentTimeMillis();
    }

    // implement toString() for Point
    // include the fields added in the aspect file
    public String Point.toString() {
        return String.format(
                "Point: {name=%s, x=%d; y=%d, created=%d, updated=%d}",
                getName(), getX(), getY(), created, lastUpdated);
    }

    // pointcut the constructor, and set the value for created
    after() returning(Point p) : call(Point.new(..)) && !within(PointAspect) {
        System.out.println(thisJoinPointStaticPart);
        System.out.println("Set created");
        p.created = System.currentTimeMillis();
    }

    // define a pointcut for setX and setY
    pointcut update(Point p): target(p) && call(void Point.set*(..));

    // make the lastUpdated updated every time
    // setX or setY invoked
    after(Point p): update(p) && !within(PointAspect) {
        System.out.println("set updated for Point due to " + thisJoinPointStaticPart);
        p.setUpdated();
    }
}

在上面的aspect文件中,咱们首先定义了一个接口,而且让Point类实现该接口,且给该新接口加了一个成员变量(name)并实现了对应的setter/getter:wordpress

// creates a new interface named HasName
    private interface HasName{}
    // make class Ppint implements HashName
    declare parents: Point implements HasName;
    // make HasName has a field named name
    private String HasName.name;
    // make HasName has a method getName() and default implemented
    public String HasName.getName() {
        return name;
    }

    // make HasName has a method named setName and default
    public void HasName.setName(String name) {
        this.name = name;
    }

随后,咱们给Point类加了两个成员变量,并实现了两个成员方法。其中,实现toString()接口的时候,咱们把经过aspect注入的成员变量也都包含在结果里面:函数

// add a field named created to class Point
    // with default value 0
    long Point.created = 0;

    // add a field named lastUpdated to class Point
    // with default value 0
    private long Point.lastUpdated = 0;

    // add a private method setUpdated()
    private void Point.updated() {
        this.lastUpdated = System.currentTimeMillis();
    }

    // implement toString() for Point
    // include the fields added in the aspect file
    public String Point.toString() {
        return String.format(
                "Point: {name=%s, x=%d; y=%d, created=%d, updated=%d}",
                getName(), getX(), getY(), created, lastUpdated);
    }

最后,咱们加了两个pointcut一级advice,分别实如今调用Point构造函数以后为created的赋值,以及调用setX(int), set(int)以及setName(string)的时候更新lastUpdated成员变量(这里使用!within(PointAspect)排除掉在aspect脚本里面调用set*的状况):

// pointcut the constructor, and set the value for created
    after() returning(Point p) : call(Point.new(..)) && !within(PointAspect) {
        System.out.println(thisJoinPointStaticPart);
        System.out.println("Set created");
        p.created = System.currentTimeMillis();
    }

    // define a pointcut for setX and setY
    pointcut update(Point p): target(p) && call(void Point.set*(..));

    // make the lastUpdated updated every time
    // setX or setY invoked
    after(Point p): update(p) && !within(PointAspect) {
        System.out.println("set updated for Point due to " + thisJoinPointStaticPart);
        p.setUpdated();
    }

一样,咱们能够新建一个单元测试类来进行测试:

package cc.databus.aspect.intertype;

import org.junit.Test;

public class TestPointAspect {

    @Test
    public void test() {
        Point point = new Point(1,1);
        point.setName("test");
        point.setX(12);
        point.setY(123);
        System.out.println(point);
    }
}

运行测试,咱们能看到以下结果:

call(cc.databus.aspect.intertype.Point(int, int))
Set created
set updated for Point due to call(void cc.databus.aspect.intertype.Point.setName(String))
set updated for Point due to call(void cc.databus.aspect.intertype.Point.setX(int))
set updated for Point due to call(void cc.databus.aspect.intertype.Point.setY(int))
Point: {name=test, x=12; y=123, created=1536153649547, updated=1536153649548}

能够看到,经过aspect注入的成员对象和成员方法都是工做的。

总结

ITD着实是一个强大的功能,可以方便给现有类注入新的功能。可是,笔者认为使用这种方法相对容易出错,尤为在大项目的状况下,若是经过大量的aspect脚原本实现功能,相信对后期的维护是一个很大的挑战。因此,我建议在没有spring这种框架作支撑的状况下,不要大量的使用这种方法为项目造血。

Reference

  1. Advanced AspectJ Part II : Inter-type declaration
  2. Inter-type declarations

文章同步发布在个人我的博客https://jianyuan.me上,欢迎拍砖。 传送门: AspectJ中的类型间声明(成员注入)

相关文章
相关标签/搜索