最近在學習AOP,之前一直很不明白,什么是AOP?為什么要使用AOP,它有什么作用?學完之后有一點小小的感觸和自己的理解,所以在這里呢就跟大家一起分享一下
AOP(Aspect-Oriented Programming)其實是OOP(Object-Oriented Programing) 思想的補充和完善。我們知道,OOP引進"抽象"、"封裝"、"繼承"、"多態"等概念,對萬事萬物進行抽象和封裝,來建立一種對象的層次結構,它強調了
一種完整事物的自上而下的關系。但是具體細粒度到每個事物內部的情況,OOP就顯得無能為力了。比如日志功能。日志代碼往往水平地散布在所有對象層次當
中,卻與它所散布到的對象的核心功能毫無關系。對於其他很多類似功能,如事務管理、權限控制等也是如此。這導致了大量代碼的重復,而不利於各個模塊的重
用。 而AOP技
術則恰恰相反,它利用一種稱為"橫切"的技術,能夠剖解開封裝的對象內部,並將那些影響了多個類並且與具體業務無關的公共行為 封裝成一個獨立的模塊(稱
為切面)。更重要的是,它又能以巧奪天功的妙手將這些剖開的切面復原,不留痕跡的融入核心業務邏輯中。這樣,對於日后橫切功能的編輯和重用都能夠帶來極大
的方便。 AOP技術的具體實現,無非也就是通過動態代理技術或者是在程序編譯期間進行靜態的"織入"方式。下面是這方面技術的幾個基本術語:
1、join point(連接點):是程序執行中的一個精確執行點,例如類中的一個方法。它是一個抽象的概念,在實現AOP時,並不需要去定義一個join point。
2、point cut(切入點):本質上是一個捕獲連接點的結構。在AOP中,可以定義一個point cut,來捕獲相關方法的調用。
3、advice(通知):是point cut的執行代碼,是執行“方面”的具體邏輯。
4、aspect(切面):point cut和advice結合起來就是aspect,它類似於OOP中定義的一個類,但它代表的更多是對象間橫向的關系。
說了這么多,可能我們還是對AOP有點不知所措,不知道是干什么的,那么我們就以一個例子作為講解,來理解這個抽象的概念
我們有一個簡易的計算器,進行加減乘除的操作,有一個需求,1.需要在進行算法之前和之后進行輸出一句話
那么對於以上操作我們可能最容易想到的就是用一個實現類實現這個接口。然后在接口調用方法前后輸出一句話
這樣確實能實現這個需求,但是可能有的同學就想到了,是不是重復代碼了呢?如果我有上千個方法呢?是不是還得在每個方法里增加幾行代碼?我們能不能在不改變原來方法的結構上
也能實現相同的需求呢?這個時候AOP就能幫我們實現了。下面我們詳細的講解下如何使用注解的方式來實現AOP
還是同樣的接口和實現類,只是這時候實現類中沒有了輸出語句,如圖
上圖就是最原始的方法了,也就是說我們在這個方法里面只需要關注我們方法執行的內容,並不需要關注一些方法之外的東西,比如說記錄日志,方法前輸出語句等等。。
那么,既然這個方法什么都不關注的話,那我們的輸出語句又在哪兒寫呢?這個時候我們就定義一個專門的類,用它來作為切面,代碼如下所示
package advice; import java.util.Arrays; import java.util.List; import org.aspectj.lang.JoinPoint; 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; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @Aspect //聲明注解 public class CalculationAnnotation { /** * 定義前置通知 * execution(* biz.UserBiz.*(..)) 表示 所有修飾符的所有返回值類型 biz.UserBiz 包下的所有方法 * 在方法執行之前執行 * */ @Before("execution(* biz.CalculationImpl.*(..))") public void before(JoinPoint join){ //獲取方法名 String mathName=join.getSignature().getName(); //獲取參數列表 List<Object> args = Arrays.asList(join.getArgs()); System.out.println("前置通知---->before 方法名是:"+mathName+"\t參數列表是:"+args); } /** * 后置通知 * 在方法返回后執行,無論是否發生異常 * 不能訪問到返回值 * * */ @After("execution(* biz.CalculationImpl.*(..))") public void after(){ System.out.println("后置通知---->after...."); } }
@Aspect ----->表示聲明這個類是一個切面,
這樣呢,咱們這個切面就聲明完畢了,那么,我們可以想到,這個時候我們只是聲明了一個切面而已,並沒有在那個地方用到這個切面對不對?也就是說我們配置的切面還跟我們程序還沒有任何的關聯關系
這樣的話呢,就引出了我們的配置文件了也就是我們Spring的配置文件applicationContext.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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd "> <!-- 配置Bean --> <bean id ="CalculationImpl" class="biz.CalculationImpl"></bean> <!-- 將切面類交與Spring容器管理 --> <bean class="advice.CalculationAnnotation"></bean> <!-- 使用注解自動生成代理對象 --> <aop:aspectj-autoproxy/> </beans>
這個時候我們可以看看配置文件里到底寫了什么,寫這些是干啥的,有什么用。
這個大家肯定都懂是吧,這沒話說,也就是將CalculationImpl類交給Spring容器來管理,如果有不懂的童鞋可以看看我的另一篇關於Spring IOC 的文章
那么這行代碼呢?這行代碼的意思就是將我們的CalculationAnnotation類交給Spring容器管理,因為我們在CalculationAnnotation類中不是聲明了一個@Aspect切面注解嗎對不對
當Spring容器初始化的時候它會找有沒有這個節點,如果有的話呢,容器就會根據你的Bean配置,找看那個類中配置了@Aspect切面注解
如果找到了的話那么就根據你的注解來執行相應的代碼,什么意思呢?比如說如圖所示
好,那么我們就來看看執行之后結果會是怎樣的呢?
這樣我們是不是就完成了之前的需求呢?在執行代碼前輸出一行語句,如果我們想要做到日志的記錄的話,是不是只需要把輸出語句修改為記錄日志的代碼就可以了呢。而且我還沒有影響任何的功能性代碼
也就是對源代碼並沒有做任何的修改,那么既然有前置增強的話肯定也有后置增強和其他增強操作下面我就講講后置增強,
其實對於其他的增強類型的話呢,既然知道前置增強是怎么一回事了,那么其他四種就輕而易舉了
后置增強,其實我們只需要在切面類也就是我們寫前置增強的類中直接添加后置 增強代碼即可,如圖
只是將注解標簽給進行了一道修改,其他的任何操作我們都不需要在進行修改,示例結果如圖所示
這樣是不是就完成了在方法前后執行與方法無關的代碼呢?可能有些童鞋有疑問,為什么輸出語句是在最后輸出的,不應該是夾在中間嗎?但是我們看測試代碼,我是執行了add方法,接收了一個返回值,然后在方法的外面輸出的我接收的返回值變量,那么這樣的話,可不就是我們看到的結果嘛。
由於時間的關系呢,我今天就先給大家分享下前置增強和后置增強。至於返回,異常和環繞的話呢,我就下次在跟大家分享吧!希望大家能夠學到點東西吧!