spring切面編程AOP


一. AOP 概念

AOP

AOP(Aspect Oriented Programming),即面向切面編程,可以說是OOP(Object Oriented Programming,面向對象編程)的補充和完善。OOP引入封裝、繼承、多態等概念來建立一種對象層次結構,用於模擬公共行爲的一個集合。不過OOP允許開發者定義縱向的關係,但並不適合定義橫向的關係,例如日誌功能。日誌代碼往往橫向地散佈在所有對象層次中,而與它對應的對象的核心功能毫無關係對於其他類型的代碼,如安全性、異常處理和透明的持續性也都是如此,這種散佈在各處的無關的代碼被稱爲橫切(cross cutting),在OOP設計中,它導致了大量代碼的重複,而不利於各個模塊的重用。

AOP技術恰恰相反,它利用一種稱爲"橫切"的技術,剖解開封裝的對象內部,並將那些影響了多個類的公共行爲封裝到一個可重用模塊,並將其命名爲"Aspect",即切面。所謂"切面",簡單說就是那些與業務無關,卻爲業務模塊所共同調用的邏輯或責任封裝起來,便於減少系統的重複代碼,降低模塊之間的耦合度,並有利於未來的可操作性和可維護性。

使用"橫切"技術,AOP把軟件系統分爲兩個部分:核心關注點橫切關注點。業務處理的主要流程是核心關注點,與之關係不大的部分是橫切關注點。橫切關注點的一個特點是,他們經常發生在覈心關注點的多處,而各處基本相似,比如權限認證、日誌、事物。AOP的作用在於分離系統中的各種關注點,將核心關注點和橫切關注點分離開來。

 

AOP核心概念

面向切面編程

(1)通知(增強)Advice

  通知定義了切面是什麼以及何時使用,應該應用在某個方法被調用之前?之後?還是拋出異常時?等等。

(2)連接點 Join point

  連接點是在應用執行過程中能夠插入切面的一個點。這個點可以是調用方法時,拋出異常時,甚至修改一個字段時。切面代碼可以利用這些點插入到應用的正常流程中,並添加新的行爲。

(3)切點 Pointcut

  切點有助於縮小切面所通知的連接點的範圍。如果說通知定義了切面的“什麼”和“何時”的話,那麼切點就定義了“何處”,切點會匹配通知所要織入的一個或多個連接點,一般常用正則表達式定義所匹配的類和方法名稱來指定這些切點。

(4)切面 Aspect

  切面是通知和切點的結合。通知和切點定義了切面的全部內容——它是什麼,在何時何處完成其功能。

(5)引入 Introduction

  引入允許我們向現有的類添加新方法或屬性,從而無需修改這些現有類的情況下,讓他們具有新的行爲和狀態。

(6)織入 Weaving

  在過去我常常把織入與引入的概念混淆,我是這樣來辨別的,“引入”我把它看做是一個定義,也就是一個名詞,而“織入”我把它看做是一個動作,一個動詞,也就是切面在指定的連接點被織入到目標對象中。

總結

  通知包含了需要用於多個應用對象的橫切行爲;連接點是程序執行過程中能夠應用通知的所有點;切點定義了通知被應用的具體位置(在哪些連接點)。其中關鍵的概念是切點定義了哪些連接點會得到通知(增強)。創建切點來定義切面所織入的連接點是AOP框架的基本功能。

  另外,Spring是基於動態代理的,所以Spring只支持方法連接點,而像AspectJ和JBoss除了方法切點,它們還提供字段和構造器接入點。如果需要方法攔截之外的連接點攔截功能,則可以利用AspectJ來補充SpringAOP的功能。

 

Spring對AOP的支持

Spring中AOP代理由Spring的IOC容器負責生成、管理,其依賴關係也由IOC容器負責管理。因此,AOP代理可以直接使用容器中的其它bean實例作爲目標,這種關係可由IOC容器的依賴注入提供。Spring創建代理的規則爲:

1、默認使用Java動態代理來創建AOP代理,這樣就可以爲任何接口實例創建代理了

2、當需要代理的類不是代理接口的時候,Spring會切換爲使用CGLIB代理,也可強制使用CGLIB

AOP編程其實是很簡單的事情,縱觀AOP編程,程序員只需要參與三個部分:

1、定義普通業務組件

2、定義切入點,一個切入點可能橫切多個業務組件

3、定義增強處理,增強處理就是在AOP框架爲普通業務組件織入的處理動作

所以進行AOP編程的關鍵就是定義切入點和定義增強處理,一旦定義了合適的切入點和增強處理,AOP框架將自動生成AOP代理,即:代理對象的方法=增強處理+被代理對象的方法。

 

二. 基於AspectJ的AOP簡單實現:

首先,在 applicationContext.xml 核心配置文件中需要用到 AOP 命名空間,和 兩個架包

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aspects</artifactId>
			<version>5.0.8.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>aopalliance</groupId>
			<artifactId>aopalliance</artifactId>
			<version>1.0</version>
		</dependency>

註釋在類上

@Aspect     聲明該類爲 切面類
@Order(2)   當同一個目標方法有多個切面的時,哪個切面類先執行,取決於在切面類上的註解@order(值小的先執行)

註解的這個方法在AOP裏叫:通知(advice)

@Before 前置通知(在目標方法執行之前執行)

@After 後置通知(在目標方法執行之後執行)無論有沒有異常拋出都會執行

@AfterReturning 返回通知(在目標方法返回結果之後執行)

@AfterThrowing 異常通知(在目標方法跑出異常之後執行)

@Around 環繞通知(圍繞着方法執行)

 

1. AOP 採用註釋 操作:  

<?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: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-4.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
	
	<!-- 使用註解驅動 自動註冊bean  -->
	<context:component-scan base-package="cn.jq.springdemo"></context:component-scan>
	<!-- 使用註解驅動 , 讓切面類裏的方法上的註釋起作用  -->
	<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
	
</beans>

2.  定義一個操作類:

@Component
public class HelloAop {

	public int add(int a, int b) {
		return a+b;
	}
	public int div(int a, int b) {
		return a/b;
	}
}

3. 自定義兩個橫切關注點:

     打印當前時間:

@Component
@Aspect
@Order(1)
public class TimeAscept {
	//除法上加入切入點
	@Before("execution(public int cn.jq.springdemo.aop.HelloAop.div(int, int))")
	public void printTime() {
		System.out.println("CurrentTime = " + System.currentTimeMillis());
	}
}

  日誌記錄:

AspectJ 切入點表達式

execution(* cn.jq.springdemo.aop.HelloAop.*(..)) : 匹配HelloAop中聲明的所有方法,

第一個 * 代表任意修飾符及任意返回值. 第二個 * 代表任意方法。 .. 代表匹配任意數量的參數。

@Component
@Aspect	//切面類
@Order(2) //值越小,優先級越高
public class LoggerAscept {

	Logger logger = Logger.getLogger(this.getClass());
	
	//定義切入點表達式
	@Pointcut("execution(* cn.jq.springdemo.aop.HelloAop.*(..))")
	public void joinPointExpression() {}
	
	@Before("joinPointExpression()")
	public void beforeMethod(org.aspectj.lang.JoinPoint joinPoint) {
		String methodName = joinPoint.getSignature().getName();	//獲取方法名
		List<Object> args = Arrays.asList(joinPoint.getArgs()); //獲取參數數組
		logger.info(methodName+"方法執行之前,參數:" + args);
	}
	
	@After("joinPointExpression()")
	public void afterMethod(JoinPoint joinPoint) {
		String methodName = joinPoint.getSignature().getName();
		List<Object> args = Arrays.asList(joinPoint.getArgs());
		logger.info(methodName+"方法執行之後,參數:" + args);
	}
	
	@AfterReturning(value="joinPointExpression()", returning="rs")
	public void afterReturningMethod(JoinPoint joinPoint, Object rs) {
		String methodName = joinPoint.getSignature().getName();
		List<Object> args = Arrays.asList(joinPoint.getArgs());
		logger.info(methodName+"方法(無異常)執行之後,參數:" + args + ",結果:"+ rs);
	}
	
	@AfterThrowing(value="joinPointExpression()",throwing="ex")
	public void afterThrowingMethod(JoinPoint joinPoint, Exception ex) {
		String methodName = joinPoint.getSignature().getName();
		List<Object> args = Arrays.asList(joinPoint.getArgs());
		logger.info(methodName+"方法執行時發生異常啦,參數:" + args + ",異常信息:" + ex.getMessage());
	}
}

4. 測試類:

public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
	
		HelloAop helloAop = (HelloAop) context.getBean("helloAop");
		helloAop.add(2, 2);
		helloAop.div(10, 0);
	}

----結果--

2018-08-25 13:35:25,442 INFO [cn.jq.springdemo.aop.LoggerAscept.beforeMethod(LoggerAscept.java:32)] add方法執行之前,參數:[2, 2]  
   2018-08-25 13:35:25,456 INFO [cn.jq.springdemo.aop.LoggerAscept.afterMethod(LoggerAscept.java:39)] add方法執行之後,參數:[2, 2]  
   2018-08-25 13:35:25,456 INFO [cn.jq.springdemo.aop.LoggerAscept.afterReturningMethod(LoggerAscept.java:46)] add方法(無異常)執行之後,參數:[2, 2],結果:4  
   CurrentTime = 1535175325456
2018-08-25 13:35:25,456 INFO [cn.jq.springdemo.aop.LoggerAscept.beforeMethod(LoggerAscept.java:32)] div方法執行之前,參數:[10, 0]  
   2018-08-25 13:35:25,457 INFO [cn.jq.springdemo.aop.LoggerAscept.afterMethod(LoggerAscept.java:39)] div方法執行之後,參數:[10, 0]  
   2018-08-25 13:35:25,457 INFO [cn.jq.springdemo.aop.LoggerAscept.afterThrowingMethod(LoggerAscept.java:53)] div方法執行時發生異常啦,參數:[10, 0],異常信息:/ by zero  
   Exception in thread "main" java.lang.ArithmeticException: / by zero
	at cn.jq.springdemo.aop.HelloAop.div(HelloAop.java:12)
	at cn.jq.springdemo.aop.HelloAop$$FastClassBySpringCGLIB$$c897cdd6.invoke(<generated>)

此時,AOP 的四種通知  簡單實現。

環繞通知:就是把前面4中通知全給整合在一起。使用它必須要求: 

1、必須要帶參數 ProceedingJoinPoint 類型的參數,這個參數可以直接調用原來的目標方法。

2、環繞通知方法必須有返回值,這個反正值就是目標方法的返回值。

@Component
@Aspect	//橫切關注點
@Order(2) //值越小,優先級越高
public class LoggerAscept {

	Logger logger = Logger.getLogger(this.getClass());
	
	//定義切入點表達式
	@Pointcut("execution(* cn.jq.springdemo.aop.HelloAop.*(..))")
	public void joinPointExpression() {}
	
	@Around("joinPointExpression()")
	public Object aroundMethod(ProceedingJoinPoint pjp) {
		Object rs = null;
		String methodName = pjp.getSignature().getName();
		try {
			//前置通知
			logger.info(methodName+"方法執行之前");
			rs = pjp.proceed(); //在這裏執行目標方法
			//返回通知
			logger.info(methodName+"方法(無異常)執行之後,結果:"+ rs);
		} catch (Throwable e) {
			//異常通知
			logger.info(methodName+"方法執行時發生異常啦,異常信息:" + e.getMessage());
			e.printStackTrace();
		}
		//後置通知
		logger.info(methodName+"方法執行之後, 結果" + rs);
		return rs;
	}

}
----結果一樣---

基於 xml 配置文件的 AOP 使用

<?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: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-4.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
	
	<!-- 使用註解驅動 自動註冊bean  -->
	<context:component-scan base-package="cn.jq.springdemo"></context:component-scan>
	<!-- 使用註解驅動AOP , 讓切面類裏的方法上的註釋起作用  -->
	<!-- <aop:aspectj-autoproxy></aop:aspectj-autoproxy> -->
	
	<!-- 使用xml配置AOP  -->
	<aop:config>
		<!-- 配置切面點表達式 -->
		<aop:pointcut expression="execution(* cn.jq.springdemo.aop.HelloAop.*(..))" id="pointCutLogger"/>
		<aop:pointcut expression="execution(public int cn.jq.springdemo.aop.HelloAop.div(int, int))" id="pointCutTime"/>
		<!-- 配置切面和通知 -->
		<aop:aspect ref="loggerAscept" order="2">
			<aop:before method="beforeMethod" pointcut-ref="pointCutLogger"/>
			<aop:after method="afterMethod" pointcut-ref="pointCutLogger"/>		<!-- 注意方法名 參數名一致 -->
			<aop:after-returning method="afterReturningMethod" pointcut-ref="pointCutLogger" returning="rs"/>
			<aop:after-throwing method="afterThrowingMethod" pointcut-ref="pointCutLogger" throwing="ex"/>
		</aop:aspect>
		<aop:aspect ref="timeAscept" order="1">
			<aop:before method="printTime" pointcut-ref="pointCutTime"/>
		</aop:aspect>
	</aop:config>
	
</beans>

 

 

 

 

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