本篇介绍Joint point,对应原著中第三章节的前三小节。主要分为三个部分,概念,类型,以及示例。
1、概念
回想第二章节的示例,或实际项目中的事务功能。
首先第一步,需要明确的是在哪些方法上添加事务,即明确需要公共模块的业务模块,join point的功能就是标识业务模块,并将标识作为条件,构建业务模块代码的筛选条件。举个例子,在CSS,HTML中,标签名,ID属性,name属性,class属性,DOM结构都是这样的标识。
其次第二步,编写Pointcut选取业务模块,在CSS中类似于选择器,在数据库中类似于SQL脚本,所有的这些都是基于join point的。在第二章节示例中Pointcut的一般格式为Pointcut name:join point。其中Pointcut是关键字,固定的。Name是Pointcut的名称,由用户定义。第三部分是join point。可以看出当join point确定之后,Pointcut会变的非常简单,所以学习join point是学习Point cut的基础,也是其核心组成部分。
最后第三步,编写Aspect,它有类似于Java的语法,它也有自己独有的元素和语法,其中Pointcut,Advice就是独有的元素,它们的语法规则在第二章节的示例中提到过。Aspect是将它们整合到一起的,提供了上下文或者是语义。比如name属性,没有确定它是属于哪个类时,它的语义是不明确的,若它是Person类的name属性,表示人的名称,若是Book类的name属性,表示书籍的名称。若没有Aspect,Pointcut和Advice无法单独存在。
2、类型
2.1 方法
方法是最常见,使用也最频繁的一种类型,它有两种形式,call(调用)& execution(执行)。
// 非静态方法 public void nonStaticMethod() { // 对应nonStaticMethod的execution } public static void main(String[] args) { TestJoinPoint jp = new TestJoinPoint(); // 调用nonStaticMethod方法,对应call jp.nonStaticMethod(); }
方法的execution是可预测的,只发生在方法体内部。最常见的就是方法的运行时间。
方法的call是无法预测数量和位置的,可以在任何地方调用。它是相对的,对于其自身而言,它的调用通常发生在方法体之外,但又是在其他方法的方法体之内,在上述示例中可以看到nonStaticMethod的调用发生在其方法体之外,在main方法体之内。方法的调用也可以发生在其方法体之内时,这种现象称为方法递归。
2.2 构造器
构造器是一种特殊的方法,与方法的形式基本相同,call(调用) 和execution(执行)。call是new对象的时候,而execution发生在方法体之内。
2.3 属性
Java中属性的操作有访问属性,修改属性,新增,删除等操作是不支持的,这些操作在JavaScript中是可以的。
访问属性的实质是读取属性的值。
修改属性的实质是修改属性的值,等价于任何赋值语句。
public void setProp1(String prop1) { // 修改属性的操作 this.prop1 = prop1; // 访问属性的操作 System.out.println("prop1:"+ this.prop1); }
当主体是属性时,Join point有两种形式,访问属性,修改属性。
2.4 异常
这里的异常是指异常的代码块,即try,catch,finally的代码结构。当主体是异常时,Join Point只有一种形式,exception execution,即catch代码块的执行。
try { System.out.println(1 / 0); } catch (ArithmeticException e) { // 异常处理代码块 e.printStackTrace(); }
2.5 类初始化
类初始化的过程,它包含三个阶段,加载,连接,初始化。加载阶段基本与类加载器有关,第二个阶段是JVM验证,准备,解析等过程,第三阶段,为静态变量赋值,执行静态代码块。
从程序员的角度,在整个过程中,由我们主导的只有静态代码块,静态变量赋值。当主体是静态代码块时,Join Point为静态代码块的执行,它没有调用的说法。
static { // static block execution }
2.6 Advice方法执行
当涉及到Advice时,主体是Aspect中的Advice元素或者是Advice元素映射的Java方法,例如第二章节中标注有@Before注解的方法。当为Aspect中的Advice元素时,Join point为Advice方法体的执行,它没有调用的说法。当为Advice映射的Java方法时,Join point为Java方法体的执行,也不包含调用。
例如第二章节的示例
before() : secureAccess(){ // advice execution System.out.println("Checking and authenticating user"); authenticator.authenticate(); }
2.7 对象初始化
引用原著中“对象初始化”的内容:
Select the initialization of an Object,from the return of a parent class’s constructor until the end of the first called constructor. Such a join point,unlike a constructor execution join point,occur only in the first called constructor for each type in the hierarchy
从所有父类构造器的执行结束到子类构造器的执行结束。对象初始化时,首先会去创建其父类。最后才会创建子类。
public class Male extends Person{ public Male(String name) { // before super code super(name); // Join Point start // after super code // Join Point end } }
2.8 对象初始化之前
引用原著中”对象初始化之前”的内容:
It encompasses the passage from the first called constructor to the beginning of its parent constructor
从子类构造器的调用到初始父类构造器执行之前。这段代码中几乎没有程序员编写的代码。基本不会使用。
3、示例
AOP的三部分,业务模块,公共模块,Aspect。其中业务模块参考原书中的代码,公共模块代码只是简单打印一条语句,直接写在了Advice方法体中。Aspect的代码如下:
/** * * @File Name: JoinPointTraceAspect.aj * @Description: 演示Join point示例 * @version 1.0 * @since JDK 1.8 */ public aspect JoinPointTraceAspect { // 方法的调用层次结构 private int callDepth; // 定义pointcut,它的含义是除JoinPointTraceAspect所有的join point, // 意味着其他类的任何方法调用,属性访问,等等都都是切面点 pointcut traced(): !within(JoinPointTraceAspect); // 定义Advice,在方法运行之前打印方法的堆栈层数 before() : traced(){ print("Before",thisJoinPoint); callDepth++; } // 定义Advice,在方法运行之后打印方法的堆栈层数 after() : traced(){ callDepth--; print("After",thisJoinPoint); } private void print(String prefix,Object message) { for(int i=0;i<callDepth;i++) { System.out.print("-"); } System.out.println(prefix + " : " +message); } }
这段Aspect的代码比较简单,除JoinPointTraceAspect的任何Join point之前打印Before,在其之后打印After。
测试代码更简单,只有两行,创建对象,调用getTotalPrice方法。
public class Main { public static void main(String[] args) { Order order = new Order(); order.getTotalPrice(); } }
结果如下:
Before : staticinitialization(ch3.bean.Main.<clinit>) After : staticinitialization(ch3.bean.Main.<clinit>) Before : execution(void ch3.bean.Main.main(String[])) -Before : call(ch3.bean.Order()) --Before : staticinitialization(ch3.bean.DomainEntity.<clinit>) --After : staticinitialization(ch3.bean.DomainEntity.<clinit>) --Before : staticinitialization(ch3.bean.Order.<clinit>) --After : staticinitialization(ch3.bean.Order.<clinit>) --Before : preinitialization(ch3.bean.Order()) --After : preinitialization(ch3.bean.Order()) --Before : preinitialization(ch3.bean.DomainEntity()) --After : preinitialization(ch3.bean.DomainEntity()) --Before : initialization(ch3.bean.DomainEntity()) ---Before : execution(ch3.bean.DomainEntity()) ---After : execution(ch3.bean.DomainEntity()) --After : initialization(ch3.bean.DomainEntity()) --Before : initialization(ch3.bean.Order()) ---Before : execution(ch3.bean.Order()) ----Before : call(java.util.ArrayList()) ----After : call(java.util.ArrayList()) ----Before : set(Collection ch3.bean.Order.lineItems) ----After : set(Collection ch3.bean.Order.lineItems) ---After : execution(ch3.bean.Order()) --After : initialization(ch3.bean.Order()) -After : call(ch3.bean.Order()) -Before : call(double ch3.bean.Order.getTotalPrice()) --Before : execution(double ch3.bean.Order.getTotalPrice()) ---Before : call(Collection ch3.bean.Order.getLineItems()) ----Before : execution(Collection ch3.bean.Order.getLineItems()) -----Before : call(java.util.ArrayList()) -----After : call(java.util.ArrayList()) ----After : execution(Collection ch3.bean.Order.getLineItems()) ---After : call(Collection ch3.bean.Order.getLineItems()) ---Before : call(Iterator java.util.Collection.iterator()) ---After : call(Iterator java.util.Collection.iterator()) ---Before : call(boolean java.util.Iterator.hasNext()) ---After : call(boolean java.util.Iterator.hasNext()) --After : execution(double ch3.bean.Order.getTotalPrice()) -After : call(double ch3.bean.Order.getTotalPrice()) After : execution(void ch3.bean.Main.main(String[]))
分析这个结果
- 当主体是Main时,
-
- 可以看到第一行,第二行触发了Main类的初始化过程。此时Join Point为Main初始化过程。
- 运行main方法,此时Join Point为Main方法的执行。
- 跳转到第2步,
- 结束了main方法的执行。
2. 当主体是Order时,
-
- 它首先触发的是Order构造器的调用,此时Join Point为Order构造器的调用。
- 由于Order类继承自DomainEntity,首先会加载DomainEntity,其次会加载Order类,此时Join point为Domain Entity和Order类的初始化过程和pre-intialization过程
- 之后会首先创建DomainEntity实例,此时触发DomainEntity的对象初始化过程,并且触发DomainEntity构造器的执行过程。
- 之后会创建Order实例,并且实例化lineItems,此时join Point 为Order对象初始化过程,lineItems属性的赋值过程。
- 此时代码的第一行new Order结束。开始代码的第二行,order调用getTotalPrice方法。
3. 当主体是getTotalPrice方法
-
- 它首先触发的该方法的调用过程和执行过程,此时join Point为getTotalPrice的执行。
- 它执行迭代for循环,跳转到第4步,第5步
- 结束getTotalPrice的执行过程和调用过程。
4.当主体是Iterator方法
-
- 它首先触发了iterator方法的调用过程,之后触发了hasNext方法的调用过程。(或许是接口的原因,没有触发执行过程),每循环一次触发一次。
5. 当主体是getLineItems方法
-
- 在getTotalPrice内部调用getLineItems方法,会触发getLineItems的调用和执行过程,它只会触发一次,与循环次数无关,获取迭代器的过程只有一次。可以反编译查看一下迭代for循环转换之后的代码。
至此,本篇内容结束,最常使用的就三种,方法,构造器,以及异常。