在上一篇博客初窺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
inter-type declaration (ITD),翻譯成中文是類型間聲明。即便看到中文翻譯,相信你們仍是一頭霧水,不知所云,因此我不是很喜歡對一些英文名字,尤爲是技術名字進行生硬的翻譯,這隻會增長你們的理解負擔。其實,換一種說法可能更好理解,member introduction(成員注入),其目的就是經過aspect的方式,在現有的類中注入一些新的成員變量或者成員方法。經過aspect,咱們能夠向一個類中注入以下成員:github
除了往類裏面添加內容,aspect還能夠修改java中的interface(接口),實如今現有接口中注入:spring
經過ITD注入的成員的訪問修飾符能夠是:框架
在編寫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這種框架作支撐的狀況下,不要大量的使用這種方法爲項目造血。
文章同步發佈在個人我的博客https://jianyuan.me上,歡迎拍磚。 傳送門: AspectJ中的類型間聲明(成員注入)