在上一篇博客初窥 AspectJ https://jianyuan.me/2018/09/03/aspjectj-introduce/ 中, 我们提到 AspectJ 给 java 提供了三种新的结构, pointcut,advice 以及 inter-type declaration(ITD), 而且我们通过一个简单的 Demo 介绍了如何使用 pointcut 和 advice. 而本文将介绍 inter-type declaration 是什么, 可以做什么, 最后同样会通过一个 Demo 来介绍如何使用. 后文将主要用 ITD 来表示 inter-type declaration.
本文中 Demo 的代码可以在 github https://github.com/jianyuanzh/java-examples/tree/master/aspect-demo/ 中找到.
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
- Advanced AspectJ Part II : Inter-type declaration https://doanduyhai.wordpress.com/2011/12/12/advanced-aspectj-part-ii-inter-type-declaration/
- Inter-type declarations https://www.eclipse.org/aspectj/doc/next/progguide/language-interType.html
来源: https://www.cnblogs.com/yflog/p/9596791.html