Spring中的AOP以及切入點表達式和各種通知

上篇講了動態代理:Java中動態代理的兩種方式JDK動態代理和cglib動態代理以及區別

我們用上篇的做法去實現目標方法的增強,實現代碼的解耦,是沒有問題的,但是還是需要自己去生成代理對象,自己手寫攔截器,在攔截器裏自己手動的去把要增強的內容和目標方法結合起來,這用起來還是有點繁瑣,有更好的解決方案嗎?

答案是:有的!那就是Spring的AOP,這纔是咱們最終想引出來的重點!

有了Spring的AOP後,就不用自己去寫了,只需要在配置文件裏進行配置,告訴Spring你的哪些類需要生成代理類、你的哪個類是增強類、是在目標方法執行之前增強還是目標方法執行後增強。配置好這些後Spring按照你的配置去幫你生成代理對象,按照你的配置把增強的內容和目標方法結合起來。就相當於自己寫代碼也能實現和aop類似的功能,但是有了Spring aop以後有些事情Spring幫你做了,而且人家Spring做成了可配置化,用起來非常簡單而且很靈活

咱們上邊用的JDK動態代理和cglib動態代理,這兩種在Spring的AOP裏都有用到,Spring是根據不同的情況去決定是使用JDK的動態代理生成代理對象,還是使用cglib去生成代理對象,具體的內容本篇會講一下。

聊Spring AOP之前,需要對涉及到一些名詞有所瞭解

1、Spring AOP裏的名詞概念

翻閱Spring AOP相關的文檔,發現裏邊有好多概念性的東西,有很多名詞,有很多概念都寫的很玄乎,讀好幾遍都讀不懂,我個人認爲下邊的幾個名詞比較關鍵,是必須知道和掌握的

切面類:就是要執行的增強的方法所在的類,比如咱們例子裏的MyTransaction類

通知:切面類裏的方法,比如咱們例子的beginTransaction( )方法和commit( )

目標方法:要執行的目標方法,比如咱們例子裏的savePerson( )方法

上述解釋是自己的理解

瞭解了這些以後,下面先代碼實現一下,感受一下Spring AOP的魅力

2、代碼使用Spring AOP

package com.cj.study.spring.aop;

public interface PersonService {
	
	public String savePerson();
	
	public void updatePerson();
	
	public void deletePerson();
	
}
package com.cj.study.spring.aop;

public class PersonServiceImpl implements PersonService{

	@Override
	public String savePerson() {
		System.out.println("添加");
		return "保存成功!";
	}

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

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

}
package com.cj.study.spring.aop;

//切面類
public class MyTransaction {
	//切面裏的通知方法
	public void beginTransaction(){
		System.out.println("開啓事務 ");
	}
	//切面裏的通知方法
	public void commit(){
		System.out.println("提交事務");
	}
}
package com.cj.study.spring.aop;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AopTest {
	@Test
	public void test(){
		ApplicationContext context = new ClassPathXmlApplicationContext("com/cj/study/spring/aop/applicationContext.xml");
		PersonService proxyPersonService = (PersonService) context.getBean("personService");
		String returnValue = proxyPersonService.savePerson();
		System.out.println(returnValue);
	}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:aop="http://www.springframework.org/schema/aop" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

	<!-- 導入目標類 -->   
	<bean id="personService" class="com.cj.study.spring.aop.PersonServiceImpl"></bean>
	<!-- 導入切面 -->
	<bean id="myTransaction" class="com.cj.study.spring.aop.MyTransaction"></bean>
	
	<aop:config>
		<aop:pointcut expression="execution(* com.cj.study.spring.aop.*.*(..))" id="perform"/>
		<aop:aspect ref="myTransaction">
			<aop:before method="beginTransaction" pointcut-ref="perform"/>
		</aop:aspect>
		<aop:aspect ref="myTransaction">
			<aop:after method="commit" pointcut-ref="perform"/>
		</aop:aspect>
	</aop:config>
	
</beans>

可以斷點看一下

發現返回的對象是$Proxy4,說明返回的是代理類的對象

最後的運行結果

效果和咱們自己去寫的一樣,但是Spring AOP它做成了配置化的,用起來非常的爽。

講完例子後,下面講一下具體的細節

3、AOP配置文件內容講解

這是咱們上述例子裏的配置文件

(1)、導入目標類和切面類,就是把目標類和切面類放入Spring容器裏,讓Spring幫你生成

(2)、aop:pointcut 是指切入點

(3)、expression 是指切入點表達式

(4)、aop:aspect 是指上邊講的切面類

(5)、aop:before、aop:after 是指上邊講的通知,通知有很多種,前置通知、後置通知、環繞通知、最終通知、異常通知,等下會詳細講

3.1、切入點表達式execution

切入點和切入點表達式是用來告訴Spring你的哪些類需要Spring給你生成代理對象,這個很重要

要徹底瞭解這個表達式的意思,首先需要知道Java裏一個方法最完整的描述長什麼樣子

截圖上的execution是AOP文檔裏給出的表達式示意,下邊這一行是java中一個方法最完整的描述(以Object裏的wait方法爲例)

對應的含義我進行了標註,而且和圖上的execution表達式做了一一對應。

下面舉幾個表達式的例子,進一步幫大家理解(*代表通配符)

execution(public * *(..))  所有的公共方法

execution(* set*(..))  以set開頭的任意方法

execution(* com.ci.service.AccountService.*(..)) com.cj.service.AccountService類中的所有的方法

execution(* com.cj.service.*.*(..))  com.cj.service包中的所有的類的所有的方法

execution(* com.cj.service..*.*(..)) com.cj.service包及子包中所有的類的所有的方法

execution(* com.cj.spring.aop..*.*(String,?,Integer))  com.cj.spring.aop包及子包中所有的類的有三個參數
                                                            且第一個參數爲String,第二個參數爲任意類型,
                                                            第三個參數爲Integer類型的方法

理解了這個以後,咱們就可以根據自己想配置的路徑進行配置了

3.2、AOP中的各種通知

通知:
   1、前置通知
      1、在目標方法執行之前執行
      2、無論目標方法是否拋出異常,都執行,因爲在執行前置通知的時候,目標方法還沒有執行,還沒有遇到異常
   2、後置通知
      1、在目標方法執行之後執行
      2、當目標方法遇到異常,後置通知將不再執行
      3、後置通知可以接受目標方法的返回值,但是必須注意:
               後置通知的參數的名稱和配置文件中returning="var"的值是一致的
   3、最終通知:
      1、在目標方法執行之後執行
      2、無論目標方法是否拋出異常,都執行,因爲相當於finally
   4、異常通知
      1、接受目標方法拋出的異常信息
      2、步驟
           在異常通知方法中有一個參數Throwable  ex
           在配置文件中
              <aop:after-throwing method="throwingMethod" pointcut-ref="perform" throwing="ex"/>
   5、環繞通知
       1、如果不在環繞通知中調用ProceedingJoinPoint的proceed,目標方法不會執行
       2、環繞通知可以控制目標方法的執行

3.3、Spring AOP的具體加載步驟

springAOP的具體加載步驟:
   1、當spring容器啓動的時候,加載了spring的配置文件
   2、爲配置文件中所有的bean創建對象
   3、spring容器在創建對象的時候它會解析aop:config的配置 
       	    解析切入點表達式,用切入點表達式和納入spring容器中的bean做匹配
            如果匹配成功,則會爲該bean創建代理對象,代理對象的方法=目標方法+通知
            如果匹配不成功,則爲該bean創建正常的對象
	其實就是你通過表達式告訴Spring哪些bean需要它幫你生成代理對象而不是生成原有的正常對象
      理解這一點相當重要!
   4、在客戶端利用context.getBean獲取對象時,如果該對象有代理對象則返回代理對象,如果沒有代理對象,則返回目標對象

說明:如果目標類沒有實現接口,spring容器會採用cglib的方式產生代理對象,如果實現了接口,則會採用jdk的動態代理產生代理對象

以上就是本人對Spring AOP的一些理解和總結

別人一問,spirng aop的原理,都知道是動態代理,但是具體的不太清楚

通過前兩篇文章:

Java中的代理模式——靜態代理以及分析靜態代理的缺點

Java中動態代理的兩種方式JDK動態代理和cglib動態代理以及區別

再加上本篇,這三篇下來,基本上可以清楚Spring AOP來龍去脈了,希望這幾篇文章對大家理解AOP有所幫助

本篇講的是配置文件形式的AOP,除了用配置文件的形式來實現,用註解也可以實現

下一篇會講一下註解形式的AOP:SpringAOP的註解形式

瞭解了Spring AOP後,再去了解Spring的聲明式事務就比較簡單了,Spring的聲明式事務只是對AOP的一種應用

所以,講完註解形式的AOP後,接着會抽時間繼續講一下Spring的聲明式事務

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