天天看點

Spring AOP 與 AspectJ一、介紹二、 AOP術語三、AOP介紹四、面向切面程式設計

文章目錄

  • 一、介紹
  • 二、 AOP術語
  • 三、AOP介紹
    • 3.1 什麼是AOP ?
    • 3.2 Spring通知類型
  • 四、面向切面程式設計
    • 4.1 面向切面AspectJ
    • 4.2 注解式的Aspect

一、介紹

在傳統的業務處理代碼中,通常都會進行事務處理、日志記錄等操作。雖然使用OOP可以通過組合或者繼承的方式來達到代碼的重用,但如果要實作某個功能(如日志記錄),同樣的代碼仍然會分散到各個方法中。這樣,如果想要關閉某個功能,或者對其進行修改,就必須要修改所有的相關方法。這不但增加了開發人員的工作量,而且提高了代碼的出錯率
AOP采取橫向抽取機制,将分散在各個方法中的重複代碼提取出來,然後在程式編譯或運作時,再将這些提取出來的代碼應用到需要執行的地方。這種采用橫向抽取機制的方式,采用傳統的OOP思想顯然是無法辦到的,因為OOP隻能實作父子關系的縱向的重用。雖然AOP是一種新的程式設計思想,但卻不是OOP的替代品,它隻是OOP的延伸和補充

二、 AOP術語

Spring AOP 與 AspectJ一、介紹二、 AOP術語三、AOP介紹四、面向切面程式設計
  • Advice(通知/增強處理):AOP架構在特定的切入點執行的增強處理,即在定義好的切入點處所要執行的程式代碼。可以将其了解為切面類中的方法
  • Target Object(目标對象):指所有被通知的對象,也被稱為被增強對象。如果AOP架構采用的是動态的AOP實作,那麼該對象就是一個被代理對象
  • Proxy(代理):将通知應用到目标對象之後,被動态建立的對象
  • Weaving(織入):将切面代碼插入到目标對象上,進而生成代理對象的過程

三、AOP介紹

3.1 什麼是AOP ?

  • AOP采取橫向抽取機制,将分散在各個方法中的重複代碼提取出來
  • 在程式編譯或運作時,再将這些提取出來的代碼應用到需要執行的地方

3.2 Spring通知類型

前置通知、後置通知、環繞通知、異常抛出通知、引介通知

  • org.springframework.aop.MethodBeforeAdvice(前置通知)

    在目标方法執行前實施增強,可以應用于權限管理等功能

  • org.springframework.aop.AfterReturningAdvice(後置通知)

    在目标方法執行後實施增強,可以應用于關閉流、上傳檔案、删除臨時檔案等功能

  • org.aopalliance.intercept.MethodInterceptor(環繞通知)

    在目标方法執行前後實施增強,可以應用于日志、事務管理等功能

  • org.springframework.aop.ThrowsAdvice(異常抛出通知)

    在方法抛出異常後實施增強,可以應用于處理異常記錄日志等功能

  • org.springframework.aop.IntroductionInterceptor(引介通知)

    在目标類中添加一些新的方法和屬性,可以應用于修改老版本程式

四、面向切面程式設計

4.1 面向切面AspectJ

  • 自定義的切面
public class MyAspect {
	
	// 前置通知
	public void beforeProxy(JoinPoint joinPoint) {
		System.out.print("前置通知: 模拟執行權限檢查, ");
		System.out.print("目标類是: " + joinPoint.getTarget());
		System.out.println(", 被織入的方法: " + joinPoint.getSignature().getName());
	}
	
	// 後置通知
	public void afterReturningProxy(JoinPoint joinPoint) {
		System.out.print("後置通知:模拟記錄日志...");
		System.out.print("目标類是:" + joinPoint.getTarget());
		System.out.println(", 被織入的目标方法: " + joinPoint.getSignature().getName());
	}
	
	// ProceedingJoinPoint是 JoinPoint的子接口, 表示可以執行的目标方法
	// 使用proceedingJoinPoint為參數,必須傳回一個對象 Object
	// 必須接收一個參數,類型為 ProceedingJoinPoint
	// 必須抛出異常 throws Throwable
	public Object aroundProxy(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
		
		System.out.println("環繞通知開始執行, 模拟開啟事務: ");
		
		Object obj = proceedingJoinPoint.proceed();
		
		System.out.println("環繞通知結束, 模拟關閉事務...");
		return obj;
	}
	
	public void afterThrowingProxy(JoinPoint joinPoint, Throwable exception) {
		System.out.println("抛出異常: " + exception.getMessage());
	}
	
	// 最終通知
	public void afterProxy() {
		System.out.println("最終通知,結束後釋放資源... ");
	}
	
}
           
  • 編寫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:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">

	<!-- 1 目标類 -->
	<bean id="userDAO" class="com.hlq.AspectJ.UserDAOImpl" />
	<!-- 2 切面 -->
	<bean id="myAspect" class="com.hlq.AspectJ.MyAspect" />
	<!-- 3 aop程式設計 -->

	<aop:config>
		<!-- 配置切面 -->
		<aop:aspect ref="myAspect">
			<!-- 3.1 配置切入點,通知最後增強哪些方法 -->
			<aop:pointcut
				expression="execution(* com.hlq.AspectJ.*.add*(..))"
				id="addPointCut" />
			
			<!-- 配置另一個切入點 -->
			<aop:pointcut expression="execution(* com.hlq.AspectJ.*.delete*(..))" id="deletePointCut"/>
			<!-- 3.2 關聯通知Advice和切入點pointCut -->
			
			<!-- 3.2.1 前置通知 -->
			<aop:before method="beforeProxy" pointcut-ref="addPointCut" />
			<!-- 3.2.2 後置通知,在方法傳回之後執行,就可以獲得傳回值 returning屬性:用于設定後置通知的第二個參數的名稱,類型是Object -->
			<aop:after-returning method="afterReturningProxy"
				pointcut-ref="deletePointCut" returning="returnVal" />
			<!-- 3.2.3 環繞通知 -->
			<aop:around method="aroundProxy" pointcut-ref="addPointCut" />
			<!-- 3.2.4 抛出通知:用于處理程式發生異常-->
			<!-- * 注意:如果程式沒有異常,将不會執行增強 -->
			<!-- * throwing屬性:用于設定通知第二個參數的名稱,類型Throwable -->
			<aop:after-throwing method="afterThrowingProxy"
				pointcut-ref="addPointCut" throwing="exception" />
			<!-- 3.2.5 最終通知:無論程式發生任何事情,都将執行 -->
			<aop:after method="afterProxy" pointcut-ref="addPointCut" />
			
		</aop:aspect>
	</aop:config>
</beans>

           
  • 測試
public class UserDAOImpl implements UserDAO {
	
	public void addUser() {
		System.out.println("UserDAO addUser is called... ");
	}
	
	public void deleteUser() {
		System.out.println("UserDAO deleteUser is called...");
	}
	
	public UserDAOImpl() {
		super();
	}
	
}
           
public class AspectTest {
	
	public static void main(String[] args) {
		
		String xmlPath = "com/hlq/AspectJ/aspectConf.xml";
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
		
		UserDAO userDAO = (UserDAO)applicationContext.getBean("userDAO");
		
		userDAO.addUser();
		userDAO.deleteUser();
		
	}
	
}
           
前置通知: 模拟執行權限檢查, 目标類是: [email protected], 被織入的方法: addUser
環繞通知開始執行, 模拟開啟事務: 
UserDAO addUser is called... 
最終通知,結束後釋放資源... 
環繞通知結束, 模拟關閉事務...
UserDAO deleteUser is called...
後置通知:模拟記錄日志...目标類是:[email protected], 被織入的目标方法: deleteUser
           

4.2 注解式的Aspect

  • 定義注解切面
@Aspect
@Component
public class AspectJ {

	// 定義切入點表達式
	@Pointcut("execution(* com.hlq.annotationAspect.*.add*(..))")
	// 使用一個傳回值為void、方法體為空的方法來命名切入點
	private void addPointCut() {
	}

	@Pointcut("execution(* com.hlq.annotationAspect.*.delete*(..))")
	public void deletePointCut() {
	}

	// 前置通知
	@Before("addPointCut()")
	public void beforeProxy(JoinPoint joinPoint) {
		System.out.print("前置通知: 模拟執行權限檢查, ");
		System.out.print("目标類是: " + joinPoint.getTarget());
		System.out.println(", 被織入的方法: " + joinPoint.getSignature().getName());
	}

	// 後置通知
	@AfterReturning("deletePointCut()")
	public void afterReturningProxy(JoinPoint joinPoint) {
		System.out.print("後置通知:模拟記錄日志...");
		System.out.print("目标類是:" + joinPoint.getTarget());
		System.out.println(", 被織入的目标方法: " + joinPoint.getSignature().getName());
	}

//	// ProceedingJoinPoint是 JoinPoint的子接口, 表示可以執行的目标方法
//	// 使用proceedingJoinPoint為參數,必須傳回一個對象 Object
//	// 必須接收一個參數,類型為 ProceedingJoinPoint
//	// 必須抛出異常 throws Throwable
//	@Around("myPointCut()")
//	public Object aroundProxy(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//
//		System.out.println("環繞通知開始執行, 模拟開啟事務: ");
//
//		Object obj = proceedingJoinPoint.proceed();
//
//		System.out.println("環繞通知結束, 模拟關閉事務...");
//		return obj;
//	}
//
//	@AfterThrowing(value="myPointCut()", throwing="exception")
//	public void afterThrowingProxy(JoinPoint joinPoint, Throwable exception) {
//		System.out.println("抛出異常: " + exception.getMessage());
//	}
//
//	// 最終通知
//	@After("myPointCut()")
//	public void afterProxy() {
//		System.out.println("最終通知,結束後釋放資源... ");
//	}

}

           
  • 配置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:aop="http://www.springframework.org/schema/aop"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
  	http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
  	http://www.springframework.org/schema/aop 
  	http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
  	http://www.springframework.org/schema/context 
  	http://www.springframework.org/schema/context/spring-context-4.3.xsd">

	<!-- 指定需要掃描的包,使注解生效 -->
	<context:component-scan base-package="com.hlq.annotationAspect" />

	<!-- 啟動基于注解的聲明式AspectJ支援 -->
	<aop:aspectj-autoproxy />

</beans>

           
  • 編寫任意測試類
@Repository("userDAO")
public class UserDAOImpl implements UserDAO {
	
	public void addUser() {
		System.out.println("UserDAO addUser is called... ");
	}
	
	public void deleteUser() {
		System.out.println("UserDAO deleteUser is called...");
	}

	
}
           
public class AspectTest {
	
	public static void main(String[] args) {
		
		String xmlPath = "com/hlq/annotationAspect/aspectConf.xml";
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
		
		UserDAO userDAO = (UserDAO)applicationContext.getBean("userDAO");
		
		userDAO.addUser();
		userDAO.deleteUser();
		
	}
	
}

           
前置通知: 模拟執行權限檢查, 目标類是: [email protected], 被織入的方法: addUser
UserDAO addUser is called... 
UserDAO deleteUser is called...
後置通知:模拟記錄日志...目标類是:com.hl[email protected], 被織入的目标方法: deleteUser