In the first glimpse of AspectJ, we mentioned that ASPECTJ provides three new constructs for Java, pointcut,advice and Inter-type Declaration (ITD), And we've introduced a simple demo of how to use pointcut and advice. This article will introduce what Inter-type declaration is, what can be done, and finally, a demo will show how to use it. The following article will mainly use ITD to represent Inter-type declaration.
The demo code in this article can be found in GitHub Aspect-demo.
ITD and member injection
Inter-type Declaration (ITD), translated into Chinese is a type of inter-declaration. Even if I see Chinese translation, I believe we are still confused, unintelligible, so I am not very fond of some English names, especially the technical name of the blunt translation, which will only increase the understanding of the burden of everyone. In fact, a different argument may be better understood, Member introduction (member injection), and its purpose is to inject some new member variables or member methods into existing classes by aspect way. With aspect, we can inject the following members into a class:
- Member variables (final or non-final)
- Method
- constructor function
In addition to adding content to a class, aspect can also modify the interface (interface) in Java to inject in existing interfaces:
- Default implementation of the method
- A non-final domain
The access modifiers for members injected through ITD can be:
- Private member is the target class, but it is only visible to the aspect script, but not to the target class;
- Public: Members declared as public are visible to all classes and apsect;
- Default Package Protected: The packet visibility here is relative to the package that the aspect resides in, not the package that contains the target class.
Inter-type Declaration Example
Before you write aspect, prepare a simple Java class:
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; }}
With this basic class, let's look at how to modify the interface, member variables, and member methods implemented by this class through aspect. Here is our aspect code:
Package Cc.databus.aspect.intertype;public aspect Pointaspect {//Creates a new interface named Hasname private int Erface hasname{}//Make class Ppint implements Hashname declare Parents:point implements Hasname; Make Hasname have a field named name Private String Hasname.name; Make Hasname have a method GetName () and default implemented public String Hasname.getname () {return name; }//Make Hasname have a method named SetName and default public void Hasname.setname (String name) {this.na me = 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.currenttimemill Is (); }//Implement ToString () for point//include the fields added in the aspectFile 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 (..)) && amp;!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 (p Ointaspect) {System.out.println ("set updated for point due to" + Thisjoinpointstaticpart); P.setupdated (); }}
In the aspect file above, we first define an interface and let the Point
class implement the interface, and add a member variable (name) to the new interface and implement the corresponding 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; }
We then added two member variables to the point class and implemented two member methods. where the ToString () interface is implemented, the member variables injected through aspect are also included in the result:
// 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); }
Finally, we add two pointcut first-level advice, each implementing the assignment after the point constructor is called created
, and calling setx (int), set (int), and SetName (string) Update the lastupdated member variable (this is used to !within(PointAspect)
exclude the call to set* in the aspect script):
// 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(); }
Again, we can create a new unit test class to test:
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); }}
To run the test, we can see the following results:
call(cc.databus.aspect.intertype.Point(int, int))Set createdset 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}
As you can see, both the member object and the member method injected through aspect are working.
Summarize
ITD is truly a powerful feature that allows you to inject new functionality into existing classes. However, I think that using this method is relatively error-prone, especially in the case of large projects, if through a large number of aspect scripts to achieve the function, I believe that the maintenance of the latter is a great challenge. Therefore, I suggest that in the absence of the spring framework to support the case, do not use this method a lot of blood for the project.
Reference
- Advanced AspectJ Part Ii:inter-type declaration
- Inter-type declarations
Article sync posted on my personal blog jianyuan.me, welcome to shoot bricks.
Portal: Inter-type declarations in ASPECTJ (member injection)