中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

Spring AOP是什么?你都拿它做什么?

2019-01-17    來源:importnew

容器云強勢上線!快速搭建集群,上萬Linux鏡像隨意使用

為什么會有面向切面編程(AOP)?我們知道Java是一個面向?qū)ο螅∣OP)的語言,但它有一些弊端,比如當(dāng)我們需要為多個不具有繼承關(guān)系的對象引入一個公共行為,例如日志、權(quán)限驗證、事務(wù)等功能時,只能在在每個對象里引用公共行為。這樣做不便于維護,而且有大量重復(fù)代碼。AOP的出現(xiàn)彌補了OOP的這點不足。

為了闡述清楚Spring AOP,我們從將以下方面進行討論:

  1. 代理模式
  2. 靜態(tài)代理原理及實踐
  3. 動態(tài)代理原理及實踐
  4. Spring AOP原理及實戰(zhàn)

1. 代理模式

代理模式:為其他對象提供一種代理以控制對這個對象的訪問。這段話比較官方,但我更傾向于用自己的語言理解:比如A對象要做一件事情,在沒有代理前,自己來做;在對 A 代理后,由 A 的代理類 B 來做。代理其實是在原實例前后加了一層處理,這也是 AOP 的初級輪廓。

2. 靜態(tài)代理原理及實踐

靜態(tài)代理模式:靜態(tài)代理說白了,就是在程序運行前就已經(jīng)存在代理類的字節(jié)碼文件、代理類和原始類的關(guān)系在運行前就已經(jīng)確定。廢話不多說,我們看一下代碼。為了方便閱讀,博主把單獨的 class 文件合并到接口中,讀者可以直接復(fù)制代碼運行:

package test.staticProxy;

// 接口
public interface IUserDao {
    void save();
    void find();
}

//目標(biāo)對象
class UserDao implements IUserDao{
    @Override
    public void save() {
        System.out.println("模擬:保存用戶!");
    }
    @Override
    public void find() {
        System.out.println("模擬:查詢用戶");
    }
}

/**
  * 靜態(tài)代理
  * 特點:
  * 2. 目標(biāo)對象必須要實現(xiàn)接口
  * 2. 代理對象,要實現(xiàn)與目標(biāo)對象一樣的接口
 */
class UserDaoProxy implements IUserDao{

    // 代理對象,需要維護一個目標(biāo)對象
    private IUserDao target = new UserDao();

    @Override
    public void save() {
        System.out.println("代理操作: 開啟事務(wù)...");
        target.save();   // 執(zhí)行目標(biāo)對象的方法
        System.out.println("代理操作:提交事務(wù)...");
    }

    @Override
    public void find() {
        target.find();
    }
}

測試結(jié)果:

靜態(tài)代理雖然保證了業(yè)務(wù)類只需關(guān)注邏輯本身,代理對象的一個接口只服務(wù)于一種類型的對象。如果要代理的方法很多,勢必要為每一種方法都進行代理。再者,如果增加一個方法,除了實現(xiàn)類需要實現(xiàn)這個方法外,所有的代理類也要實現(xiàn)此方法。增加了代碼的維護成本。那么要如何解決呢?答案是使用動態(tài)代理。

3. 動態(tài)代理原理及實踐

動態(tài)代理模式:動態(tài)代理類的源碼是在程序運行期間,通過 JVM 反射等機制動態(tài)生成。代理類和委托類的關(guān)系是運行時才確定的。實例如下:

package test.dynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 接口
public interface IUserDao {
    void save();
    void find();
}

//目標(biāo)對象
class UserDao implements IUserDao{

    @Override
    public void save() {
        System.out.println("模擬: 保存用戶!");
    }

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

/**
 * 動態(tài)代理:
 * 代理工廠,給多個目標(biāo)對象生成代理對象!
 *
 */
class ProxyFactory {

    // 接收一個目標(biāo)對象
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    // 返回對目標(biāo)對象(target)代理后的對象(proxy)
    public Object getProxyInstance() {
        Object proxy = Proxy.newProxyInstance(
            target.getClass().getClassLoader(),  // 目標(biāo)對象使用的類加載器
            target.getClass().getInterfaces(),   // 目標(biāo)對象實現(xiàn)的所有接口
            new InvocationHandler() {            // 執(zhí)行代理對象方法時候觸發(fā)

                @Override
                public Object invoke(Object proxy, Method method, Object[] args)
                        throws Throwable {

                    // 獲取當(dāng)前執(zhí)行的方法的方法名
                    String methodName = method.getName();
                    // 方法返回值
                    Object result = null;
                    if ("find".equals(methodName)) {
                        // 直接調(diào)用目標(biāo)對象方法
                        result = method.invoke(target, args);
                    } else {
                        System.out.println("開啟事務(wù)...");
                        // 執(zhí)行目標(biāo)對象方法
                        result = method.invoke(target, args);
                        System.out.println("提交事務(wù)...");
                    }
                    return result;
                }
            }
        );
        return proxy;
    }
}

測試結(jié)果如下:

IUserDao proxy = (IUserDao)new ProxyFactory(target).getProxyInstance();

其實是 JDK 動態(tài)生成了一個類去實現(xiàn)接口,隱藏了這個過程:

class $jdkProxy implements IUserDao{}

使用 JDK 生成的動態(tài)代理的前提是目標(biāo)類必須有實現(xiàn)的接口。但這里又引入一個問題,如果某個類沒有實現(xiàn)接口,就不能使用 JDK 動態(tài)代理。所以 CGLIB 代理就是解決這個問題的。

CGLIB?是以動態(tài)生成的子類繼承目標(biāo)的方式實現(xiàn),在運行期動態(tài)的在內(nèi)存中構(gòu)建一個子類,如下:

public class UserDao{}

// CGLIB 是以動態(tài)生成的子類繼承目標(biāo)的方式實現(xiàn),程序執(zhí)行時,隱藏了下面的過程
public class $Cglib_Proxy_class  extends UserDao{}

CGLIB 使用的前提是目標(biāo)類不能為 final 修飾。因為 final 修飾的類不能被繼承。

現(xiàn)在,我們可以看看 AOP 的定義:面向切面編程,核心原理是使用動態(tài)代理模式在方法執(zhí)行前后或出現(xiàn)異常時加入相關(guān)邏輯。

通過定義和前面代碼我們可以發(fā)現(xiàn)3點:

  • AOP 是基于動態(tài)代理模式。
  • AOP 是方法級別的。
  • AOP 可以分離業(yè)務(wù)代碼和關(guān)注點代碼(重復(fù)代碼),在執(zhí)行業(yè)務(wù)代碼時,動態(tài)的注入關(guān)注點代碼。切面就是關(guān)注點代碼形成的類。

4. Spring AOP

前文提到 JDK 代理和 CGLIB 代理兩種動態(tài)代理。優(yōu)秀的 Spring 框架把兩種方式在底層都集成了進去,我們無需擔(dān)心自己去實現(xiàn)動態(tài)生成代理。那么,Spring是如何生成代理對象的?

  1. 創(chuàng)建容器對象的時候,根據(jù)切入點表達式攔截的類,生成代理對象。
  2. 如果目標(biāo)對象有實現(xiàn)接口,使用 JDK 代理。如果目標(biāo)對象沒有實現(xiàn)接口,則使用 CGLIB 代理。然后從容器獲取代理后的對象,在運行期植入“切面”類的方法。通過查看 Spring 源碼,我們在 DefaultAopProxyFactory 類中,找到這樣一段話。

簡單的從字面意思看出:如果有接口,則使用 JDK 代理,反之使用 CGLIB ,這剛好印證了前文所闡述的內(nèi)容。Spring AOP 綜合兩種代理方式的使用前提有會如下結(jié)論:如果目標(biāo)類沒有實現(xiàn)接口,且 class 為 final 修飾的,則不能進行 Spring AOP 編程!

知道了原理,現(xiàn)在我們將自己手動實現(xiàn) Spring 的 AOP:

package test.spring_aop_anno;

import org.aspectj.lang.ProceedingJoinPoint;

public interface IUserDao {
    void save();
}

// 用于測試 CGLIB 動態(tài)代理
class OrderDao {
    public void save() {
        //int i =1/0; 用于測試異常通知
        System.out.println("保存訂單...");
    }
}

//用于測試 JDK 動態(tài)代理
class UserDao implements IUserDao {
    public void save() {
        //int i =1/0; 用于測試異常通知
        System.out.println("保存用戶...");
    }
}

//切面類
class TransactionAop {

    public void beginTransaction() {
        System.out.println("[前置通知]  開啟事務(wù)..");
    }

    public void commit() {
        System.out.println("[后置通知] 提交事務(wù)..");
    }

    public void afterReturing() {
        System.out.println("[返回后通知]");
    }

    public void afterThrowing() {
        System.out.println("[異常通知]");
    }

    public void arroud(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("[環(huán)繞前:]");
        pjp.proceed(); // 執(zhí)行目標(biāo)方法
        System.out.println("[環(huán)繞后:]");
    }
}

Spring 的 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:context="http://www.springframework.org/schema/context"
    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/context


http://www.springframework.org/schema/context/spring-context.xsd


http://www.springframework.org/schema/aop


http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- dao實例加入容器 -->
    <bean id="userDao" class="test.spring_aop_anno.UserDao"></bean>

    <!-- dao實例加入容器 -->
    <bean id="orderDao" class="test.spring_aop_anno.OrderDao"></bean>

    <!-- 實例化切面類 -->
    <bean id="transactionAop" class="test.spring_aop_anno.TransactionAop"></bean>

    <!-- Aop相關(guān)配置 -->
    <aop:config>
        <!-- 切入點表達式定義 -->
        <aop:pointcut expression="execution(* test.spring_aop_anno.*Dao.*(..))" id="transactionPointcut"/>
        <!-- 切面配置 -->
        <aop:aspect ref="transactionAop">
            <!-- 【環(huán)繞通知】 -->
            <aop:around method="arroud" pointcut-ref="transactionPointcut"/>
            <!-- 【前置通知】 在目標(biāo)方法之前執(zhí)行 -->
            <aop:before method="beginTransaction" pointcut-ref="transactionPointcut" />
            <!-- 【后置通知】 -->
            <aop:after method="commit" pointcut-ref="transactionPointcut"/>
            <!-- 【返回后通知】 -->
            <aop:after-returning method="afterReturing" pointcut-ref="transactionPointcut"/>
            <!-- 異常通知 -->
            <aop:after-throwing method="afterThrowing" pointcut-ref="transactionPointcut"/>
        </aop:aspect>
    </aop:config>
</beans>

切入點表達式不在這里介紹。參考?Spring AOP 切入點表達式

代碼的測試結(jié)果如下:

到這里,我們已經(jīng)全部介紹完Spring AOP;氐介_篇的問題,我們拿它做什么?

  1. Spring聲明式事務(wù)管理配置:請參考博主的另一篇文章:分布式系統(tǒng)架構(gòu)實戰(zhàn) demo:SSM+Dubbo
  2. Controller層的參數(shù)校驗:參考 Spring AOP攔截Controller做參數(shù)校驗
  3. 使用 Spring AOP 實現(xiàn) MySQL 數(shù)據(jù)庫讀寫分離案例分析
  4. 在執(zhí)行方法前,判斷是否具有權(quán)限
  5. 對部分函數(shù)的調(diào)用進行日志記錄:監(jiān)控部分重要函數(shù),若拋出指定的異常,可以以短信或郵件方式通知相關(guān)人員。
  6. 信息過濾,頁面轉(zhuǎn)發(fā)等等功能

博主一個人的力量有限,只能列舉這么多,歡迎評論區(qū)對文章做補充。

Spring AOP還能做什么,實現(xiàn)什么魔幻功能,就在于我們每一個平凡而又睿智的程序猿!

標(biāo)簽: Mysql ssl 代碼 權(quán)限 數(shù)據(jù)庫

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請與原作者聯(lián)系。

上一篇:一文搞清Gradle依賴

下一篇:php 操作mysql