• 后端——框架——切面框架——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循环转换之后的代码。

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

  • 相关阅读:
    解决问题redis问题:ERR Client sent AUTH, but no password is set
    关于使用Git的几点小技巧
    解决linux中使用git,ssh每次都要输入密码
    No package tomcatX available. 解决办法
    关于spring定时任务被多次调用的问题
    vue 非常规跨域实现 proxyTable 设置及依赖
    css 3 获取设备宽度的方法
    ionic 文本域文字输入监听事件
    Ionic3学习基础之Input组件
    微信小程序tabBar使用
  • 原文地址:https://www.cnblogs.com/rain144576/p/14708726.html
Copyright © 2020-2023  润新知