本篇介绍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循环转换之后的代码。
至此,本篇内容结束,最常使用的就三种,方法,构造器,以及异常。