1. 程式人生 > >精通Spring+4.x++企業開發與實踐之基於@AspectJ和Schema的AOP

精通Spring+4.x++企業開發與實踐之基於@AspectJ和Schema的AOP

#  精通Spring+4.x++企業開發與實踐之基於@AspectJ和Schema的AOP

使用@AspectJ的條件

1.保證是java5以上的版本(需要使用註解,而java5及以上才使用註解)

2.需要將Spring的asm(輕量級的位元組碼處理框架)的模組新增到類路徑中,因為java的反射無法獲取入參的名字,所以Spring就是要asm處理@AspectJ中描述的方法入參名。

3.Spring使用AspectJ提供的@AspectJ註解類庫及相應的解析類庫,需要在pom.xml檔案新增aspectjweaver和aspectj的工具類aspectjrt

例子:

PreGreetingAspect.java

@Aspect//使用該註解定義一個切面
public class PreGreetingAspect {
	/**
	 * 這段程式碼包含了橫切的邏輯
	 * @Before 增強的型別
	 * "execution(* greetTo(..))"目標切點的表示式
	 */
	@Before("execution(* greetTo(..))")
	public void beforeGreeting(){
		System.out.println("How are you");
	}
}

測試程式碼:

		//建立被代理的物件
		Waiter waiter = new PoliteWaiter();
		//AspectJ代理工廠
		AspectJProxyFactory factory = new AspectJProxyFactory();
		//提娜佳目標類
		factory.setTarget(waiter);
		//新增切面類
		factory.addAspect(PreGreetingAspect.class);
		//生成切入的代理物件
		Waiter proxy = factory.getProxy();
		proxy.greetTo("張三");

執行結果: How are you

greet to 張三....

使用schema的配置方式進行配置

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
	   xmlns:context="http://www.springframework.org/schema/context"
	   xmlns:aop="http://www.springframework.org/schema/aop"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans
		 http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
		 http://www.springframework.org/schema/context
		 http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

	<!--基於asjectj的切面驅動器-->
	<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
	<bean id="waiter" class="com.flexible.aspectj.PoliteWaiter"></bean>
	<bean class="com.flexible.aspectj.PreGreetingAspect"></bean>
	</beans>

測試程式碼:

	ApplicationContext context = new ClassPathXmlApplicationContext("classpath:beans.xml");
	Waiter waiter = (Waiter) context.getBean("waiter");
	waiter.greetTo("zhangsan");

執行結果:

@AspectJ語法基礎

@AspectJ使用Java5.0註解和正規的AspectJ的切點表示式語言描述切面,由於Spring只支援方法的連結,所以Spring僅支援部分AspectJ的切點語言。

切點表示式函式

組成:1.關鍵字和操作引數execution(* greetTo(..)),execution是關鍵字,"* greetTo(..)"位操作引數。execution代表目標類執行某一方法,"* greetTo(..)"描述目標方法的匹配模式串,二者聯合起來表示目標類的greetTo()方法的連線點。execution()稱為函式,"* greet(..)"稱為函式入參。

Spring支援9個@AdpectJ切點表示式函式,它用不同的方式藐視目標類的連線點。根據描述的不同可以分為4種: 1.方法切點函式:通過描述目標類方法的資訊定義連線點。 2.方法入參切點函式:通過描述目標類的方法入參的資訊定義連線點。 3.目標類切點函式:通過描述目標類型別的信心定義連線點。 4.代理切點函式:通過描述目標類的代理類的資訊定義連線點。

入參函式的萬用字元

  1. *:匹配任意字元,但它只能匹配上下文中的一個元素。

  2. ..:匹配任意字元,可以匹配上下文中的多個元素,但在表示類時,必須和*聯合使用,而在表示入參時則單獨使用。

  3. +:表示按型別匹配指定的類的所有類,必須跟在類名後面,如com.flexible.Car+表示繼承或者拓展了制定類的所有類,同時還包括指定類本身。

@AaspectJ函式按其是否支援萬用字元及支援的程度,可以分為三類:

1.支援萬用字元:execution()和within(),如 within(com.flexible.),within(com.flexible.service...*.Service).

2.僅支援"+"萬用字元:args(),this()和target(),如args(com.flexible.Waiter+),target(java.util.List+),但是其實這幾個函式是不是有這個萬用字元都時一樣的。

3.不支援萬用字元的:@args,@within(),@target()和@annotation().

不同型別的增強

[email protected] 前置增強,相當於BeforeAdvice。Before註解類用於兩個成員

  • value:該成員用於定義切點。
  • argsNames:由於無法通過Java反射機制獲取方法入參名,所以如果在Java編譯時未啟用調式資訊,或者需要在執行期間解析切點,就必須通過這個成員指定直接所標註增強的方法的引數名(注意二者名字必須完全相同),多個引數名用逗號分開。

[email protected] 後置增強,相當於AfterReturningAdvice。AfterReturning註解類擁有4個成員。

  • value:該成員用於定義切點
  • pointcut:表示切點的資訊。如果顯式地指定pointcut值,那麼它將覆蓋value的設定值,可以將pointcut成員看作value同義詞。
  • returning:將目標物件方法的返回值繫結給增強的方法。
  • argNames:如上所述

[email protected] 環繞增強,相當於MethodInterceptor。Around註釋類用於兩個成員。

  • value:該成員用於定義切點
  • argNames:如上所述

[email protected] 丟擲增強,相當於ThrowsAdvice。AfterThrowing註解有4個成員。

  • value:指定切點

  • pointcut:表示切點的資訊,如果顯示指定pointcut值,那麼它將覆蓋value值得設定值,可以將pointcut成員看作value得同義詞。

  • throwing:將丟擲得異常繫結到增強方法中。

  • argNames:如上所述。

[email protected] Final增強,不管時丟擲異常還是正常退出,該增強都會執行,該增強沒有對應得增強介面,可以把它看成ThrowsAdvice和AfterReturningAdvice得混合物,議案用於釋放資源,相當於try{}finally{}的控制流程。它有兩個成員;

  • value:該成員用於定義切點。

  • argNames:如上所述。

[email protected] 引介增強,相當於IntroductionInterceptor.DeclareParents註解類擁有兩個成員

  • value:定義切點,表示在那個木堡壘上新增引介增強。

  • defaultImpl:預設介面實現類。

引介增強的使用

通過引介增強將waiter也具有seller的功能

EnableSellerAspect.java

@Aspect
public class EnableSellerAspect {
//1.為PoliteWaiter太你家介面實現 2.預設介面實現類 3.要是西安的目標介面。
@DeclareParents(value = "com.flexible.inroductionofdeclareparent.PoliteWaiter",defaultImpl = SmartSeller.class)
public Seller seller;
}

beans.xml

	<!--基於asjectj的切面驅動器-->
	<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
	<bean id="waiter_2" class="com.flexible.inroductionofdeclareparent.PoliteWaiter"></bean>
	<bean class="com.flexible.inroductionofdeclareparent.EnableSellerAspect"></bean>

測試程式碼:

		ApplicationContext context = new ClassPathXmlApplicationContext("classpath:beans.xml");
		Waiter waiter = (Waiter) context.getBean("waiter_2");
		waiter.greetTo("zhangsan");
		Seller seller = (Seller) waiter;
		seller.sell("apple");

執行結果:

切點函式詳解

命名切點

直接將切點宣告在增強的方式是匿名增強,而如果希望在其他地方重用一個切點,可以通過@Pointcut註解及切面類方法對切點進行命名。

例子:

import org.aspectj.lang.annotation.Pointcut;

public class NamedPointcut {
	//通過註解方法inpackage()對該切點進行命名,方法可視域修飾符為private
	//表明該命名切點只能在本切面類中使用
	@Pointcut("within(com.flexible.*)")
	private void inpackage(){}

	//通過註解方法greetTo()對該切點進行命名,方法可以視域修飾符為protected
	//表明該命名切點可以在當前包中的切面類,自切脈你類只要。
	@Pointcut("execution(* greetTo(..))")
	protected void greetTo(){}

	//引用命名切點定義的切點,本切點也是命名切點,它對應的可視域為public
	@Pointcut("inpackage() && greetTo()")
	public void inPkgGreetTo(){}

}

命名切點的使用類方法作為切點的名稱,此方法的訪問修飾符還控制了切點的可引用性。命名切點定義好之後我們就可以在定義切面的時通過名稱引用切點。

例子:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class NamedAspect {
	@Before("NamedPointcut.inPkgGreetTo()")
	public void pkgGreetTo(){
		System.out.println("----------pkgGreetTo() executed!---");
	}
	@Before("!target(com.flexible.pointcutbreakdown.annotation.PoliteWaiter) && NamedPointcut.inPkgGreetTo()")
	public void pkgGreetToNotNaivewaiter(){
		System.out.println("----------pkgGreetToNotNaivewaiter() executed!---");
	}
}

增強植入的順序

一個連線點可u哦同時匹配多個其欸但,切點對應的增強在連線點上的植入順序有三種情況需要討論

1.如果增強在同一個切面類中宣告,則依照增強在切面類中定義的順序織入

2.如果增強位於不同的切面類中,且這些切面類都實現了org.springframework.core.Ordered介面,則由介面方法的順序好決定(順序號小的先織入)

3.如果增強位於不同的切面類中,且這些切面類沒有實現org.springframework.core.Ordered介面,則織入順序是不確定的。

例子: 如果有切面A和切面B,而且這兩個切面都實現了org.springframework.core.Ordered介面,A的順序是1,而得順序是2,而且A定義了三個切點,B定義兩個切點。那麼訪問順序如下圖所示:

訪問連線點資訊

AspectJ使用org.aspectj.lang.JoinPoint介面表示目標類連線點物件。如果是環繞增強,則使用org.aspectj.lang.ProceedingJoinPoint表示連繫欸但物件,該類是JoinPoint得子介面。任何增強方法都可以通過將第一個入參宣告為JoinPoint訪問連繫欸但上下文資訊。 1.JoinPoint

  • java.lang.Object[] getArgs():獲取連繫欸但方法執行時得入參列表。
  • Signature getSignature():獲取連線點得方法簽名物件。
  • java.lang.Object getTarget():獲取連線點所在目標物件
  • java.lang.Object getThis():獲取代理物件本身。

2.ProceedingJoinPoint

ProceedingJoinPoint繼承於JoinPoint子介面,它新增了兩個用於執行連線點得方法。

  • java.lang.Object.proceed() throws java.lang.Throwable:通過反射執行目標物件得連線點處得方法。
  • java.lang.Object proceed(java.lang.Object[] args)throws java.lang.Throwable:通過反射執行目標物件連線點處得方法,不過使用新得入倉替換原來得入參。

例子:

TestAspect.java

@Aspect
public class TestAspect {

	@Around("execution(* com.flexible.obtainproceedingpointinfo..*(..))")
	public void joinPointAccess(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
		System.out.println("------joinPointAccess----");
		System.out.println("args[0]:" + proceedingJoinPoint.getArgs()[0]);
		System.out.println("signature:" + proceedingJoinPoint.getTarget().getClass());
		proceedingJoinPoint.proceed();
		System.out.println("------joinPointAccess----");
	}
}

beans.xml

	<!--基於asjectj的切面驅動器-->
	<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
	<!--獲取連線點的資訊-->
	<bean id="waiter_4" class="com.flexible.obtainproceedingpointinfo.PoliteWaiter"></bean>
	<bean class="com.flexible.obtainproceedingpointinfo.TestAspect"></bean>

測試程式碼:

		ApplicationContext context = new ClassPathXmlApplicationContext("classpath:beans.xml");
		Waiter waiter = (Waiter) context.getBean("waiter_4");
		waiter.greetTo("zhangsan");

執行結果:

繫結連線點方法入參