欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring AOP 與代理的概念與使用

 更新時(shí)間:2020年10月29日 09:55:50   作者:空夜  
大家知道我現(xiàn)在還是一個(gè) CRUD 崽,平時(shí)用 AOP 也是 CV 大法。最近痛定思痛,決定研究一下 Spring AOP 的原理。 這里寫一篇文章總結(jié)一下。主要介紹 Java 中 AOP 的實(shí)現(xiàn)原理,最后以兩個(gè)簡單的示例來收尾。

一、AOP 的基本概念

1.1 什么是 AOP

Aspect Oriented Programming,面向切面編程。

就跟我們說 OOP 是面向?qū)ο笠粯樱珹OP 是面向切面的。切面是分散在應(yīng)用中的一個(gè)標(biāo)準(zhǔn)代碼或功能。切面通常與實(shí)際的業(yè)務(wù)邏輯不同(例如,事務(wù)管理)。每個(gè)切面專注于一個(gè)特定的環(huán)切功能。

這里的切面呢,可以理解為橫切。比如在所有的 DAO 層方法上加上一個(gè)同樣的切面,功能是記錄日志;又或者在某個(gè)接口上應(yīng)用一個(gè)切面,作用是檢查權(quán)限。

AOP 是基于代理來實(shí)現(xiàn)的。而代理又分為靜態(tài)代理和動(dòng)態(tài)代理。兩者的區(qū)別在于代理類于何時(shí)生成。

下面我們講講代理是怎么回事?

1.2 代理與 Spring AOP

代理分為靜態(tài)代理和動(dòng)態(tài)代理:

  • 靜態(tài)代理:代理類在編譯階段生成,程序運(yùn)行前就存在。包括:AspectJ 靜態(tài)代理、JDK 靜態(tài)代理
  • 動(dòng)態(tài)代理:代理類在程序運(yùn)行時(shí)創(chuàng)建。包括:JDK 動(dòng)態(tài)代理、CGLib 動(dòng)態(tài)代理

Spring AOP 原理:

  • JDK Proxy:interface based
  • CGLib Proxy: class based

Spring AOP 中默認(rèn)使用 JDK 動(dòng)態(tài)代理,通過反射獲取被代理的類,這個(gè)類必須實(shí)現(xiàn)一個(gè)接口。如果目標(biāo)類沒有實(shí)現(xiàn)接口,就會(huì)默認(rèn)使用 CGLIB Proxy 來動(dòng)態(tài)生成代理目標(biāo)類,后者是被代理類的子類。

可以通過獲取代理對(duì)象并打印的方式來查看其類型(JDK Proxy 下是 com.sun.prxy, CGlib 下是子類.

AspectJ: 用特定的編譯器和語法,在編譯時(shí)增強(qiáng),實(shí)現(xiàn)了靜態(tài)代理技術(shù)。

1.3 Spring AOP 與 AspectJ 的區(qū)別

AspectJ 是一套完整的 AOP 解決方案,而 Spring AOP 并不是 —— 它只是在 Spring 框架下滿足其使用要求的一個(gè)解決方法,比如 Spring AOP 僅支持對(duì)方法使用切面。

二、靜態(tài)代理

2.1 AspectJ 靜態(tài)代理

基于特殊的編譯器和語法。這里不多介紹了。

IDEA 下編譯 AspectJ 可以參考這篇:https://blog.csdn.net/gavin_john/article/details/80156963

2.2 JDK 靜態(tài)代理

實(shí)際上是利用實(shí)現(xiàn)一個(gè)具體的代理類來調(diào)用業(yè)務(wù)類。代理類持有了一個(gè)業(yè)務(wù)類的引用。

更概況地說,JDK 靜態(tài)代理體現(xiàn)的是一種設(shè)計(jì)模式。

缺點(diǎn)很明顯,代碼冗余,難以維護(hù)。

這里以 借書 和 還書 這兩個(gè)行為來作為一個(gè)示例:

編寫一個(gè) BookService 接口:

public interface BookService {

    boolean borrow(String id, String userName);

    boolean reBack(String id, String userName);
}

然后實(shí)現(xiàn)這個(gè)接口:

public class BookServiceImpl implements BookService {

    @Override
    public boolean borrow(String id, String userName) {
        System.out.println(userName + " 借書:" + id);
        return true;
    }

    @Override
    public boolean reBack(String id, String userName) {
        System.out.println(userName + " 還書:" + id);
        return true;
    }
}

下面我們來編寫 BookService 的代理類:

public class BookProxy implements BookService {

    private BookServiceImpl bookService;

    public BookProxy(BookServiceImpl bookService) {
        this.bookService = bookService;
    }

    @Override
    public boolean borrow(String id, String userName) {
        boolean res = false;
        if (check()) {
            res = bookService.borrow(id, userName);
        }
        addLog();
        return res;
    }

    @Override
    public boolean reBack(String id, String userName) {
        boolean res = false;
        if (check()) {
            res = bookService.reBack(id, userName);
        }
        addLog();
        return res;
    }

    //
    private boolean check() {
        System.out.println("檢查權(quán)限");
        return true;
    }

    private void addLog() {
        System.out.println("操作完成");
    }
}

編寫一個(gè)測試類:

public class MainTest {

    public static void main(String[] args) {
        BookProxy proxy = new BookProxy(new BookServiceImpl());
        proxy.borrow("123", "eknown");
        proxy.reBack("234", "java");
    }
}

這里我們可以看到,JDK 靜態(tài)代理就是說在原來的實(shí)現(xiàn)類上套一層 代理。它好像是體現(xiàn)了代理模式,但實(shí)際上并沒有帶來太多的好處。代碼相當(dāng)冗余,也不利于維護(hù)。

真正體現(xiàn)代理模式好處的還是動(dòng)態(tài)代理,下面我們來看看動(dòng)態(tài)代理的原理。

三、動(dòng)態(tài)代理

動(dòng)態(tài)代理是程序運(yùn)行時(shí),由 JVM 根據(jù)反射等機(jī)制動(dòng)態(tài)生成代理類的。

也就是說,程序運(yùn)行前,我們僅僅定義了代理的規(guī)則,而不知道代理類具體長什么樣,這不像上面的靜態(tài)代理里,我們完整地定義了代理對(duì)象。

3.1 JDK 動(dòng)態(tài)代理

JDK 動(dòng)態(tài)代理是基于接口的。

我們可以通過實(shí)現(xiàn) InvocationHandler 接口來手動(dòng)創(chuàng)建一個(gè) JDK 代理類。

首先需要定義一個(gè)接口,讓業(yè)務(wù)類和代理類都實(shí)現(xiàn)這個(gè)接口。

然后編寫一個(gè) InvocationHandler 接口的實(shí)現(xiàn)類:

public class BookProxy implements InvocationHandler {

    // 被該代理類處理的業(yè)務(wù)類
    private BookService bookService;

    public BookProxy(BookService bookService) {
        this.bookService = bookService;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object res = null;
        if (check()) {
            // 調(diào)用實(shí)際的 method,參數(shù)是 接口 + 參數(shù)
            res = method.invoke(bookService, args);
        }
        addLog();
        return res;
    }
    
    private boolean check() {
        System.out.println("檢查權(quán)限");
        return true;
    }

    private void addLog() {
        System.out.println("操作完成");
    }
}

測試:

public class MainTest {

    public static void main(String[] args) {
        // 創(chuàng)建被代理的實(shí)際業(yè)務(wù)類
        BookServiceImpl bookServiceImpl = new BookServiceImpl();

        ClassLoader classLoader = bookServiceImpl.getClass().getClassLoader();
        // 獲取所有的接口方法
        Class[] interfaces = bookServiceImpl.getClass().getInterfaces();

        // 構(gòu)造 Handler
        InvocationHandler invocationHandler = new BookProxy(bookServiceImpl);

        // 創(chuàng)建代理
        Object obj = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
        BookService bookService = (BookService) obj;
        bookService.borrow("abc", "eknown");
        bookService.reBack("c23", "py");
    }
}

3.2 CGLIB 動(dòng)態(tài)代理

CGLIB 代理的原理是:讓代理類繼承業(yè)務(wù)類(也就自動(dòng)擁有了業(yè)務(wù)類的所有非 final 的 public 方法)

我們這里手動(dòng)編寫一個(gè) CGLIB 的代理試試看。

首先我們有一個(gè) BookServiceImpl 業(yè)務(wù)類,這個(gè)業(yè)務(wù)類可以實(shí)現(xiàn)接口,也可以就是單純的一個(gè)業(yè)務(wù)類。

然后我們定義一個(gè) BookCglibProxy 類:

public class BookCglibProxy implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        check();
        // 調(diào)用實(shí)際的 method
        Object obj = methodProxy.invokeSuper(o, objects);
        addLog();
        return obj;
    }

    private boolean check() {
        System.out.println("檢查權(quán)限");
        return true;
    }

    private void addLog() {
        System.out.println("操作完成");
    }
}

測試類:

public class CglibTest {
    public static void main(String[] args) {
        BookServiceImpl bookServiceImpl = new BookServiceImpl();

        BookCglibProxy proxy = new BookCglibProxy();

        // cjlib 中的增強(qiáng)器,用于創(chuàng)建動(dòng)態(tài)代理(被代理類的子類)
        Enhancer enhancer = new Enhancer();
        // 設(shè)置要被代理的類
        enhancer.setSuperclass(bookServiceImpl.getClass());
        // 設(shè)置回調(diào)
        enhancer.setCallback(proxy);

        // 強(qiáng)轉(zhuǎn)成父類
        BookServiceImpl proxyResult = (BookServiceImpl) enhancer.create();
        proxyResult.borrow("12333", "ye");
        proxyResult.reBack("123", "fe");
    }
}

在第一節(jié)我們提到過 Spring AOP 是基于 JDK 動(dòng)態(tài)代理和 CGLIB 動(dòng)態(tài)代理的。下面我們來 Spring AOP 的一些基本案例。

四、Spring AOP 實(shí)例

AOP 中一些概念詞匯,通過這些詞匯,我們可以對(duì) AOP 有更高一層的抽象。

  • Aspect - 切面,分散在應(yīng)用中的一個(gè)標(biāo)準(zhǔn)代碼或功能。切面通常與實(shí)際的業(yè)務(wù)邏輯不同(例如,事務(wù)管理)。每個(gè)切面專注于一個(gè)特定的環(huán)切功能。
  • Joinpoint - 連接點(diǎn),是程序執(zhí)行過程中的特定點(diǎn),比如方法執(zhí)行、構(gòu)造器調(diào)用、字段賦值
  • Advice - 通知,切面在某個(gè)連接點(diǎn)采取的操作。Advice 有 5 種類型。
  • Pointcut - 切入點(diǎn),一個(gè)匹配連接點(diǎn)的正則表達(dá)式。每當(dāng)連接點(diǎn)匹配了一個(gè)切入點(diǎn)時(shí),一個(gè)特定的通知就會(huì)被執(zhí)行。
  • Weaving - 織入,指的是將切面和目標(biāo)對(duì)象連接起來以創(chuàng)建代理對(duì)象的過程。

Spring AOP 有兩種實(shí)現(xiàn)方式:基于 XML 或基于注解。更流行、更方便的是后者。(阿 sir,不會(huì)還有人用 XML 來做 Bean 的配置文件吧?)

4.1 基于 XML 的實(shí)例

首先定義一下接口和實(shí)現(xiàn)類(沒有注解的?。T倬帉懸粋€(gè)代理類:

這里的代理類方法以 JoinPoint 為參數(shù)即可:

public class BookAspect {

    public void checkUser(JoinPoint point) {
        System.out.println("-----before-----");
        Object[] args = point.getArgs();
        for (Object arg : args) {
            System.out.println(arg);
        }
        System.out.println("檢查用戶權(quán)限...");
    }

    public void saveLog(JoinPoint point) {
        System.out.println("-----after-----");
        Object[] args = point.getArgs();
        for (Object arg : args) {
            System.out.println(arg);
        }
        System.out.println("請(qǐng)求完畢,記錄日志...");
    }
}

然后編寫 Spring 的配置文件:

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">

    <!-- 定義 bean -->
    <bean id="bookService" class="com.example.springaopdemo.basicxml.BookServiceImpl" />
    <bean id="bookAspect" class="com.example.springaopdemo.basicxml.BookAspect" />

    <aop:config>
        <!-- 這是定義一個(gè)切面,切面是切點(diǎn)和通知的集合-->
        <aop:aspect id="do" ref="bookAspect">
            <!-- 定義切點(diǎn) ,后面是 expression 語言,表示包括該接口中定義的所有方法都會(huì)被執(zhí)行 -->
            <aop:pointcut id="point" expression="execution(* com.example.springaopdemo.basicxml.BookService.*(..))" />
            <!-- 定義通知 -->
            <aop:before method="checkUser" pointcut-ref="point" />
            <aop:after method="saveLog" pointcut-ref="point" />
        </aop:aspect>
    </aop:config>
</beans>

運(yùn)行測試:

public class AopXMLTest {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("SpringAop.xml");

        BookService bookService = context.getBean("bookService", BookService.class);
        bookService.borrow("123", "eknown");
        bookService.reback("123", "eknown");
    }
}

基于 XML 配置的 Spring 現(xiàn)在已經(jīng)很少使用了。下面我們來看看如何基于注解使用 Spring AOP

4.2 基于注解的實(shí)例

這里以一個(gè)使用 SpringBoot 框架的 Web 項(xiàng)目作為簡單的實(shí)例。

首先創(chuàng)建一個(gè) SpringBoot 項(xiàng)目,寫好 Controller、Service、DAO 層的基本類。(示例源碼中沒有使用 Mybatis 等持久層框架,而是用 Map 來模擬數(shù)據(jù)的存取)

下面我們針對(duì) UserService 接口類,添加切面。

@Aspect
@Component
public class UserAspect {

    @Before(value = "execution(* com.example.springaopdemo.boot.UserService.*(..))")
    public void checkUser(JoinPoint point) {
        System.out.println("-----before-----");
        Object[] args = point.getArgs();
        for (Object arg : args) {
            System.out.println(arg);
        }
        System.out.println("檢查..." + point);
    }

    @After(value = "execution(* com.example.springaopdemo.boot.UserService.*(..))")
    public void saveLog(JoinPoint point) {
        System.out.println("-----after-----");
        Object[] args = point.getArgs();
        for (Object arg : args) {
            System.out.println(arg);
        }

        // 這里可以使用 point.getTarget() 獲取到切面對(duì)應(yīng)的 bean
        //Object target = point.getTarget();
        //UserService userService = (UserService) target;
        //List<User> userList = userService.findAll();

        System.out.println("請(qǐng)求完畢,記錄日志..." + point);
    }

    @Around(value = "execution(* com.example.springaopdemo.boot.UserService.save(..))")
    public Object saveAround(ProceedingJoinPoint point) {
        System.out.println("around-before");
        Object obj = null;
        try {
            obj = point.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("around-after");
        return obj;
    }

}

示例中使用了 @Before/@After/@Aroud 三個(gè)注解,value 中使用切點(diǎn)表達(dá)式,分別匹配了 UserService 接口的所有方法和單個(gè) save 方法。

我們還可以通過切點(diǎn)表達(dá)式匹配自定義的注解,比如實(shí)現(xiàn)一個(gè) UserMonitor 注解,然后定義其切點(diǎn)方法:

public @interface UserMonitor {

    String value() default "";

    int roleLimit() default 0;

}

切點(diǎn):

@Around("@annotation(com.example.springaopdemo.boot.UserMonitor)")
    public Object userRolePointCut(ProceedingJoinPoint point) {
        System.out.println("檢查用戶權(quán)限...");
    
       // 獲取參數(shù)
        Object[] args = point.getArgs();
        Class<?>[] argTypes = new Class[point.getArgs().length];
        for (int i = 0; i < args.length; i++) {
            argTypes[i] = args[i].getClass();
        }
      
        // 獲取方法
        Method method = null;
        try {
            method = point.getTarget().getClass()
                    .getMethod(point.getSignature().getName(), argTypes);
        } catch (NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
        }

        // 獲取方法上的該注解,之后可以根據(jù)注解中的值進(jìn)行一些操作,比如判定是否具有權(quán)限
        UserMonitor monitor = method.getAnnotation(UserMonitor.class);
        System.out.println(monitor);
        Object obj = null;
        try {
            obj = point.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return obj;
    }

以上就是Spring AOP 與代理的概念與使用的詳細(xì)內(nèi)容,更多關(guān)于Spring AOP 與代理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • MyBatis Log Free插件IDE下載與使用方式

    MyBatis Log Free插件IDE下載與使用方式

    這篇文章主要介紹了MyBatis Log Free插件IDE下載與使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-07-07
  • Java設(shè)計(jì)模式 模板模式及應(yīng)用場景解析

    Java設(shè)計(jì)模式 模板模式及應(yīng)用場景解析

    這篇文章主要介紹了Java設(shè)計(jì)模式 模板模式及應(yīng)用場景解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-07-07
  • 在Java程序中使用數(shù)據(jù)庫的新方法

    在Java程序中使用數(shù)據(jù)庫的新方法

    這篇文章主要介紹了在Java程序中使用數(shù)據(jù)庫的新方法,講述了Java8以來數(shù)據(jù)庫API的一些新特性,需要的朋友可以參考下
    2015-07-07
  • centos7安裝jdk-8u333詳細(xì)圖文教程

    centos7安裝jdk-8u333詳細(xì)圖文教程

    很多集成環(huán)境并不包含jdk環(huán)境,即使有相應(yīng)的組件,在使用時(shí)也無法很好的使用,調(diào)試過程中也會(huì)遇到各種各樣的問題,所以很多情況下還是建議在服務(wù)器內(nèi)手動(dòng)部署,下面這篇文章主要給大家介紹了關(guān)于centos7安裝jdk-8u333的相關(guān)資料,需要的朋友可以參考下
    2023-05-05
  • 基于ReentrantLock的實(shí)現(xiàn)原理講解

    基于ReentrantLock的實(shí)現(xiàn)原理講解

    這篇文章主要介紹了ReentrantLock的實(shí)現(xiàn)原理,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • Mac M1 Java 開發(fā)環(huán)境配置詳解

    Mac M1 Java 開發(fā)環(huán)境配置詳解

    這篇文章主要介紹了Mac M1 Java 開發(fā)環(huán)境配置詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • JAVA多線程與并發(fā)學(xué)習(xí)總結(jié)分析

    JAVA多線程與并發(fā)學(xué)習(xí)總結(jié)分析

    以下是對(duì)小編對(duì)JAVA多線程與并發(fā)的學(xué)習(xí)進(jìn)行了總結(jié)介紹,需要的朋友可以過來參考下
    2013-08-08
  • 淺談java中HashMap鍵的比較方式

    淺談java中HashMap鍵的比較方式

    今天帶大家了解一下java中HashMap鍵的比較方式,文中有非常詳細(xì)的解釋說明及代碼示例,對(duì)正在學(xué)習(xí)java的小伙伴們很有幫助,需要的朋友可以參考下
    2021-05-05
  • java 基礎(chǔ)知識(shí)之網(wǎng)絡(luò)通信(TCP通信、UDP通信、多播以及NIO)總結(jié)

    java 基礎(chǔ)知識(shí)之網(wǎng)絡(luò)通信(TCP通信、UDP通信、多播以及NIO)總結(jié)

    這篇文章主要介紹了java 基礎(chǔ)知識(shí)之網(wǎng)絡(luò)通信總結(jié)的相關(guān)資料,包括TCP通信、UDP通信、多播以及NIO,需要的朋友可以參考下
    2017-03-03
  • Java8 新特性之日期時(shí)間對(duì)象及一些其他特性

    Java8 新特性之日期時(shí)間對(duì)象及一些其他特性

    這篇文章主要介紹了Java8 新特性之日期時(shí)間對(duì)象及一些其他特性,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-01-01

最新評(píng)論