aop——JointPoint

  本篇介绍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[]))

  分析这个结果

  1. 当主体是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循环转换之后的代码。

  至此,本篇内容结束,最常使用的就三种,方法,构造器,以及异常。

posted @ 2021-04-27 14:09  蜗牛旅行1899  阅读(245)  评论(0编辑  收藏  举报