【04】Spring AOP

1. AOP

  • AOP:面向切面(方面)編程,通俗的理解就是:擴展功能不通過修改源代碼實現
  • AOP:採用橫向抽取機制,取代了傳統 縱向 繼成體系 重用代碼(性能監視,事務管理,安全檢查,緩存)

2. AOP實現機制 – 代理機制:

  • Spring 的 AOP 的底層用到兩種代理機制:
    • JDK 的動態代理 :針對實現了接口的類產生代理.
    • Cglib 的動態代理 :針對沒有實現接口的類產生代理. 應用的是底層的字節碼增強的技術 生成當前類
      的子類對象.

3. AOP的相關概念

  1. PointCut(切入點)

    切入點實際上是用來定義橫切邏輯規則的組件;
    所謂切入點是指我們要對哪些Joinpoint進行攔截的定義;
    【糯米藕】切藕的規則:距藕節3~4cm;

  2. Target(目標對象)

    代理的目標對象 (要增強的類)
    根據切入點(pointcut)的規則,找出來的需要被增強的類 / 對象。
    【糯米藕】根據切藕的規則,找出來的符號條件的藕;

  3. JoinPoint(連接點)

    所謂連接點是指那些被攔截到的點,在Spring中,這些點指的是方法,因爲Spring只支持方法類型的連接點。
    連接點是根據切入點(pointcut)的規則,在目標對象(Target)上要進行切面的那個位置,代碼中體現爲一個特定的方法;
    【糯米藕】具體的一節藕上,下刀的位置;

  4. Advice(通知/增強)

    所謂通知是指攔截到連接點之後所要做的事情就是通知;
    通知分爲前置通知,後置通知,異常通知,最終通知,環繞通知 (切面要完成的任務)
    通知(Advice)可以看做是添加到目標對象(Target)上的新的功能;
    通知體現爲類的方法;
    【糯米藕】米;

  5. Aspect(切面)

    切面(Aspect)是切入點和通知(引介)的結合
    代碼中的切面是一個理解性的概念;
    【糯米藕】在藕上下刀,形成的橫截面;

  6. Introduction(引介)

    引介是一種特殊的通知(Advice),在不修改類的代碼的前提下,Introduction可以在運行期間爲類動態添加一些方法或者Field

  7. Weaving(織入)

    把增強的應用到目標過程。【把advice應用到target的過程】
    織入在開發過程中,需要進行xml或註解配置;
    【糯米藕】在藕上下刀,把米灌入藕的全過程;

  8. Proxy(代理)

    一個類被AOP織入增強後,就產生一個結果代理類
    【糯米藕】糯米藕;

4. Spring 使用 AspectJ 進行 AOP 的開發: XML 的方式

4.1 引入jar包

		<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aspects</artifactId>
			<version>4.3.21.RELEASE</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
		<dependency>
			<groupId>aopalliance</groupId>
			<artifactId>aopalliance</artifactId>
			<version>1.0</version>
		</dependency>

4.2 引入 Spring 的配置文件

	<?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.xsd
			http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
			http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">

4.3 編寫目標類

	import com.hxzy.service.TeacherService;

	public class TeacherServiceImpl implements TeacherService{	
		@Override
		public void dianMing() {
			System.out.println("TeacherServiceImpl ... dianMing()");
			teacherDao.dianMing();
		}
	}

4.4 整合 Junit 單元測試

  • 引入 spring-test.jar
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-test</artifactId>
		<version>4.3.21.RELEASE</version>
	</dependency>

4.5 通知類型

  • 前置通知 : 在目標方法執行之前執行.
  • 後置通知 : 在目標方法執行之後執行
  • 環繞通知 : 在目標方法執行前和執行後執行
  • 異常拋出通知: 在目標方法執行出現 異常的時候 執行
  • 最終通知 : 無論目標方法是否出現異常 最終通知都會 執行.

4.6 切入點表達式

語法結構:
execution( 【方法修飾符】 方法返回值 方法所屬類 匹配方法名 ( 方法中的形參表 ) 方法申明拋出的異常 )

其中 方法返回值、匹配方法名、 方法中的形參表 的部分時不能省略的,各部分都支持通配符 “*” 來匹配全部。

比較特殊的爲形參表部分,其支持兩種通配符

“*”:代表一個任意類型的參數;
  “…”:代表零個或多個任意類型的參數。
  
  例如:

()匹配一個無參方法

(…)匹配一個可接受任意數量參數和類型的方法

(*)匹配一個接受一個任意類型參數的方法

(*,Integer)匹配一個接受兩個參數的方法,第一個可以爲任意類型,第二個必須爲Integer。

分類示例描述
通過方法簽名定義切入點execution(public * * (..))匹配所有目標類的public方法,第一個*爲返回類型,第二個*爲方法名
execution(* save* (..))匹配所有目標類以save開頭的方法,第一個*代表返回類型
execution( * *product(*,String)) 匹配目標類所有以product結尾的方法,並且其方法的參數表第一個參數可爲任意類型,第二個參數必須爲String
通過類定義切入點execution(* aop_part.Demo1.service.*(..))匹配service接口及其實現子類中的所有方法
通過包定義切入點execution(* aop_part.*(..))匹配aop_part包下的所有類的所有方法,但不包括子包
execution(* aop_part..*(..))匹配aop_part包下的所有類的所有方法,包括子包。(當".."出現再類名中時,後面必須跟“*”,表示包、子孫包下的所有類)
execution(* aop_part..*.*service.find*(..))匹配aop_part包及其子包下的所有後綴名爲service的類中,所有方法名必須以find爲前綴的方法
通過方法形參定義切入點execution(*foo(String,int))匹配所有方法名爲foo,且有兩個參數,其中,第一個的類型爲String,第二個的類型爲int
execution(* foo(String,..))匹配所有方法名爲foo,且至少含有一個參數,並且第一個參數爲String的方法(後面可以有任意個類型不限的形參)

4.7 編寫一個切面類

	import java.util.Arrays;
	
	import org.apache.log4j.Logger;
	import org.aspectj.lang.JoinPoint;
	import org.aspectj.lang.ProceedingJoinPoint;
	
	/**
	 * 增強 Advice
	 * 
	 * @author Administrator
	 */
	public class Aspect01 {
	
		Logger logger = Logger.getLogger(Aspect01.class);
	
		/**
		 * 前置增強
		 */
		public void before(JoinPoint jp) {
			logger.debug("前置增強:before");
	//		jp.getTarget() : 獲得類名 , 獲得目標對象(Target)
	//		jp.getSignature().getName() : 獲得方法名
	//		jp.getArgs() : 獲得參數數組
			logger.debug("調用" + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法,方法入參:"
					+ Arrays.toString(jp.getArgs()));
		}
	
		/**
		 * 後置增強
		 */
		public void afterReturn(JoinPoint jp, Object returnValue) {
			logger.debug("後置增強:afterReturn ... ");
	//		jp.getTarget() : 獲得類名
	//		jp.getSignature().getName() : 獲得方法名
	//		jp.getArgs() : 獲得參數數組
			logger.debug("調用" + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法," + "方法入參:"
					+ Arrays.toString(jp.getArgs()) + " ,方法返回值:" + returnValue);
		}
	
		/**
		 * 環繞增強
		 * 
		 * @return
		 */
		public Object around(ProceedingJoinPoint jp) {
			logger.debug("環繞增強 begin");
	//		jp.getTarget() : 獲得類名 , 獲得目標對象(Target)
	//		jp.getSignature().getName() : 獲得方法名
	//		jp.getArgs() : 獲得參數數組
			logger.debug("調用" + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法,方法入參:"
					+ Arrays.toString(jp.getArgs()));
	
			// 在環繞增強中,可以使用連接點的對象jp,直接調用目標方法,並得到返回值。
			Object returnValue = null; // 返回值
			try {
				returnValue = jp.proceed();
				System.out.println("返回值是:" + returnValue);
			} catch (Throwable e) {
				e.printStackTrace();
			}
			logger.debug("環繞增強 end ");
			return returnValue;
		}
	
		/**
		 * 異常增強
		 * @param jp
		 * @param e
		 */
		public void afterThrowing(JoinPoint jp, RuntimeException e) {
			logger.info(jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法發生異常,異常信息是:" + e.getMessage());
		}
	
		/**
		 * 最終增強
		 * @return
		 */
		public void after(JoinPoint jp) {
			logger.debug("最終增強 ");
			logger.debug(jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法結束執行。");
		}
	}

4.8 配置完成增強

	<!-- 配置切面類(增強) -->
	<bean id="aspect01" class="com.hxzy.aop.Aspect01"></bean>
		
	<!-- 進行 aop 的配置 -->
	<aop:config>
		<!-- 切入點:指明哪些類的哪些方法需要進行增強,是一個規則  -->
		<!-- [方法訪問修飾符] 方法返回值 包名.類名.方法名(方法的參數) -->
	    <aop:pointcut expression="execution( * com.hxzy.service..*.*(..))"  id="pointcut"/>
		<!-- 配置切面 , 織入 -->
		<aop:aspect ref="aspect01">
			<!-- 前置增強 -->
			<aop:before method="before" pointcut-ref="pointcut"/>	
			<!-- 後置增強 -->
			<aop:after-returning method="afterReturn" pointcut-ref="pointcut" returning="returnValue"/>
			<!-- 環繞增強 -->
			<aop:around method="around" pointcut-ref="pointcut"/>
			<!-- 異常增強 -->
			<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"/>
			<!-- 最終增強 -->
			<aop:after method="after" pointcut-ref="pointcut"/>
		</aop:aspect>
	</aop:config>

5. Spring 使用 AspectJ 進行 AOP 的開發: 註解配置

5.1 Spring配置文件中開啓aop註解的自動代理

	<!-- 掃描帶有註解的增強,需要引入aop的命名空間 -->
	<aop:aspectj-autoproxy />

5.2 AspectJ的AOP註解

  • 通知類型
    • @Before : 前置增強
    • @AfterReturning : 後置增強
    • @Around : 環繞增強
    • @AfterThrowing : 異常拋出增強
    • @After : 最終增強
  • 切入點
    • @Pointcut

5.3 增強類中使用註解獲得連接點信息

	
	import java.util.Arrays;
	import org.apache.log4j.Logger;
	import org.aspectj.lang.JoinPoint;
	import org.aspectj.lang.ProceedingJoinPoint;
	import org.aspectj.lang.annotation.After;
	import org.aspectj.lang.annotation.AfterReturning;
	import org.aspectj.lang.annotation.AfterThrowing;
	import org.aspectj.lang.annotation.Around;
	import org.aspectj.lang.annotation.Aspect;
	import org.aspectj.lang.annotation.Before;
	import org.aspectj.lang.annotation.Pointcut;
	import org.springframework.stereotype.Component;
	
	/**
	 * 增強 Advice
	 * @author Administrator
	 */
	@Component // 註冊bean , 配置切面 
	@Aspect //配置切面類
	public class Aspect02 {
	
		Logger logger = Logger.getLogger(Aspect02.class);
		
		// 切入點
		@Pointcut(value = "execution( * com.hxzy.service.impl.*.*(..))")
		private void anyMethod() {}
	
		/**
		 * 前置增強
		 */
	//	@Before(value="execution( * com.hxzy.service.impl.*.*(..))")
	//	@Before("execution( * com.hxzy.service.impl.*.*(..))")
		@Before("anyMethod()")
		public void before(JoinPoint jp) {
			logger.debug("前置增強:before");
			logger.debug("調用" + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法,方法入參:"
					+ Arrays.toString(jp.getArgs()));
		}
	
		/**
		 * 後置增強
		 */
		@AfterReturning(pointcut = "anyMethod()",returning = "returnValue")
		public void afterReturn(JoinPoint jp, Object returnValue) {
			logger.debug("後置增強:afterReturn ... ");
			logger.debug("調用" + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法," + "方法入參:"
					+ Arrays.toString(jp.getArgs()) + " ,方法返回值:" + returnValue);
		}
	
		/**
		 * 環繞增強
		 * @return
		 */
		@Around("anyMethod()")
		public Object around(ProceedingJoinPoint jp) {
			logger.debug("環繞增強 begin");
			logger.debug("調用" + jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法,方法入參:"
					+ Arrays.toString(jp.getArgs()));
	
			// 在環繞增強中,可以使用連接點的對象jp,直接調用目標方法,並得到返回值。
			Object returnValue = null; // 返回值
			try {
				returnValue = jp.proceed();
				System.out.println("返回值是:" + returnValue);
			} catch (Throwable e) {
				e.printStackTrace();
			}
			logger.debug("環繞增強 end ");
			return returnValue;
		}
	
		/**
		 * 異常增強
		 * @param jp
		 * @param e
		 */
		@AfterThrowing(pointcut = "anyMethod()" , throwing = "e")
		public void afterThrowing(JoinPoint jp, RuntimeException e) {
			logger.info(jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法發生異常,異常信息是:" + e.getMessage());
		}
	
		/**
		 * 最終增強
		 * @return
		 */
		@After("anyMethod()")
		public void after(JoinPoint jp) {
			logger.debug("最終增強 ");
			logger.debug(jp.getTarget() + " 的 " + jp.getSignature().getName() + " 方法結束執行。");
		}
	}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章