天天看點

29--Pointcut和Advisor以及靜态普通方法名比對切面

上一篇我們簡單介紹了一下AOP中的一些相關術語、以及Advice接口下的一些增強實作,但是這裡會有一個問題,那就是增強方法還會被應用到目标類的所有接口。修改一下上一節的測試類并運作。(本篇很多簡介摘自Spring3.X企業應用開發實戰,實在想不出來如何去介紹這些概念類的資訊。。。)

1.Pointcut概念的引入及簡介
@Test
public void test5() {
    // 前置增強
    // 1、執行個體化bean和增強
    Animal dog = new Dog();
    MyMethodBeforeAdvice advice = new MyMethodBeforeAdvice();

    // 2、建立ProxyFactory并設定代理目标和增強
    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.setTarget(dog);
    proxyFactory.addAdvice(advice);

    // 3、生成代理執行個體
    Animal proxyDog = (Animal) proxyFactory.getProxy();
    proxyDog.sayHello("二哈", 3);
    System.out.println("\n\n");
    proxyDog.sayException("二哈", 3);
}
           
==前置增強
==方法名:sayHello
==第1參數:二哈
==第2參數:3
==目标類資訊:com.lyc.cn.v2.day04.advisor.Dog@65e579dc
==名字:二哈 年齡:3



==前置增強
==方法名:sayException
==第1參數:二哈
==第2參數:3
==目标類資訊:com.lyc.cn.v2.day04.advisor.Dog@65e579dc
==名字:二哈 年齡:3

java.lang.ArithmeticException: / by zero

	at com.lyc.cn.v2.day04.advisor.Dog.sayException(Dog.java:17)
	at com.lyc.cn.v2.day04.advisor.Dog$$FastClassBySpringCGLIB$$a974b1ec.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
           

從測試結果上看,隻要我們調用了Animal類的接口,增強方法都會被應用到目标類的方法上,這樣的增強效果肯定不能滿足我們實際的應用,那麼這個時候就需要引入一個概念----

切入點(Pointcut)

。通過切入點就可以有選擇的将增強應用到目标類的方法上,而目标類的方法就是我們上一節說的連接配接點,即sayHello和sayException方法,目标類就是要被增強的類,即Dog類,是以增強描述了連接配接點的方位資訊,例如織入到方法之前、方法之後,而切入點則進一步的描述了織入到那些類的那些方法上。到這裡相信大家對連接配接點、切入點、增強、目标對象等概念有了更為深刻的了解。

但是這又帶了一個新的問題,那就是如何将切入點定位到連接配接點,換言之,就是切入點如何知道自己要被應用到那些連接配接點上呢?

接下來就有必要看一下Pointcut接口的源碼了。

  • Pointcut接口
public interface Pointcut {
	/**
	 * 傳回目前切點比對的類
	 */
	ClassFilter getClassFilter();

	/**
	 * 傳回目前切點比對的方法
	 */
	MethodMatcher getMethodMatcher();


	/**
	 * Canonical Pointcut instance that always matches.
	 */
	Pointcut TRUE = TruePointcut.INSTANCE;

}
           

Pointcut接口的定義非常簡單,僅僅包含了ClassFilter和MethodMatcher的定義,ClassFilter可以定位到具體的類上,MethodMatcher可以定位到具體的方法上,這樣通過Pointcut我們就可以将将增強織入到特定類的特定方法上了。再來看下ClassFilter和MethodMatcher的定義:

  • ClassFilter接口
public interface ClassFilter {

	/**
	 * 切入點應該應用于給定的接口還是目标類
	 * Should the pointcut apply to the given interface or target class?
	 * @param clazz the candidate target class 候選目标類
	 * @return whether the advice should apply to the given target class 增強是否應用于目标類
	 */
	boolean matches(Class<?> clazz);


	/**
	 * Canonical instance of a ClassFilter that matches all classes.
	 */
	ClassFilter TRUE = TrueClassFilter.INSTANCE;

}
           
  • MethodMatcher接口
public interface MethodMatcher {

	/**
	 * 靜态方法比對判斷
	 */
	boolean matches(Method method, Class<?> targetClass);

	/**
	 * 判斷靜态方法比對或動态方法比對
	 * true:動态方法比對
	 * false:靜态方法比對
	 */
	boolean isRuntime();

	/**
	 * 動态方法比對判斷
	 */
	boolean matches(Method method, Class<?> targetClass, Object... args);


	/**
	 * Canonical instance that matches all methods.
	 */
	MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;

}
           

雖然還沒有看到以上三個接口的具體實作,但是現在我們隻要知道Pointcut接口提供了這樣的功能就行了。在MethodMatcher接口中又引入了一個新的概念,方法比對模式,Spring支援兩種方法比對器:

  • 靜态方法匹模式:所謂靜态方法比對器,僅對方法名簽名(包括方法名和入參類型及順序)進行比對。
  • 動态方法比對器:動态方法比對器會在運作期方法檢查入參的值。 靜态比對僅會判斷一次,而動态比對因為每次調用方法的入參可能不一樣,是以每次調用方法都必須判斷。

接下來簡單介紹一下Spring提供的切點類型:

  • 靜态方法切點–>org.springframework.aop.support.StaticMethodMatcherPointcut

    靜态方法切點的抽象基類,預設情況下比對所有的類。最常用的兩個子類NameMatchMethodPointcut和 AbstractRegexpMethodPointcut , 前者提供簡單字元串比對方法簽名,後者使用正規表達式比對方法簽名。

  • 動态方法切點–>org.springframework.aop.support.DynamicMethodMatcherPointcut

    動态方法切點的抽象基類,預設情況下比對所有的類

  • 注解切點–>org.springframework.aop.support.annotation.AnnotationMatchingPointcut
  • 表達式切點–>org.springframework.aop.support.ExpressionPointcut

    提供了對AspectJ切點表達式文法的支援

  • 流程切點–>org.springframework.aop.support.ControlFlowPointcut

    該切點是一個比較特殊的節點,它根據程式執行的堆棧資訊檢視目标方法是否由某一個方法直接或間接發起調用,一次來判斷是否為比對的連結點

  • 複合切點–>org.springframework.aop.support.ComposablePointcut

    該類是為實作建立多個切點而提供的操作類

2.切面簡介

由于增強包括橫切代碼,又包含部分連接配接點資訊(方法前、方法後主方位資訊),是以可以僅通過增強類生成一個切面。 但切點僅僅代表目标類連接配接點的部分資訊(類和方法的定位),是以僅有切點無法制作出一個切面,必須結合增強才能制作出切面。Spring使用org.springframework.aop.Advisor接口辨別切面概念,一個切面同時包含橫切代碼和連接配接點資訊。

切面可以分為3類:一般切面、切點切面、引介切面

  • 一般切面Advisor

    org.springframework.aop.Advisor代表一般切面,

    僅包含一個Advice ,因為Advice包含了橫切代碼和連接配接點資訊,是以Advice本身一個簡單的切面,隻不過它代表的橫切的連接配接點是所有目标類的所有方法,因為這個橫切面太寬泛,是以一般不會直接使用。

  • 切點切面PointcutAdvisor

    org.springframework.aop.PointcutAdvisor ,代表具有切點的切面,包括Advice和Pointcut兩個類,這樣就可以通過類、方法名以及方位等資訊靈活的定義切面的連接配接點,提供更具實用性的切面。PointcutAdvisor主要有6個具體的實作類:

    1. DefaultPointcutAdvisor:最常用的切面類型,它可以通過任意Pointcut和Advice定義一個切面,唯一不支援的就是引介的切面類型,一般可以通過擴充該類實作自定義的切面
    2. NameMatchMethodPointcutAdvisor:通過該類可以定義按方法名定義切點的切面
    3. AspectJExpressionPointcutAdvisor:用于AspectJ切點表達式定義切點的切面
    4. StaticMethodMatcherPointcutAdvisor:靜态方法比對器切點定義的切面,預設情況下比對所有的的目标類
    5. AspectJPointcutAdvisor:用于AspectJ文法定義切點的切面
  • 引介切面IntroductionAdvisor

    org.springframework.aop.IntroductionAdvisor代表引介切面, 引介切面是對應引介增強的特殊的切面,它應用于類層上面,是以引介切點使用ClassFilter進行定義。

3.靜态普通方法名比對切面

上面已經對切入點、切面做了簡介,下面通過幾個例子來加深大家的印象。先來看靜态普通方法名比對切面,前面我們介紹切入點

通過ClassFilter可以定位到具體的類上,MethodMatcher可以定位到具體的方法上

,那麼接下來通過定義一個接口、兩個類。并通過實作類中的不同方法來驗證我們之前的介紹。

  • 接口和實作類(目标對象)
package com.lyc.cn.v2.day05;

/**
 * @author: LiYanChao
 * @create: 2018-11-04 22:40
 */
public interface Animal {
	void sayHello();
}
           
package com.lyc.cn.v2.day05;

/**
 * @author: LiYanChao
 * @create: 2018-11-04 22:09
 */
public class Cat implements Animal {

	@Override
	public void sayHello() {
		System.out.println("我是Cat類的sayHello方法。。。");
	}

	public void sayHelloCat() {
		System.out.println("我是一隻貓。。。");
	}

}
           
package com.lyc.cn.v2.day05;

/**
 * @author: LiYanChao
 * @create: 2018-11-04 22:09
 */
public class Dog implements Animal{

	@Override
	public void sayHello() {
		System.out.println("我是Dog類的sayHello方法。。。");
	}

	public void sayHelloDog() {
		System.out.println("我是一隻狗。。。");
	}
}
           
  • 增強(為了示範,這裡隻實作MethodBeforeAdvice前置增強接口)
package com.lyc.cn.v2.day05;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

/**
 * 前置增強
 * @author: LiYanChao
 * @create: 2018-11-01 21:50
 */
public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
	@Override
	public void before(Method method, Object[] args, Object target) throws Throwable {
		System.out.println("==前置增強");
		System.out.println("==方法名:" + method.getName());
		if (null != args && args.length > 0) {
			for (int i = 0; i < args.length; i++) {
				System.out.println("==第" + (i + 1) + "參數:" + args[i]);
			}
		}
		System.out.println("==目标類資訊:" + target.toString());
	}

}
           
  • 切面
package com.lyc.cn.v2.day05;

import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;

import java.lang.reflect.Method;

/**
 * 靜态普通方法名比對切面
 * @author: LiYanChao
 * @create: 2018-11-04 22:08
 */
public class MyStaticPointcutAdvisor extends StaticMethodMatcherPointcutAdvisor {

	private static String METHOD_NAME = "sayHello";

	/**
	 * 靜态方法比對判斷,這裡隻有方法名為sayHello的,才能被比對
	 */
	@Override
	public boolean matches(Method method, Class<?> targetClass) {
		return METHOD_NAME.equals(method.getName());
	}

	/**
	 * 覆寫getClassFilter,隻比對Dog類
	 * @return
	 */
	public ClassFilter getClassFilter() {
		return new ClassFilter() {
			@Override
			public boolean matches(Class<?> clazz) {
				return Dog.class.isAssignableFrom(clazz);
			}
		};
	}
}
           

StaticMethodMatcherPointcutAdvisor抽象類繼承了StaticMethodMatcherPointcut類并實作了PointcutAdvisor接口。在MyStaticPointcutAdvisor類中我們實作了matches靜态方法比對判斷,并且隻有方法名為sayHello的,才能被比對;覆寫了getClassFilter方法,并且隻比對Dog類。

  • 測試一

    為了大家能夠更便捷的使用測試類,也為了減少大家書寫配置檔案的負擔,我們還是采用編碼的形式實作。建立MyTest類并書寫測試方法。

@Test
public void test1() {
    // 1、建立目标類、增強、切入點
    Animal animal = new Dog();
    MyMethodBeforeAdvice advice = new MyMethodBeforeAdvice();
    MyStaticPointcutAdvisor advisor = new MyStaticPointcutAdvisor();

    // 2、建立ProxyFactory并設定目标類、增強、切面
    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.setTarget(animal);
    // 為切面類提供增強
    advisor.setAdvice(advice);
    proxyFactory.addAdvisor(advisor);

    // 3、生成代理執行個體
    Dog proxyDog = (Dog) proxyFactory.getProxy();
    proxyDog.sayHelloDog();
    System.out.println("\n\n");
    proxyDog.sayHello();

}
           
我是一隻狗。。。



==前置增強
==方法名:sayHello
==目标類資訊:com.lyc.cn.v2.day05.Dog@65e579dc
我是Dog類的sayHello方法。。。
           

之前我們在代碼裡配置了,在類一級隻比對Dog類,在方法一級隻比對sayHello方法。運作結果與我們的設定符合。隻有Dog類的sayHello被增強了。

  • 測試二
@Test
public void test2() {
	// 1、建立目标類、增強、切入點
	Animal animal = new Cat();
	MyMethodBeforeAdvice advice = new MyMethodBeforeAdvice();
	MyStaticPointcutAdvisor advisor = new MyStaticPointcutAdvisor();

	// 2、建立ProxyFactory并設定目标類、增強、切面
	ProxyFactory proxyFactory = new ProxyFactory();
	proxyFactory.setTarget(animal);
	// 為切面類提供增強
	advisor.setAdvice(advice);
	proxyFactory.addAdvisor(advisor);

	// 3、生成代理執行個體
	Cat proxyDog = (Cat) proxyFactory.getProxy();
	proxyDog.sayHelloCat();
	System.out.println("\n\n");
	proxyDog.sayHello();

}
           
我是一隻貓。。。



我是Cat類的sayHello方法。。。
           

測試二的結果沒有一個方法被增強,雖然在Cat類中也有sayHello方法,但是我們設定的是隻比對Dog類,是以雖然在Cat類中有sayHello方法,但是它也是無法被增強的。

至于其他的切點和切面,這裡就不一一示範了,這裡特别感謝《Spring3.X企業應用開發實戰》這本書,本篇很多介紹均摘自本書,哈哈!

4.總結

本篇主要介紹了切點和切面的概念,并通過實際的例子為大家示範了切點是如何比對類和方法的。概念性的東西大家隻看簡介是不行的,需要自己動手寫代碼,才能更深刻的了解AOP的相關概念,在接下來的源碼分析中才不會陷入迷茫。

上一篇和本篇的測試類中,我們都是通過ProxyFactory建立的代理,這樣的實作肯定無法滿足我們的實際需要,那麼接下來的篇幅,我們就要介紹Spring的自動代理機制。

繼續閱讀