Spring AOP 註解和xml實現 --轉載

来源:http://www.cnblogs.com/xiadongqing/archive/2016/03/17/5289603.html
-Advertisement-
Play Games

我們現在做的一些非業務,如:日誌、事務、安全等都會寫在業務代碼中(也即是說,這些非業務類橫切於業務類),但這些代碼往往是重覆,複製——粘貼式的代碼會給程式的維護帶來不便,AOP就實現了把這些業務需求與系統需求分開來做。這種解決的方式也稱代理機制。 先來瞭解一下AOP的相關概念,《Spring參考手冊


 AOP是OOP的延續,是Aspect Oriented Programming的縮寫,意思是面向切麵編程。可以通過預編譯方式和運行期動態代理實現在不修改源代碼的情況下給程式動態統一添加功能的一種技術。 AOP實際是GoF設計模式的延續,設計模式孜孜不倦追求的是調用者和被調用者之間的解耦,AOP可以說也是這種目標的一種實現。

我們現在做的一些非業務,如:日誌、事務、安全等都會寫在業務代碼中(也即是說,這些非業務類橫切於業務類),但這些代碼往往是重覆,複製——粘貼式的代碼會給程式的維護帶來不便,AOP就實現了把這些業務需求與系統需求分開來做。這種解決的方式也稱代理機制。

先來瞭解一下AOP的相關概念,《Spring參考手冊》中定義了以下幾個AOP的重要概念,結合以上代碼分析如下:

  • 切麵(Aspect):官 方的抽象定義為“一個關註點的模塊化,這個關註點可能會橫切多個對象”,在本例中,“切麵”就是類TestAspect所關註的具體行為,例 如,AServiceImpl.barA()的調用就是切麵TestAspect所關註的行為之一。“切麵”在ApplicationContext 中<aop:aspect>來配置。
  • 連接點(Joinpoint) :程式執行過程中的某一行為,例如,UserService.get的調用或者UserService.delete拋出異常等行為。
  • 通知(Advice) :“切麵”對於某個“連接點”所產生的動作,例如,TestAspect中對com.spring.service包下所有類的方法進行日誌記錄的動作就是一個Advice。其中,一個“切麵”可以包含多個“Advice”,例如ServiceAspect。
  • 切入點(Pointcut) :匹配連接點的斷言,在AOP中通知和一個切入點表達式關聯。例如,TestAspect中的所有通知所關註的連接點,都由切入點表達式execution(* com.spring.service.*.*(..))來決定。
  • 目標對象(Target Object) :被一個或者多個切麵所通知的對象。例如,AServcieImpl和BServiceImpl,當然在實際運行時,Spring AOP採用代理實現,實際AOP操作的是TargetObject的代理對象。
  • AOP代理(AOP Proxy) : 在Spring AOP中有兩種代理方式,JDK動態代理和CGLIB代理。預設情況下,TargetObject實現了介面時,則採用JDK動態代理,例 如,AServiceImpl;反之,採用CGLIB代理,例如,BServiceImpl。強制使用CGLIB代理需要將 <aop:config>的 proxy-target-class屬性設為true。

通知(Advice)類型:

  • 前置通知(Before advice):在 某連接點(JoinPoint)之前執行的通知,但這個通知不能阻止連接點前的執行。ApplicationContext中 在<aop:aspect>裡面使用<aop:before>元素進行聲明。例如,TestAspect中的doBefore方 法。
  • 後置通知(After advice):當 某連接點退出的時候執行的通知(不論是正常返回還是異常退出)。ApplicationContext中在<aop:aspect>裡面使 用<aop:after>元素進行聲明。例如,ServiceAspect中的returnAfter方法,所以Teser中調用 UserService.delete拋出異常時,returnAfter方法仍然執行。
  • 返回後通知(After return advice):在某連接點正常完成後執行的通知,不包括拋出異常的情況。ApplicationContext中在<aop:aspect>裡面使用<after-returning>元素進行聲明。
  • 環繞通知(Around advice):包 圍一個連接點的通知,類似Web中Servlet規範中的Filter的doFilter方法。可以在方法的調用前後完成自定義的行為,也可以選擇不執 行。ApplicationContext中在<aop:aspect>裡面使用<aop:around>元素進行聲明。例 如,ServiceAspect中的around方法。
  • 拋出異常後通知(After throwing advice):在方法拋出異常退出時執行的通知。ApplicationContext中在<aop:aspect>裡面使用<aop:after-throwing>元素進行聲明。例如,ServiceAspect中的returnThrow方法。

註:可以將多個通知應用到一個目標對象上,即可以將多個切麵織入到同一目標對象。

使用Spring AOP可以基於兩種方式,一種是比較方便和強大的註解方式,另一種則是中規中矩的xml配置方式。

先說註解,使用註解配置Spring AOP總體分為兩步,

第一步是在xml文件中聲明激活自動掃描組件功能,同時激活自動代理功能:

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

	<!-- 激活組件掃描功能,在包cn.ysh.studio.spring.aop及其子包下麵自動掃描通過註解配置的組件 -->
	<context:component-scan base-package="cn.ysh.studio.spring.aop"/>
	<!-- 激活自動代理功能 -->
	<aop:aspectj-autoproxy proxy-target-class="true"/>
	
	<!-- 用戶服務對象 -->
	<bean id="userService" class="cn.ysh.studio.spring.aop.service.UserService" />

</beans>
 
第二步是為Aspect切麵類添加註解:
/**
 * 系統服務組件Aspect切麵Bean
 * @author Shenghany
 * @date 2013-5-28
 */
//聲明這是一個組件
@Component
//聲明這是一個切麵Bean
@Aspect
public class ServiceAspect {

	private final static Log log = LogFactory.getLog(ServiceAspect.class);
	
	//配置切入點,該方法無方法體,主要為方便同類中其他方法使用此處配置的切入點
	@Pointcut("execution(* cn.ysh.studio.spring.aop.service..*(..))")
	public void aspect(){	}
	
	/*
	 * 配置前置通知,使用在方法aspect()上註冊的切入點
	 * 同時接受JoinPoint切入點對象,可以沒有該參數
	 */
	@Before("aspect()")
	public void before(JoinPoint joinPoint){
		if(log.isInfoEnabled()){
			log.info("before " + joinPoint);
		}
	}
	
	//配置後置通知,使用在方法aspect()上註冊的切入點
	@After("aspect()")
	public void after(JoinPoint joinPoint){
		if(log.isInfoEnabled()){
			log.info("after " + joinPoint);
		}
	}
	
	//配置環繞通知,使用在方法aspect()上註冊的切入點
	@Around("aspect()")
	public void around(JoinPoint joinPoint){
		long start = System.currentTimeMillis();
		try {
			((ProceedingJoinPoint) joinPoint).proceed();
			long end = System.currentTimeMillis();
			if(log.isInfoEnabled()){
				log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms!");
			}
		} catch (Throwable e) {
			long end = System.currentTimeMillis();
			if(log.isInfoEnabled()){
				log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms with exception : " + e.getMessage());
			}
		}
	}
	
	//配置後置返回通知,使用在方法aspect()上註冊的切入點
	@AfterReturning("aspect()")
	public void afterReturn(JoinPoint joinPoint){
		if(log.isInfoEnabled()){
			log.info("afterReturn " + joinPoint);
		}
	}
	
	//配置拋出異常後通知,使用在方法aspect()上註冊的切入點
	@AfterThrowing(pointcut="aspect()", throwing="ex")
	public void afterThrow(JoinPoint joinPoint, Exception ex){
		if(log.isInfoEnabled()){
			log.info("afterThrow " + joinPoint + "\t" + ex.getMessage());
		}
	}
	
}
測試代碼:
/**
 * Spring AOP測試
 * @author Shenghany
 * @date 2013-5-28
 */
public class Tester {

	private final static Log log = LogFactory.getLog(Tester.class);
	
	public static void main(String[] args) {
		//啟動Spring容器
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
		//獲取service組件
		UserService service = (UserService) context.getBean("userService");
		//以普通的方式調用UserService對象的三個方法
		User user = service.get(1L);
		service.save(user);
		try {
			service.delete(1L);
		} catch (Exception e) {
			if(log.isWarnEnabled()){
				log.warn("Delete user : " + e.getMessage());
			}
		}
	}
}

控制台輸出如下:

 

 INFO [spring.aop.aspect.ServiceAspect:40] before execution(User cn.ysh.studio.spring.aop.service.UserService.get(long))
 INFO [spring.aop.service.UserService:19] getUser method . . .
 INFO [spring.aop.aspect.ServiceAspect:60] around execution(User cn.ysh.studio.spring.aop.service.UserService.get(long))	Use time : 42 ms!
 INFO [spring.aop.aspect.ServiceAspect:48] after execution(User cn.ysh.studio.spring.aop.service.UserService.get(long))
 INFO [spring.aop.aspect.ServiceAspect:74] afterReturn execution(User cn.ysh.studio.spring.aop.service.UserService.get(long))
 INFO [spring.aop.aspect.ServiceAspect:40] before execution(void cn.ysh.studio.spring.aop.service.UserService.save(User))
 INFO [spring.aop.service.UserService:26] saveUser method . . .
 INFO [spring.aop.aspect.ServiceAspect:60] around execution(void cn.ysh.studio.spring.aop.service.UserService.save(User))	Use time : 2 ms!
 INFO [spring.aop.aspect.ServiceAspect:48] after execution(void cn.ysh.studio.spring.aop.service.UserService.save(User))
 INFO [spring.aop.aspect.ServiceAspect:74] afterReturn execution(void cn.ysh.studio.spring.aop.service.UserService.save(User))
 INFO [spring.aop.aspect.ServiceAspect:40] before execution(boolean cn.ysh.studio.spring.aop.service.UserService.delete(long))
 INFO [spring.aop.service.UserService:32] delete method . . .
 INFO [spring.aop.aspect.ServiceAspect:65] around execution(boolean cn.ysh.studio.spring.aop.service.UserService.delete(long))	Use time : 5 ms with exception : spring aop ThrowAdvice演示
 INFO [spring.aop.aspect.ServiceAspect:48] after execution(boolean cn.ysh.studio.spring.aop.service.UserService.delete(long))
 INFO [spring.aop.aspect.ServiceAspect:74] afterReturn execution(boolean cn.ysh.studio.spring.aop.service.UserService.delete(long))
 WARN [studio.spring.aop.Tester:32] Delete user : Null return value from advice does not match primitive return type for: public boolean cn.ysh.studio.spring.aop.service.UserService.delete(long) throws java.lang.Exception
xml配置方式,其實也一樣簡單:
<?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/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
		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-3.0.xsd">


	<!-- 系統服務組件的切麵Bean -->
	<bean id="serviceAspect" class="cn.ysh.studio.spring.aop.aspect.ServiceAspect"/>
	<!-- AOP配置 -->
	<aop:config>
		<!-- 聲明一個切麵,並註入切麵Bean,相當於@Aspect -->
		<aop:aspect id="simpleAspect" ref="serviceAspect">
			<!-- 配置一個切入點,相當於@Pointcut -->
			<aop:pointcut expression="execution(* cn.ysh.studio.spring.aop.service..*(..))" id="simplePointcut"/>
			<!-- 配置通知,相當於@Before、@After、@AfterReturn、@Around、@AfterThrowing -->
			<aop:before pointcut-ref="simplePointcut" method="before"/>
			<aop:after pointcut-ref="simplePointcut" method="after"/>
			<aop:after-returning pointcut-ref="simplePointcut" method="afterReturn"/>
			<aop:after-throwing pointcut-ref="simplePointcut" method="afterThrow" throwing="ex"/>
		</aop:aspect>
	</aop:config>

</beans>

 

通常情況下,表達式中使用”execution“就可以滿足大部分的要求。表達式格式如下:

1execution(* *(..))

表示匹配所有方法

2execution(public * com. savage.service.UserService.*(..))
表示匹配com.savage.server.UserService中所有的公有方法
3execution(* com.savage.server..*.*(..))
表示匹配com.savage.server包及其子包下的所有方法
除了execution表示式外,還有withinthistargetargsPointcut表示式。一個Pointcut定義由Pointcut表示式和Pointcut簽名組成

 

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) 
  • modifiers-pattern:方法的操作許可權
  • :返回值
  • :方法所在的包
  • :方法名
  • :參數名
  • :異常
ret-type-patterndeclaring-type-patternname-patternparm-patternthrows-pattern

其中,除ret-type-pattern和name-pattern之外,其他都是可選的。上例中,execution(* com.spring.service.*.*(..))表示com.spring.service包下,返回值為任意類型;方法名任意;參數不作限制的所有方法。

 

Pointcut定義時,還可以使用&&||! 運算,如
  1. @Pointcut("execution(* com.savage.aop.MessageSender.*(..))")
  2. private void logSender(){}
  3. @Pointcut("execution(* com.savage.aop.MessageReceiver.*(..))")
  4. private void logReceiver(){}
  5. @Pointcut("logSender() || logReceiver()")
  6. private void logMessage(){}

 

來源:http://ntzrj513.blog.163.com/blog/static/27945612201362232315/

 來源:http://www.eclipse.org/aspectj/doc/released/runtime-api/org/aspectj/lang/Signature.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本篇分為兩部分: 一、Swift中的方法嵌套 二、Swift中的命名空間 在 swift 中我們可以讓方法嵌套方法,如: 我們之前在使用 OC 開發時,它是沒有命名空間的,所有的代碼和引用的靜態庫最終都會被編譯到同一個域和二進位中,這樣的後果是一旦我們有重覆的類名的話,就會導致編譯時的衝突和失敗。為
  • 表達式樹: 葉子是操作數,其餘結點為操作符,是二叉樹的其中一種應用 我是分割線 一棵表達式樹如下圖: 若是對它做中序遍歷,則可以得到中綴表達式 做後序遍歷,則可以得到尾碼表達式 已知樹的結點可以表示成: 用尾碼表達式構建一棵表達式樹: 思路:(與尾碼表達式計算四則運算結構相似) 1. 一一讀入輸入字
  • & 160;& 160;& 160;& 160; "上一篇隨筆" 介紹瞭如何使用Gradle內建任務,介紹了自定義Gradle任務類的三種方法(build文件,buildSrc文件夾、新建groovy項目),一個任務是一個原子操作,即不可分割的。項目開發過程中,我們往往需要按照一定順序執行多個任務以
  • 引用:http://jzinfo.javaeye.com/blog/519470 Java的"對象序列化"能讓你將一個實現了Serializable介面的對象轉換成一組byte,這樣日後要用這個對象時候,你就能把這些byte數據恢復出來,並據此重新構建那個對象了。這一點甚至在跨網路的環境下也是如此,...
  • 定義:Threading用於提供線程相關的操作,線程是應用程式中工作的最小單元。 上述代碼創建了10個“前臺”線程,然後控制器就交給了CPU,CPU根據指定演算法進行調度,分片執行指令。 更多方法: 線程鎖 由於線程之間是進行隨機調度,並且每個線程可能只執行n條執行之後,CPU接著執行其他線程。所以,
  • 多態 概念:指同一操作作用於某一類對象,可以有不同的解釋,產生不同的執行結果; 存在的必要條件 ① 需要存在繼承和實現關係; ② 同樣的方法調用而執行不同操作,運行不同代碼(重寫操作); ③ 在運行時父類或者介面的引用變數可以引用其子類的對象; 作用 ① 多態通過分離做什麼和怎麼做,從另一個角度將接
  • 面向對象三大特點:封裝、繼承、多態 封裝概念 ① 將東西包裝在一起,然後以新的完整形式呈現出來: 將方法和欄位一起包裝到一個單元中,單元以類的形式實現; ② 信息隱藏,隱藏對象的實現細節,不讓外部直接訪問到; ③ 將數據和方法包裝進類中,加上具體實現的隱藏,共同被稱作封裝,其結果是一個同時帶有特征和
  • Delphi Seatle can link Delphi project with Static library files(*.a): 1.at Delphi IDE, Add the "*.a" file Path to Library Path: a. Tools Menu b.Option
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...