Spring AOP 入門

引言

AOP是軟件開發思想發展到必定階段的產物,AOP的出現並非爲了代替OOP,僅做爲OOP的有益補充,在下面的例子中這個概念將會獲得印證。AOP的應用場合是受限制的,通常適用於那些具備橫切邏輯的應用場合,例如性能監測,訪問控制,事務管理,日誌記錄。在日常的應用開發中AOP很難被使用到,可是AOP是Spring的亮點之一,有必要一看。java

一 AOP以及術語

AOP是Aspect Oriented Programing的簡稱,被譯爲面向切面編程。AOP但願將散落在業務邏輯函數中的相同代碼抽取到一個獨立的模塊中。舉個例子:web

class A{
    public void run()
    {
        doSomething();
        doAthings();
        doOtherthing();
    }
}
 
class B{
    public void run()
    {
         doSomething();
         doBthings();
         doOtherthing();
    }
}
 
class C{
    public void run()
    {
        doSomething();
        doCthings();
        doOtherthing();
        doMorethings();
    }
}

例如上述三個類中的run方法,都有doSomething和doOtherthing代碼,AOP就是要把這些代碼抽取出來。咱們知道,抽取代碼很容易,可是如何將這些獨立的邏輯代碼再融合到業務類中完成和原來同樣的業務操做,這纔是AOP要解決的問題。正則表達式

術語

Joinpoint鏈接點:程序執行的某個特定位置,例如類初始化前,類初始化後,方法執行前,方法執行後等,Spring只支持方法的鏈接點,即方法執行前,方法執行後,方法拋出異常時。
Pointcut切點:每一個類中都擁有多個鏈接點,如擁有兩個方法的類,這兩個方法都是鏈接點,切點能夠在鏈接點中定位一個或多個鏈接點。spring

Advice加強(通知):加強是織入目標類鏈接點上的一段程序代碼,即當程序到達一個執行點後會執行相對應的一段代碼,Spring提供的Advice都帶有接入點方位,例如BeforeAdvice,AftereturningAdvice,ThrowsAdvice等。只有結合切點和通知才能肯定特定的鏈接點並執行對應代碼。
Target目標對象:被嵌入代碼的類。
Introductiony引介:能夠爲類添加屬性和方法。
Weaving織入:AOP就像一臺織布機,將Advice代碼嵌入到對應的類中。
Proxy代理:一個類被AOP織入後,就會產生一個代理對象,他融合了原有類代碼和Advice代碼。
Aspect切面:由切點和加強組成,他包括了鏈接點定義和橫切邏輯代碼的定義,SpringAOP就是負責實施切面的框架。編程

AOP有三種織入方式:app

編譯期織入:這要求是用特殊的Java編譯器框架

類裝載期織入:要求使用特殊的類裝載器
動態代理織入:在運行時期爲目標對象生成代理對象的方式實現織入ide

Spring採用動態代理織入,AspectJ採用編譯期織入和類裝載織入。

二 Advice加強(通知)

Spring使用Advice類定義橫切邏輯,Advice類還包括了在方法的哪一點加入代碼的信息。

Advice類型:函數

前置加強:在方法執行前實施加強org.apringframework.aop.MethodBeforeAdvice
後置加強:在方法返回後實施加強org.springframework.aop.AfterReturningAdvice
異常拋出加強:在目標方法拋出異常後實施加強org.springframework.aop.ThrowsAdvice
環繞加強:在方法執行前和執行後實施加強org.aopaliance.intercept.MethodInterceptor
引介加強:在目標類中加入新的方法和屬性org.springframework.aop.IntroductionInterceptor性能

這是典型的基於代理的AOP,使用方法以下

1 建立一個接口,而且實現它

2 建立Advice,實現上述任意接口
3 使用ProxyFactory來生成代理

接下來,我以簡單的示例解釋前四種Advice

//Dog.java
//定義狗的接口
package test.aop;
/**
 * Created by gavin on 15-7-18.
 */
public interface Dog {
    public void shout(String name);
    public void sleep(String name);
}
 
//ChinaDog.java
//設計中華田園犬
package test.aop;
/**
 * Created by gavin on 15-7-18.
 */
public class ChinaDog implements Dog {
    @Override
    public void shout(String name) {
        System.out.println("中華田園犬"+name+" 叫了一聲");
    }
    @Override
    public void sleep(String name) {
        System.out.println("中華田園犬"+name+" 睡着了");
    }
    public void error() throws Exception {
        throw new Exception("shout exception");
    }
}
 
//BeforeAdvice.java
//前置加強類
package test.aop;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
/**
 * Created by gavin on 15-7-18.
 */
public class BeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        String name = (String)objects[0];    //參數獲取
        System.out.println("中華田園犬"+name+" 前置加強");
    }
}
 
//AfterReturnAdvice.java
//後置加強類
package test.aop;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
/**
 * Created by gavin on 15-7-18.
 */
public class AfterReturnAdvice implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        String name = (String)objects[0];
        System.out.println("中華田園犬"+name+" 後置加強");
    }
}
 
//SurroundAdvice.java
//環繞加強類
package test.aop;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
 * Created by gavin on 15-7-18.
 */
public class SurroundAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        Object[] objects = methodInvocation.getArguments();
        String name = (String)objects[0];
        System.out.println("環繞加強----前"+name);
 
        Object object = methodInvocation.proceed();    //此處執行的是原有函數
 
        System.out.println("環繞加強----後"+name);
 
        return object;
    }
}
 
//ThrowAdvice.java
//異常拋出加強類
package test.aop;
import org.springframework.aop.ThrowsAdvice;
import java.lang.reflect.Method;
/**
 * Created by gavin on 15-7-18.
 */
public class ThrowAdvice implements ThrowsAdvice {
    @Override
    public void afterThrowing(Method method,Object[] args, Object obj ,Exception ex)throws Throwable
    {
        System.out.println("------------------------");
        System.out.println("------異常拋出加強");
        System.out.println("------method "+method.getName());
        System.out.println("------exception "+ex.getMessage());
        System.out.println("------------------------");
    }
}
 
//AopTest.java
//AOP測試類
package test.aop;
import org.aopalliance.aop.Advice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
/**
 * Created by gavin on 15-7-18.
 */
public class AopTest {
    private Dog dog;
    private Advice advice;
    private ProxyFactory pf;
 
 
    @BeforeTest
    public void init()
    {
        dog = new ChinaDog();
        pf = new ProxyFactory();
        pf.setTarget(dog);
    }
 
    @Test
    public void beforeAdvice()
    {
        advice = new BeforeAdvice();
        pf.addAdvice(advice);
        Dog dog = (Dog)pf.getProxy();
        dog.shout("jim");
        dog.sleep("tom");
    }
 
    @Test
    public void afterReturndvice()
    {
        advice = new AfterReturnAdvice();
        pf.addAdvice(advice);
        Dog dog = (Dog)pf.getProxy();
        dog.shout("jim");
        dog.sleep("tom");
    }
 
    @Test
    public void arroundAdvice()
    {
        SurroundAdvice advice = new SurroundAdvice();
        pf.addAdvice(advice);
        Dog dog = (Dog)pf.getProxy();
        dog.shout("jim");
        dog.sleep("tom");
    }
 
    @Test
    public void throwAdvice()
    {
        advice = new ThrowAdvice();
        pf.addAdvice(advice);
        ChinaDog dog = (ChinaDog)pf.getProxy();
        try {
            dog.error();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

分別對前四種加強方式進行測試,獲得如下結果:
前置加強

173601_zndx_1983603.png

後置加強

173601_5djs_1983603.png

環繞加強

173601_Kaut_1983603.png

異常拋出加強

173601_SAsH_1983603.png

三 建立切面

Spring經過org.springframework.aop.Pointcut接口描述切點,經過這個接口的ClassFilter定位到特定的類,經過MethodMatcher定位到特定的方法。

切面的建立方法

1 添加目標對象
2 添加Advice
3 定義切點Pointcut
4 定義Advisor
5 設置代理對象

在第二節的基礎上,在applicationContext.xml中設置以下

<!--aop-->
<!-- 被代理對象 -->
<bean class="test.aop.ChinaDog" id="chinaDog"/>
 
<!-- 前置通知 -->
<bean class="test.aop.BeforeAdvice" id="beforeAdvice"/>
<!-- 後置通知 -->
<bean class="test.aop.AfterReturnAdvice" id="afterReturnAdvice"/>
<!-- 環繞通知 -->
<bean class="test.aop.SurroundAdvice" id="surroundAdvice"/>
<!-- 異常通知 -->
<bean class="test.aop.ThrowAdvice" id="throwAdvice"/>
 
<!-- 定義切點 在sleep方法上 經過靜態正則表達式方法匹配切點-->
<bean id="sleepPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
       <property name="pattern" value=".*sleep"/>
</bean>
<!-- 定義通知者 將前置Advice綁在sleep方法上-->
<bean id="sleepHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
       <property name="advice" ref="beforeAdvice"/>
       <property name="pointcut" ref="sleepPointcut"/>
</bean>
<!-- 代理對象 -->
<bean class="org.springframework.aop.framework.ProxyFactoryBean" id="proxyFactoryBean">
       <!-- 設置代理接口集合 -->
       <property name="proxyInterfaces">
              <list>
                     <!--接口要寫全-->
                     <value>test.aop.Dog</value>
              </list>
       </property>
       <!-- 把通知者(切面)織入代理對象 -->
       <property name="interceptorNames">
              <list>
                     <value>sleepHelperAdvisor</value>
              </list>
       </property>
       <!-- 配置被代理對象 -->
       <property name="target" ref="chinaDog"/>
</bean>

在AopTest.java中調用進行測試:

public static void main(String[] args)
{
    //PropertyConfigurator.configure("web/WEB-INF/log4j.properties");
    ApplicationContext ac = new FileSystemXmlApplicationContext("web/WEB-INF/applicationContext.xml");
    Dog dog = (Dog)ac.getBean("proxyFactoryBean");//注意這裏,調用的是代理bean,返回的是Dog的代理對象
    dog.shout("jim");
    dog.sleep("tom");
}

結果爲:

213813_DR0e_1983603.png

至於前置Advice爲何出現了三次我也不懂,若是有人瞭解的話,請賜教。

四 基於@AspectJ配置切面

咱們先用@AspectJ定義一個切面:

package test.aop;
 
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
 
/**
 * Created by gavin on 15-7-18.
 */
@Aspect
public class PreAspect {
    @Before("execution(* shout(..))")//意思是返回值任意 參數任意
    public void beforeShout()
    {
        System.out.println("準備叫");
    }
}

在applicationContext.xml中設置:

<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-3.1.xsd">
 
<!--基於@AspectJ切面的驅動器-->
<aop:aspectj-autoproxy/>
<bean class="test.aop.PreAspect" />
<bean class="test.aop.ChinaDog" id="chinaDog"/>

加入的內容有:
xmlns:aop="http://www.springframework.org/schema/aop

http://www.springframework.or... http://www.springframework.org/schema/aop/spring-aop-3.1.xsd

在AopTest.java中進行測試,代碼以下:

public static void main(String[] args)
{
    ApplicationContext ac = new FileSystemXmlApplicationContext("web/WEB-INF/applicationContext.xml");
    Dog dog = (Dog)ac.getBean("chinaDog");    //注意這裏調用的是chinaDog
    dog.shout("jim");
    dog.sleep("tom");
}

結果爲:

181612_Fxc0_1983603.png

只有shout方法前調用了beforeShout方法,與咱們設想的相同。

五 使用Spring來定義純粹的POJO切面

使用方法也很是簡單,使用spring的aop標籤。xml以下:

<!-- POJO -->
<bean class="test.aop.PreAspect" id="preAspect" />
<bean class="test.aop.ChinaDog" id="chinaDog"/>
<aop:config>
       <aop:aspect ref="preAspect">
              <aop:before method="beforeShout" pointcut="execution(* *.shout(..))"/>
       </aop:aspect>
</aop:config>

java代碼以下

public static void main(String[] args)
{
    ApplicationContext ac = new FileSystemXmlApplicationContext("web/WEB-INF/applicationContext.xml");
    Dog dog = (Dog)ac.getBean("chinaDog");//注意這裏調用的是chinaDog
    dog.shout("jim");
    dog.sleep("tom");
}

最終效果和@AspectJ相同

總結

AOP是OOP的有益補充,他爲程序開發提供了一個新的思考角度,能夠將重複性的橫切邏輯抽取到統一的模塊中。只有經過OOP的縱向抽象和AOP的橫向抽取,程序才能夠真正的解決重複性代碼問題。

Spring採用JDK動態代理和CGLib動態代理技術在運行期間織入Advice。因此用戶不用裝備特殊的編譯器或類裝載器。要使用JDK動態代理,目標對象必須實現接口,而CGLib不對目標類採起限制。

JDK建立代理對象效率比CGLib高,而CGLib建立的代理對象運行效率比JDK的要高。

Spring實現AOP的三種方式:
經典代理@AspectJPOJO

更多文章:http://blog.gavinzh.com

相關文章
相關標籤/搜索