【Spring框架學習二】Spring的AOP通俗理解以及AOP的入門開發


Spring框架學習一中主要講的是一些Spring的概述、Spring工廠、Spring屬性注入以及IOC入門,其中最重要的是IOC,上一篇中IOC大概講的小結一下:
在這裏插入圖片描述

然後呢這一篇中主要講一下Spring中除了IOC之外的另一個重要的核心:AOP,在Spring中IOC也好,AOP也好,都必須會二者的XML開發以及註解開發,也就是說IOC和AOP的XML開發以及註解開發都要掌握

1、 AOP 的概述

從專業的角度來講(千萬不要問我有多專業,度娘是我表鍋不對是表嫂QAQ):

在軟件業,AOP爲Aspect Oriented Programming的縮寫,意爲:面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。AOP是OOP的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。

從通俗易懂且不失風趣的角度來講:(來自武哥文章談談Spring中的IOC和AOP概念

面向切面編程的目標就是分離關注點。什麼是關注點呢?就是你要做的事,就是關注點。假如你是個公子哥,沒啥人生目標,天天就是衣來伸手,飯來張口,整天只知道玩一件事!那麼,每天你一睜眼,就光想着吃完飯就去玩(你必須要做的事),但是在玩之前,你還需要穿衣服、穿鞋子、疊好被子、做飯等等等等事情,這些事情就是你的關注點,但是你只想吃飯然後玩,那麼怎麼辦呢?這些事情通通交給別人去幹。在你走到飯桌之前,有一個專門的僕人A幫你穿衣服,僕人B幫你穿鞋子,僕人C幫你疊好被子,僕人C幫你做飯,然後你就開始吃飯、去玩(這就是你一天的正事),你幹完你的正事之後,回來,然後一系列僕人又開始幫你幹這個幹那個,然後一天就結束了!
AOP的好處就是你只需要幹你的正事,其它事情別人幫你幹。也許有一天,你想裸奔,不想穿衣服,那麼你把僕人A解僱就是了!也許有一天,出門之前你還想帶點錢,那麼你再僱一個僕人D專門幫你幹取錢的活!這就是AOP。每個人各司其職,靈活組合,達到一種可配置的、可插拔的程序結構。
從Spring的角度看,AOP最大的用途就在於提供了事務管理的能力。事務管理就是一個關注點,你的正事就是去訪問數據庫,而你不想管事務(太煩),所以,Spring在你訪問數據庫之前,自動幫你開啓事務,當你訪問數據庫結束之後,自動幫你提交/回滾事務!

1、1 爲什麼學習 AOP

Spring 的 AOP 的由來:AOP 最早由 AOP 聯盟的組織提出的,制定了一套規範.Spring 將 AOP 思想引入到框架中,必須遵守 AOP 聯盟的規範.

Aop解決實際開發中的一些問題:

  • AOP 解決 OOP 中遇到的一些問題.是 OOP 的延續和擴展.

對程序進行增強:不修改源碼的情況下:

  • AOP 可以進行權限校驗,日誌記錄,性能監控,事務控制.

1、2 AOP底層實現: 代理機制(瞭解)

Spring 的 AOP 的底層用到兩種代理機制:

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

spring底層會完成自動代理,實現了接口的類默認使用的是JDK 的動態代理,相反的,沒有實現接口的類默認使用的是Cglib 的動態代理 ,底層代碼可以不懂但這個概念一定要知道,不然會被鄙視的,O(∩_∩)O哈哈~,下面是底層代碼,有興趣的可以瞭解瞭解。

JDK 動態代理增強一個類中方法:

public class MyJDKProxy implements InvocationHandler {
		private UserDao userDao;

		public MyJDKProxy(UserDao userDao) {
			this.userDao = userDao;
		}

		// 編寫工具方法:生成代理:
		public UserDao createProxy() {
			UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(userDao
					.getClass().getClassLoader(), userDao.getClass()
					.getInterfaces(), this);
			return userDaoProxy;
		}

		@Override
		public Object invoke(Object proxy, Method method, Object[] args)
				throws Throwable {
			if ("save".equals(method.getName())) {
				System.out.println("權限校驗================");
			}
			return method.invoke(userDao, args);
		}
	}

Cglib 動態代理增強一個類中的方法:

public class MyCglibProxy implements MethodInterceptor {
		private CustomerDao customerDao;

		public MyCglibProxy(CustomerDao customerDao) {
			this.customerDao = customerDao;
		}

		// 生成代理的方法:
		public CustomerDao createProxy() {
			// 創建 Cglib 的核心類:
			Enhancer enhancer = new Enhancer();
			// 設置父類:
			enhancer.setSuperclass(CustomerDao.class);
			// 設置回調:
			enhancer.setCallback(this);
			// 生成代理:
			CustomerDao customerDaoProxy = (CustomerDao) enhancer.create();
			return customerDaoProxy;
		}

		@Override
		public Object intercept(Object proxy, Method method, Object[] args,
				MethodProxy methodProxy) throws Throwable {
			if ("delete".equals(method.getName())) {
				Object obj = methodProxy.invokeSuper(proxy, args);
				System.out.println("日誌記錄================");
				return obj;
			}
			return methodProxy.invokeSuper(proxy, args);
		}
	}

2、 Spring 基於AspectJ 進行 AOP 的開發入門(XML 的方式):

首先,Spring爲什麼不直接進行Spring的AOP開發呢,而要基於Aspectj呢,是因爲,Spring自己的AOP開發實現方式(傳統的AOP開發)繁瑣且複雜,效率極低,於是傳統的AOP開發基本上棄用了,相反Aspectj的AOP開發效率高,所以AOP開發一般是Spring 的基於 AspectJ 的 AOP 開發。

2.1 AOP 的開發中的相關術語:

Aop是一種非常高深的思想,當然會有非常專業的相關術語了(這彎繞的,你打幾分?)

從專業的角度角度概述定義(相對來說比較枯燥不易理解):

Joinpoint(連接點):所謂連接點是指那些被攔截到的點。在 spring 中,這些點指的是方法,因爲 spring 只
支持方法類型的連接點.
Pointcut(切入點):所謂切入點是指我們要對哪些 Joinpoint 進行攔截的定義.
Advice(通知/增強):所謂通知是指攔截到 Joinpoint 之後所要做的事情就是通知.通知分爲前置通知,後置
通知,異常通知,最終通知,環繞通知(切面要完成的功能)
Introduction(引介):引介是一種特殊的通知在不修改類代碼的前提下, Introduction 可以在運行期爲類
動態地添加一些方法或 Field.
Target(目標對象):代理的目標對象
Weaving(織入):是指把增強應用到目標對象來創建新的代理對象的過程.
spring 採用動態代理織入,而 AspectJ 採用編譯期織入和類裝在期織入
Proxy(代理):一個類被 AOP 織入增強後,就產生一個結果代理類
Aspect(切面): 是切入點和通知(引介)的結合

基於專業的角度實例分析(相對來說易理解,什麼?畫質差?咳咳…1080p藍光畫質…哎哎哎…大哥…別打…別打…別打臉):
在這裏插入圖片描述

2.2引入相應的 jar 包

引入jar包:基礎六個jar包、AOP聯盟jar包、spring的AOPjar包、aspectJ的jar包、spring整合aspectj的jar包

  • spring 的傳統 AOP 的開發的包
    spring-aop-4.2.4.RELEASE.jar
    com.springsource.org.aopalliance-1.0.0.jar

  • aspectJ 的開發包:
    com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
    spring-aspects-4.2.4.RELEASE.jar
    在這裏插入圖片描述

2.3 引入 Spring 的配置文件

引入 AOP 約束:

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

2.4 編寫目標類

創建接口和類:

	public interface OrderDao {
		public void save();

		public void update();

		public void delete();

		public void find();
	}

	public class OrderDaoImpl implements OrderDao {
		@Override
		public void save() {
			System.out.println("保存訂單...");
		}

		@Override
		public void update() {
			System.out.println("修改訂單...");
		}

		@Override
		public void delete() {
			System.out.println("刪除訂單...");
		}

		@Override
		public void find() {
			System.out.println("查詢訂單...");
		}
	}

2.5 目標類的XML配置

<!-- 目標類配置:被增強的類 --> 
<bean id="orderDao" class="com.gx.spring.demo3.OrderDaoImpl"></bean>

2.6 整合 Junit 單元測試

前提:引入 spring-test.jar 測試的jar包,整合 Junit 單元測試之後就不需要每次都重複註冊工廠,只要固定格式在測試類上寫兩個註解,需要的屬性直接注入,之後只關心自己的測試類即可

//固定註解寫法(前提:引入 spring-test.jar 測試的jar包)
	@RunWith(SpringJUnit4ClassRunner.class)
	@ContextConfiguration("classpath:applicationContext.xml")
	public class SpringDemo3 {
		@Resource(name = "orderDao")  //需要的屬性直接注入(前提:引入 spring-test.jar 測試的jar包)
		private OrderDao orderDao;

		@Test
		public void demo1() {
			orderDao.save();
			orderDao.update();
			orderDao.delete();
			orderDao.find();
		}
	}

運行demo出現如下效果:
在這裏插入圖片描述

2.7 通知類型

到這裏,就需要需要對通知類型瞭解一下(前三者常用):

前置通知 :在目標方法執行之前執行.
在這裏插入圖片描述
後置通知 :在目標方法執行之後執行
在這裏插入圖片描述
如果要獲得後置通知中的返回值,必須注意的是:
在這裏插入圖片描述
環繞通知 :在目標方法執行前和執行後執行
在這裏插入圖片描述
異常拋出通知:在目標方法執行出現 異常的時候 執行
最終通知 :無論目標方法是否出現異常 最終通知都會 執行.

通知類型XML配置
在這裏插入圖片描述
在這裏插入圖片描述

2.8 切入點表達式

execution(表達式)

表達式 : [方法訪問修飾符] 方法返回值 包名.類名.方法名(方法的參數)

切入點表達式所以就是execution( [方法訪問修飾符] 方法返回值 包名.類名.方法名(方法的參數))

其中 [ ] 中的方法訪問修飾符可有可無

切入點表達式各類型例子:

public * com.gx.spring.dao. * .*(..)
com.gx.spring.dao.*.*(..)
com.gx.spring.dao.UserDao+.*(..)
com.gx.spring.dao..*.*(..)

2.9 編寫一個切面類

好了,瞭解了通知類型以及切入點表達式之後就可以來 編寫一個切面類玩起來了QAQ

public class MyAspectXml {
    // 前置增強
    public void before(){
       System.out.println("前置增強===========");
} }

2.10 配置完成增強

<!-- 配置切面類 --> 
<bean id="myAspectXml" class="com.gx.spring.demo3.MyAspectXml"></bean>
<!-- 進行 aop 的配置 --> 
<aop:config>
<!-- 配置切入點表達式:哪些類的哪些方法需要進行增強 -->
 <aop:pointcut expression="execution(* com.gx.spring.demo3.OrderDao.save(..))" id="pointcut1"/>
<!-- 配置切面 --> 
<aop:aspect ref="myAspectXml"> 
    <aop:before method="before" pointcut-ref="pointcut1"/>
</aop:aspect>
</aop:config>

需要注意的點我都規劃出來了(不用誇我,我知道我長得帥QnQ)
在這裏插入圖片描述

2.11 其他的增強的配置:

<!-- 配置切面類 -->
 <bean id="myAspectXml" class="com.gx.demo3.MyAspectXml"></bean>
	<!-- 進行 aop 的配置 -->
 <aop:config>
	<!-- 配置切入點表達式:哪些類的哪些方法需要進行增強 -->
	 <aop:pointcut expression="execution(* com.gx.spring.demo3.*Dao.save(..))" id="pointcut1"/>
	 <aop:pointcut expression="execution(* com.gx.spring.demo3.*Dao.delete(..))" id="pointcut2"/>
	 <aop:pointcut expression="execution(* com.gx.spring.demo3.*Dao.update(..))" id="pointcut3"/>
	 <aop:pointcut expression="execution(* com.gx.spring.demo3.*Dao.find(..))" id="pointcut4"/>
	<!-- 配置切面 --> 
	<aop:aspect ref="myAspectXml">
	   <aop:before method="before" pointcut-ref="pointcut1"/>
	   <aop:after-returning method="afterReturing"pointcut-ref="pointcut2"/>
	   <aop:around method="around" pointcut-ref="pointcut3"/>
	   <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4"/>
	   <aop:after method="after" pointcut-ref="pointcut4"/>
	</aop:aspect>
</aop:config>

3、Spring 基於AspectJ 進行 AOP 的開發入門(註解的方式):

3.1創建項目,引入jar包

引入的jar包如下:
在這裏插入圖片描述

3.2引入配置文件

在這裏插入圖片描述

3.3編寫目標類並配置

編寫目標類:

package com.gx.spring.demo1;

public class OrderDao {

	public void save(){
		System.out.println("保存訂單...");
	}
	public void update(){
		System.out.println("修改訂單...");
	}
	public String delete(){
		System.out.println("刪除訂單...");
		return "鄢寒";
	}
	public void find(){
		System.out.println("查詢訂單...");
	}
}

XML配置:

<!-- 配置目標類 -->
	<bean id="orderDao" class="com.gx.spring.demo1.OrderDao">

	</bean>

3.4編寫切面類並配置

編寫切面類

package com.gx.spring.demo1;

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;

/**
 * 切面類:註解的切面類
 * @author jt
 */
public class MyAspectAnno {

	public void before(){
		System.out.println("前置增強===========");
	}
}

XML配置:

<!-- 配置切面類 -->
	<bean id="myAspect" class="com.gx.spring.demo1.MyAspectAnno">
	
	</bean>

3.5使用註解的AOP對象目標類進行增強

1、在配置文件中打開註解的AOP開發

<!-- 在配置文件中開啓註解的AOP的開發 -->
	<aop:aspectj-autoproxy/>

2、在切面類上使用註解
在類上使用@Aspect註解代表這是一個切面類
在方法上注入屬性@Before(execution表達式)代表前置增強

@Aspect
public class MyAspectAnno {

	@Before(value="execution(* com.gx.spring.demo1.OrderDao.save(..))")
	public void before(){
		System.out.println("前置增強===========");
	}
}

3.6編寫測試類

package com.gx.spring.demo1;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * Spring的AOP的註解開發
 *
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo1 {
	@Resource(name="orderDao")
	private static OrderDao orderDao;
	
	public static void main(String[] args) {
		
			orderDao.save();
			orderDao.update();
			orderDao.delete();
			orderDao.find();
		
	}
	
}

測試結果:
在這裏插入圖片描述

4、Spring的註解的AOP的通知類型

4.1@Before :前置通知

@Aspect
public class MyAspectAnno {

	@Before(value="execution(* com.gx.spring.demo1.OrderDao.save(..))")
	public void before(){
		System.out.println("前置增強===========");
	}
}

4.2@AfterReturning :後置通知

後置通知可以獲取方法返回值

// 後置通知:
	@AfterReturning(value="execution(* com.gx.spring.demo1.OrderDao.save(..))")
	public void afterReturning(Object result){
		System.out.println("後置增強==========="+result);
	}

借用一下XML方式的圖,意思意思啦,意思還是那個意思QnQ
在這裏插入圖片描述

4.3@Around :環繞通知

// 環繞通知:
	@Around(value="execution(* com.gx.spring.demo1.OrderDao.save(..))")
	public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
		System.out.println("環繞前增強==========");
		Object obj  = joinPoint.proceed();
		System.out.println("環繞後增強==========");
		return obj;
	}

4.4@AfterThrowing :異常拋出通知

測試前記得製造出個異常qnq

// 異常拋出通知:
	@AfterThrowing(value="execution(* com.gx.spring.demo1.OrderDao.save(..))" throwing="e")
	public void afterThrowing(Throwable e){
		System.out.println("異常拋出增強========="+e.getMessage());
	}

4.5@After :最終通知

// 最終通知
	@After(value="execution(* com.gx.spring.demo1.OrderDao.save(..))")
	public void after(){
		System.out.println("最終增強============");
	}

5、Spring的註解的AOP的切入點的配置

首先,我們發現在Spring 基於AspectJ 進行 AOP 的開發入門(註解的方式)的過程中如果方法過多,通知過多並且作用於一個方法,需求一改變就需要更改相應的源代碼,爲了更好的維護,於是有了AOP的切入點的配置,AOP的切入點的配置能很好地決絕改問題!只需要管理AOP的切入點的配置即可!

具體代碼如下:

package com.gx.spring.demo1;

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;

/**
 * 切面類:註解的切面類
 * @author jt
 */
@Aspect
public class MyAspectAnno {
	// 前置通知:
	@Before(value="MyAspectAnno.pointcut2()")
	public void before(){
		System.out.println("前置增強===========");
	}
	
	// 後置通知:
	@AfterReturning(value="MyAspectAnno.pointcut4()",returning="result")
	public void afterReturning(Object result){
		System.out.println("後置增強==========="+result);
	}
	
	// 環繞通知:
	@Around(value="MyAspectAnno.pointcut3()")
	public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
		System.out.println("環繞前增強==========");
		Object obj  = joinPoint.proceed();
		System.out.println("環繞後增強==========");
		return obj;
	}
	
	// 異常拋出通知:
	@AfterThrowing(value="MyAspectAnno.pointcut1()",throwing="e")
	public void afterThrowing(Throwable e){
		System.out.println("異常拋出增強========="+e.getMessage());
	}
	
	// 最終通知
	@After(value="MyAspectAnno.pointcut1()")
	public void after(){
		System.out.println("最終增強============");
	}
	
	// 切入點註解:
	@Pointcut(value="execution(* com.gx.spring.demo1.OrderDao.find(..))")
	private void pointcut1(){}
	@Pointcut(value="execution(* com.gx.spring.demo1.OrderDao.save(..))")
	private void pointcut2(){}
	@Pointcut(value="execution(* com.gx.spring.demo1.OrderDao.update(..))")
	private void pointcut3(){}
	@Pointcut(value="execution(* com.gx.spring.demo1.OrderDao.delete(..))")
	private void pointcut4(){}
}

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