当前位置: 移动技术网 > IT编程>开发语言>Java > AspectJ中的类型间声明(成员注入)

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

2018年09月06日  | 移动技术网IT编程  | 我要评论
在上一篇博客 "初窥AspectJ" 中,我们提到AspectJ给java提供了三种新的结构,pointcut,advice以及inter type declaration(ITD),而且我们通过一个简单的Demo介绍了如何使用pointcut和advice。而本文将介绍inter type dec ...

在上一篇博客初窥aspectj中,我们提到aspectj给java提供了三种新的结构,pointcut,advice以及inter-type declaration(itd),而且我们通过一个简单的demo介绍了如何使用pointcut和advice。而本文将介绍inter-type declaration是什么,可以做什么,最后同样会通过一个demo来介绍如何使用。后文将主要用itd来表示inter-type declaration。

本文中demo的代码可以在github 中找到。

itd与成员注入

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

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

除了往类里面添加内容,aspect还可以修改java中的interface(接口),实现在现有接口中注入:

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

通过itd注入的成员的访问修饰符可以是:

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

inter-type declaration示例

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

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代码:

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:

    // 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中的类型间声明(成员注入)

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网