1. 程式人生 > >spring-AOP原理與應用

spring-AOP原理與應用

圖片 spa 應用 framework round 子類 cee 過程 訪問

技術分享圖片

技術分享圖片


什麽是AOP

技術分享圖片

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

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

AOP作用

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

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

SpringAOP的由來:

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

底層實現:

代理機制:

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

* JDK的動態代理 :針對實現了接口的類產生代理.

* Cglib的動態代理 :針對沒有實現接口的類

產生代理. 應用的是底層的字節碼增強的技術 生成當前類的子類對象.

動態代理

1 運行時實現指定的接口

想實現某個接口,你需要寫一個類,然後在類名字的後面給出“implementsXXX接口。這才是實現某個接口:

public interface MyInterface {
    void fun1();
    void fun2();
}
public class MyInterfaceImpl implements MyInterface {
    public void fun1() {
        System.out.println("fun1()");
    }
     
    public void fun2() {
        System.out.println("fun2()");
    }
}

上面的代碼對我們來說沒有什麽新鮮感,我們要說的是動態代理技術可以通過一個方法調用就可以生成一個對指定接口的實現類對象。

Class[] cs = {MyInterface.class};

MyInterface mi = (MyInterface)Proxy.newProxyInstance(loader, cs, h);

上面代碼中,Proxy類的靜態方法newProxyInstance()方法生成了一個對象,這個對象實現了cs數組中指定的接口。沒錯,返回值miMyInterface接口的實現類。你不要問這個類是哪個類,你只需要知道miMyInterface接口的實現類就可以了。你現在也不用去管loader

h這兩個參數是什麽東東,你只需要知道,Proxy類的靜態方法newProxyInstance()方法返回的方法是實現了指定接口的實現類對象,甚至你都沒有看見實現類的代碼。

動態代理就是在運行時生成一個類,這個類會實現你指定的一組接口,而這個類沒有.java文件,是在運行時生成的,你也不用去關心它是什麽類型的,你只需要知道它實現了哪些接口即可。

2 newProxyInstance()方法的參數

Proxy類的newInstance()方法有三個參數:

l ClassLoader loader:它是類加載器類型,你不用去理睬它,你只需要知道怎麽可以獲得它就可以了:MyInterface.class.getClassLoader()就可以獲取到ClassLoader對象,沒錯,只要你有一個Class對象就可以獲取到ClassLoader對象;

l Class[] interfaces:指定newProxyInstance()方法返回的對象要實現哪些接口,沒錯,可以指定多個接口,例如上面例子只我們只指定了一個接口:Class[] cs = {MyInterface.class};

l InvocationHandler h:它是最重要的一個參數!它是一個接口!它的名字叫調用處理器!你想一想,上面例子中mi對象是MyInterface接口的實現類對象,那麽它一定是可以調用fun1()fun2()方法了,難道你不想調用一下fun1()fun2()方法麽,它會執行些什麽東東呢?其實無論你調用代理對象的什麽方法,它都是在調用InvocationHandlerinvoke()方法!

public static void main(String[] args) {
    Class[] cs = {MyInterface.class};
    ClassLoader loader = MyInterface.class.getClassLoader();
    InvocationHandler h = new InvocationHandler() {
        public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable {
            System.out.println("無論你調用代理對象的什麽方法,其實都是在調用invoke()...");
            return null;
        }
    };
    MyInterface mi = (MyInterface)Proxy.newProxyInstance(loader, cs, h);
    mi.fun1();
    mi.fun2();
}

  InvocationHandler接口只有一個方法,即invoke()方法!它是對代理對象所有方法的唯一實現。也就是說,無論你調用代理對象上的哪個方法,其實都是在調用InvocationHandlerinvoke()方法。

想象中的類:

class X implements MyInterface {
    private InvocationHandler h;
    public X(InvocationHandler h) {
        this.h = h;
    }
    
    public void fun1() {
        h.invoke();
    }
    public void fun2() {
        h.invoke();
    }
}

註意,X類是我們用來理解代理對象與InvocationHandler之間的關系的,但它是不存在的類。是我們想象出來的!也就是說,它是用來說明,無論你調用代理對象的哪個方法,最終調用的都是調用處理器的invoke()方法。

3 InvocationHandlerinvoke()方法

技術分享圖片


InvocationHandler的invoke()方法的參數有三個:

l Object proxy:代理對象,也就是Proxy.newProxyInstance()方法返回的對象,通常我們用不上它;

l Method method:表示當前被調用方法的反射對象,例如mi.fun1(),那麽method就是fun1()方法的反射對象;

l Object[] args:表示當前被調用方法的參數,當然mi.fun1()這個調用是沒有參數的,所以args是一個零長數組。

  最後要說的是invoke()方法的返回值為Object類型,它表示當前被調用的方法的返回值,當然mi.fun1()方法是沒有返回值的,所以invoke()返回的就必須是null了。

public static void main(String[] args) {
    Class[] cs = {MyInterface.class};
    ClassLoader loader = MyInterface.class.getClassLoader();
    InvocationHandler h = new InvocationHandler() {
    public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable {
        System.out.println("當前調用的方法是:" + method.getName());
        return null;
    }
    };
    MyInterface mi = (MyInterface)Proxy.newProxyInstance(loader, cs, h);
    mi.fun1();
    mi.fun2();
}

AOP的開發中的相關術語:

Joinpoint(連接點):所謂連接點是指那些被攔截到的點。在spring,這些點指的是方法,因為spring只支持方法類型的連接點.

Pointcut(切入點):所謂切入點是指我們要對哪些Joinpoint進行攔截的定義.

Advice(通知/增強):所謂通知是指攔截到Joinpoint之後所要做的事情就是通知.通知分為前置通知,後置通知,異常通知,最終通知,環繞通知(切面要完成的功能)

Introduction(引介):引介是一種特殊的通知在不修改類代碼的前提下, Introduction可以在運行期為類動態地添加一些方法或Field.

Target(目標對象):代理的目標對象

Weaving(織入):是指把增強應用到目標對象來創建新的代理對象的過程.

spring采用動態代理織入,而AspectJ采用編譯期織入和類裝在期織入

Proxy(代理):一個類被AOP織入增強後,就產生一個結果代理類

Aspect(切面): 是切入點和通知(引介)的結合

Spring使用AspectJ進行AOP的開發:XML的方式(*****

引入相應的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

技術分享圖片


引入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>


通知類型

前置通知 :在目標方法執行之前執行

後置通知 :在目標方法執行之後執行

環繞通知 :在目標方法執行前和執行後執行

異常拋出通知:在目標方法執行出現 異常的時候執行

最終通知 :無論目標方法是否出現異常最終通知都會執行

切入點表達式

execution(表達式)

表達式:

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

public * cn.spring.dao.*.*(..)

* cn.spring.dao.*.*(..)

* cn.spring.dao.UserDao+.*(..)

* cn.spring.dao..*.*(..)

案例

技術分享圖片


其他的增強的配置:

<!-- 配置切面類 -->
<bean id="myAspectXml" class="cn.itcast.spring.demo3.MyAspectXml"></bean>

<!-- 進行aop的配置 -->
<aop:config>
    <!-- 配置切入點表達式:哪些類的哪些方法需要進行增強 -->
    <aop:pointcut expression="execution(* cn.spring.demo3.*Dao.save(..))" id="pointcut1"/>
    <aop:pointcut expression="execution(* cn.spring.demo3.*Dao.delete(..))" id="pointcut2"/>
    <aop:pointcut expression="execution(* cn.spring.demo3.*Dao.update(..))" id="pointcut3"/>
    <aop:pointcut expression="execution(* cn.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>


Spring使用AspectJ進行AOP的開發:註解的方式

引入相關的jar:

引入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>

編寫目標類:

public class ProductDao {
public void save(){
System.out.println("保存商品...");
}
public void update(){
System.out.println("修改商品...");
}
public void delete(){
System.out.println("刪除商品...");
}
public void find(){
System.out.println("查詢商品...");
}
}

配置目標類:

<!-- 目標類============ -->
     <bean id="productDao" class="cn.spring.demo4.ProductDao"></bean>

開啟aop註解的自動代理:

<aop:aspectj-autoproxy/>

AspectJAOP的註解:

@Aspect:定義切面類的註解

通知類型:

* @Before :前置通知

* @AfterReturing :後置通知

* @Around :環繞通知

* @After :最終通知

* @AfterThrowing :異常拋出通知.

@Pointcut:定義切入點的註解

編寫切面類:

@Aspect
public class MyAspectAnno {
 
    @Before("MyAspectAnno.pointcut1()")
    public void before(){
    System.out.println("前置通知===========");
    }
    
    @Pointcut("execution(* cn.spring.demo4.ProductDao.save(..))")
    private void pointcut1(){}
}

配置切面:

<!-- 配置切面類 -->
     <bean id="myAspectAnno" class="cn.spring.demo4.MyAspectAnno"></bean>

其他通知的註解:

@Aspect
public class MyAspectAnno {
 
    @Before("MyAspectAnno.pointcut1()")
    public void before(){
        System.out.println("前置通知===========");
    }
    
    @AfterReturning("MyAspectAnno.pointcut2()")
    public void afterReturning(){
        System.out.println("後置通知===========");
    }
    
    @Around("MyAspectAnno.pointcut3()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
        System.out.println("環繞前通知==========");
        Object obj = joinPoint.proceed();
        System.out.println("環繞後通知==========");
        return obj;
    }
    
    @AfterThrowing("MyAspectAnno.pointcut4()")
    public void afterThrowing(){
        System.out.println("異常拋出通知========");
    }
    
    @After("MyAspectAnno.pointcut4()")
    public void after(){
        System.out.println("最終通知==========");
    }
    
    @Pointcut("execution(* cn.itcast.spring.demo4.ProductDao.save(..))")
    private void pointcut1(){}
    @Pointcut("execution(* cn.itcast.spring.demo4.ProductDao.update(..))")
    private void pointcut2(){}
    @Pointcut("execution(* cn.itcast.spring.demo4.ProductDao.delete(..))")
    private void pointcut3(){}
    @Pointcut("execution(* cn.itcast.spring.demo4.ProductDao.find(..))")
    private void pointcut4(){}
}



spring-AOP原理與應用