Spring09_動態代理

来源:https://www.cnblogs.com/codeaction/archive/2020/06/01/13027243.html
-Advertisement-
Play Games

本教程源碼請訪問:tutorial_demo 一、什麼是動態代理 1.1、概念 動態代理的用途與裝飾模式很相似,就是為了對某個對象進行增強。所有使用裝飾者模式的案例都可以使用動態代理來替換。 特點:位元組碼隨用隨創建,隨用隨載入; 作用:不修改源碼的基礎上對方法增強; 學習目的:為了學習AOP的原理做 ...


本教程源碼請訪問:tutorial_demo

一、什麼是動態代理

1.1、概念

動態代理的用途與裝飾模式很相似,就是為了對某個對象進行增強。所有使用裝飾者模式的案例都可以使用動態代理來替換。

特點:位元組碼隨用隨創建,隨用隨載入;

作用:不修改源碼的基礎上對方法增強;

學習目的:為了學習AOP的原理做準備。

1.2、實現方式

兩種方式

  1. 基於介面的動態代理,JDK官方提供,被代理類最少實現一個介面,如果沒有則不能使用
  2. 基於子類的動態代理,第三方cglib庫提供。

我們這篇教程使用基於介面的動態代理方式講解,所有案例都使用這種方式。

1.3、需要明確的幾個概念

目標對象:被增強的對象。

代理對象:需要目標對象,然後在目標對象上添加了增強後的對象。

目標方法:被增強的方法。

代理對象 = 目標對象 + 增強

到現在為止,我們需要知道有一種方式可以在不改變目標對象方法的前提下,對方法進行增強,這個方式就是動態代理。使用它,我們需要提供目標對象增強生成代理對象

得到了代理對象就相當於有了一個強化版的目標對象,運行相關方法,除了運行方法本身,增強的內容也會被運行,從而實現了在不改變源碼的前提下,對方法進行增強。

1.4、基於介面的動態代理方式詳解

1.4.1、如何生成代理對象

使用Proxy類中的newProxyInstance方法。

1.4.2、newProxyInstance方法參數詳解

ClassLoader loader

類載入器類型,你不用去理睬它,你只需要知道怎麼可以獲得它就可以了,獲取方法:

this.class.getClassLoader();

只要你有一個Class對象就可以獲取到ClassLoader對象。

Class[] interfaces

指定newProxyInstance()方法返回的對象要實現哪些介面,因為是數組,可以指定多個介面。

InvocationHandler h

三個參數中最重要的一個參數,是一個介面,叫調用處理器。這個介面只有一個方法,即invoke()方法。它是對代理對象所有方法的唯一實現。也就是說,無論你調用代理對象上的哪個方法,其實都是在調用InvocationHandler的invoke()方法。

1.4.3、invoke()方法參數詳解

執行被代理對象的任何介面方法都會經過該方法。

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

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

Object[] args:表示當前被調用方法的參數,當然m.fun()這個調用是沒有參數的,所以args是一個長度為0的數組。

二、動態代理案例

下麵通過一個案例,說明動態代理的用途。

2.1、創建Maven工程並添加坐標

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.codeaction</groupId>
    <artifactId>proxy</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

2.2、創建一個IWaiter介面

package org.codeaction.proxy;

//表示服務員的介面
public interface IWaiter {
    //提供服務的方法
    void serve();
}

2.3、創建一個IWaiter介面的實現類

package org.codeaction.proxy;

//表示男服務員
public class ManWaiter implements IWaiter {
    @Override
    public void serve() {
        System.out.println("服務...");
    }
}

目前存在的問題,我希望讓ManWaiter提供服務的時候(調用serve方法)列印如下信息:

你好...
服務...
再見...

我們可以這樣做:

package org.codeaction.proxy;

//表示男服務員
public class ManWaiter implements IWaiter {
    @Override
    public void serve() {
        System.out.println("你好...");
        System.out.println("服務...");
        System.out.println("再見...");
    }
}

但是這樣我們修改了serve方法,如果將來有其他需求,我們還要再修改serve方法,這顯然很繁瑣,是不可取的,我們可以使用動態代理的方式在不修改源碼的基礎上對serve方法進行增強。

2.4、創建測試類使用動態代理

package org.codeaction.test;


import org.codeaction.proxy.IWaiter;
import org.codeaction.proxy.ManWaiter;
import org.junit.Test;

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

public class MyTest {
    @Test
    public void TestProxy() {
        //目標對象
        IWaiter manWaiter = new ManWaiter();

        /**
         * 三個參數,用來創建代理對象
         */
        ClassLoader loader = this.getClass().getClassLoader();
        Class[] interfaces = {IWaiter.class};
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object resultValue = null;

                System.out.println("你好...");
                resultValue = method.invoke(manWaiter, args);//調用目標對象的目標方法
                System.out.println("再見...");
                return resultValue;
            }
        };
        //得到代理對象,代理對象就是在目標對象的基礎上進行了增強的對象
        IWaiter waiter = (IWaiter) Proxy.newProxyInstance(loader, interfaces, handler);
        //前面添加“您好”,後面添加“再見”
        waiter.serve();
    }
}

運行測試方法,輸出如下:

你好...
服務...
再見...

通過上面的代碼及運行結果我們發現:

  1. 使用動態代理需要提供:目標對象、三大參數;
  2. 生成的代理對象是實現了三大參數中第二個參數的所有介面的對象;
  3. 運行代理對象的方法,就是運行invoke方法;
  4. 在invoke方法中實現增強。

三、動態代理使用代理工廠實現

上面的案例中,目標對象和增強綁定在了一起,無法自由切換,不靈活,接下來我們創建一個代理工廠來實現動態代理。

3.1、創建前置增強介面

package org.codeaction.proxy;

//前置增強
public interface BeforeAdvice {
    void before();
}

3.2、創建後置增強介面

package org.codeaction.proxy;

//後置增強
public interface AfterAdvice {
    void after();
}

3.3、創建代理工廠

package org.codeaction.proxy;

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

/**
 * 這個類用來生成代理對象
 * 需要的參數:
 *  * 目標對象
 *  * 增強
 * 怎麼用?
 *  1.創建代理工廠
 *  2.給工廠設置三樣東西:
 *      * 目標對象:setTargetObject(xxx);
 *      * 前置增強:setBeforeAdvice(該介面的實現)
 *      * 後置增強:setAfterAdvice(該介面的實現)
 *  3.調用createProxy()得到代理對象
 *      * 執行代理對象方法時:
 *          > 執行BeforeAdvice的before()
 *          > 目標對象的目標方法
 *          > 執行AfterAdvice的after()
 */
public class ProxyFactory {
    private Object targetObject;//目標對象
    private BeforeAdvice beforeAdvice;//前置增強
    private AfterAdvice afterAdvice;//後置增強

    public Object getTargetObject() {
        return targetObject;
    }

    public void setTargetObject(Object targetObject) {
        this.targetObject = targetObject;
    }

    public BeforeAdvice getBeforeAdvice() {
        return beforeAdvice;
    }

    public void setBeforeAdvice(BeforeAdvice beforeAdvice) {
        this.beforeAdvice = beforeAdvice;
    }

    public AfterAdvice getAfterAdvice() {
        return afterAdvice;
    }

    public void setAfterAdvice(AfterAdvice afterAdvice) {
        this.afterAdvice = afterAdvice;
    }

    //用來生成代理對象
    public Object createProxyObject() {
        //三大參數
        ClassLoader classLoader = this.getClass().getClassLoader();
        Class[] interfaces = this.targetObject.getClass().getInterfaces();
        InvocationHandler handler = new InvocationHandler() {
            //在調用代理對象的方法時會執行這裡的內容
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object resultValue = null;

                if(beforeAdvice != null) {
                    //執行前置增強
                    beforeAdvice.before();
                }
                //執行目標對象的目標方法
                resultValue = method.invoke(targetObject, args);
                if(afterAdvice != null) {
                    //執行後置增強
                    afterAdvice.after();
                }
                //返回目標對象的返回值
                return resultValue;
            }
        };

        //得到代理對象
        return Proxy.newProxyInstance(classLoader, interfaces, handler);
    }
}

3.4、在測試類中添加測試方法

@Test
public void testProxyFactory() {
    //創建工廠
    ProxyFactory factory = new ProxyFactory();
    //設置目標對象
    factory.setTargetObject(new ManWaiter());
    //設置前置增強
    factory.setBeforeAdvice(new BeforeAdvice() {
        @Override
        public void before() {
            System.out.println("你好...");
        }
    });
    //設置後置增強
    factory.setAfterAdvice(new AfterAdvice() {
        @Override
        public void after() {
            System.out.println("再見...");
        }
    });
    //創建代理對象
    IWaiter waiter = (IWaiter) factory.createProxyObject();
    //執行代理對象方法
    waiter.serve();
}

運行測試方法,輸出如下:

你好...
服務...
再見...

四、使用代理工廠的方式修改上一節的代碼

在上一篇文章我們將純註解方式結合Apache Commons DbUtils實現單表的CRUD操作的代碼修改成了支持事務的版本,每一個Service方法都要開啟事務,提交事務,回滾事務代碼冗餘,如果JdbcUtils中相關方法的方法名修改,那麼Service中每個調用位置都有修改,為瞭解決上面的問題,我們使用動態代理的方式修改上一節的代碼。

4.1、創建前置增強介面

package org.codeaction.proxy;

public interface BeforeAdvice {
    void before() throws Exception;
}

4.2、創建後置增強介面

package org.codeaction.proxy;

public interface AfterAdvice {
    void after() throws Exception;
}

4.3、創建特殊增強介面

這個是用來進行回滾的,就教他特殊增強吧。

package org.codeaction.proxy;

public interface ActAdvice {
    void act() throws Exception;
}

4.4、創建代理工廠

package org.codeaction.proxy;

import org.springframework.stereotype.Component;

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

@Component
public class ProxyFactory {
    private Object targetObject;
    private BeforeAdvice beforeAdvice;
    private AfterAdvice afterAdvice;
    private ActAdvice actAdvice;

    public ActAdvice getActAdvice() {
        return actAdvice;
    }

    public void setActAdvice(ActAdvice actAdvice) {
        this.actAdvice = actAdvice;
    }

    public Object getTargetObject() {
        return targetObject;
    }

    public void setTargetObject(Object targetObject) {
        this.targetObject = targetObject;
    }

    public BeforeAdvice getBeforeAdvice() {
        return beforeAdvice;
    }

    public void setBeforeAdvice(BeforeAdvice beforeAdvice) {
        this.beforeAdvice = beforeAdvice;
    }

    public AfterAdvice getAfterAdvice() {
        return afterAdvice;
    }

    public void setAfterAdvice(AfterAdvice afterAdvice) {
        this.afterAdvice = afterAdvice;
    }

    public Object createProxyObject() {

        ClassLoader classLoader = this.getClass().getClassLoader();
        Class[] interfaces = this.targetObject.getClass().getInterfaces();
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object resultValue = null;

                try {
                    if(beforeAdvice != null) {
                        beforeAdvice.before();
                    }
                    resultValue = method.invoke(targetObject, args);
                    if(afterAdvice != null) {
                        afterAdvice.after();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    if(actAdvice != null) {
                        actAdvice.act();
                    }
                }
                return resultValue;
            }
        };

        return Proxy.newProxyInstance(classLoader, interfaces, handler);
    }
}

4.5、修改Service介面的實現類AccountServiceImpl

去掉所有的和事務相關的代碼,讓Service只關註業務

package org.codeaction.service.impl;

import org.codeaction.dao.IAccountDao;
import org.codeaction.domain.Account;
import org.codeaction.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service("accountService")
public class AccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao accountDao;

    @Override
    public List<Account> findAll() throws Exception {
        return accountDao.findAll();
    }

    @Override
    public Account findById(Integer id) throws Exception {
        return accountDao.findById(id);
    }

    @Override
    public void save(Account account) throws Exception {
        accountDao.save(account);
    }

    @Override
    public void update(Account account) throws Exception {
        accountDao.update(account);
    }

    @Override
    public void delete(Integer id) throws Exception {
        accountDao.delete(id);
    }

    @Override
    public void transfer(Integer srcId, Integer dstId, Float money) throws Exception {
        Account src = accountDao.findById(srcId);
        Account dst = accountDao.findById(dstId);

        if(src == null) {
            throw new RuntimeException("轉出用戶不存在");
        }

        if(dst == null) {
            throw new RuntimeException("轉入用戶不存在");
        }

        if(src.getMoney() < money) {
            throw new RuntimeException("轉出賬戶餘額不足");
        }

        src.setMoney(src.getMoney() - money);
        dst.setMoney(dst.getMoney() + money);

        accountDao.update(src);

       //int x = 1/0;

        accountDao.update(dst);
    }
}

4.6、修改主配置類

package org.codeaction.config;

import org.codeaction.proxy.ActAdvice;
import org.codeaction.proxy.AfterAdvice;
import org.codeaction.proxy.BeforeAdvice;
import org.codeaction.proxy.ProxyFactory;
import org.codeaction.service.IAccountService;
import org.codeaction.util.JdbcUtils;
import org.springframework.context.annotation.*;

import java.sql.SQLException;

@Configuration
@ComponentScan(basePackages = "org.codeaction")
@PropertySource("classpath:jdbc.properties")
@Import(JdbcConfig.class)
public class MyConfig {
    /**
     *
     * @param factory 代理工廠
     * @param accountService 目標對象
     * @return
     */
    @Bean("proxyAccountService")
    public IAccountService createProxyAccountService(ProxyFactory factory, IAccountService accountService) {
        factory.setTargetObject(accountService);
        factory.setBeforeAdvice(new BeforeAdvice() {
            @Override
            public void before() throws Exception {
                //開啟事務
                JdbcUtils.beginTransaction();
            }
        });

        factory.setAfterAdvice(new AfterAdvice() {
            @Override
            public void after() throws Exception {
                //提交事務
                JdbcUtils.commitTransaction();
            }
        });

        factory.setActAdvice(new ActAdvice() {
            @Override
            public void act() throws Exception {
                //回滾
                JdbcUtils.rollbackTransaction();
            }
        });
		//生成代理對象
        return (IAccountService)factory.createProxyObject();
    }
}

4.7、修改測試類

package org.codeaction.test;

import org.codeaction.config.MyConfig;
import org.codeaction.domain.Account;
import org.codeaction.service.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = MyConfig.class)
public class MyTest {

    //註入代理工廠對象
    @Autowired
    @Qualifier("proxyAccountService")
    private IAccountService accountService;

    @Test
    public void testFindAll() throws Exception {
        List<Account> accounts = accountService.findAll();
        for (Account account : accounts) {
            System.out.println(account);
        }
    }

    @Test
    public void testFindById() throws Exception {
        Account account = accountService.findById(3);
        System.out.println(account);
    }

    @Test
    public void testSave() throws Exception {
        Account account = new Account();
        account.setName("abc");
        account.setMoney(10000F);

        accountService.save(account);

        System.out.println(account);
    }

    @Test
    public void testDelete() throws Exception {
        accountService.delete(4);
    }

    @Test
    public void testUpdate() throws Exception {
        Account account = new Account();
        account.setId(5);
        account.setName("ab111111111c111");
        account.setMoney(10000F);
        accountService.update(account);
    }

    @Test
    public void testTrans() throws Exception {
        accountService.transfer(1, 2, 10F);
    }
}

註意這裡註入的accountServie是代理工廠類的對象,運行測試方法,測試。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • *6.38(生成隨機字元)使用程式清單6-10RandomCharacter中的方法,列印100個大寫字母及100個一位數字,每行列印10個。 *6.38(Generate random characters) Use the methods in RandomCharacter in Listin ...
  • pydbclib是一個通用的python關係型資料庫操作工具包,使用統一的介面操作各種關係型資料庫(如 oracle、mysql、postgres、hive、impala等)進行增刪改查,它是對各個python資料庫連接驅動包(如sqlalchemy、pymysql、cx_Oracle、pyhive ...
  • 17.對象引用和拷貝 我們先來看看以下向個概念 變數:是系統變數名錶中的元素,通常是由程式員進行定義聲明 對象:是電腦分配的一塊記憶體,需要足夠的空間去表示它的值 引用:是自動形成的從變數到對象的指針 可變對象:允許對自身內容進行修改。如list、dict、set、自定義類型等。 不可變對象:不允許 ...
  • 16.生成器-迭代器 可迴圈迭代的對象稱為可迭代對象,迭代器和生成器函數是可迭代對象,在Python中提供了定義迭代器和生成器的協議和方法。 16.1 迭代和可迭代對象 16.1.1 可迭代對象、迭代器和可迭代協議 1.可迭代對象 在Python中,實現了__iter__()的對象是可迭代對象(It ...
  • <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/P ...
  • 《Java虛擬機規範》將虛擬機的記憶體分為以下幾個區域: 堆區:堆區是JVM中最大的一塊記憶體區域,按照垃圾分代收集的角度劃分,又可以分成年輕代和老年代,而年輕代記憶體又被分成三部分,Eden空間、From Survivor空間、To Survivor空間,預設情況下年輕代按照8:1:1的比例來分配; 方 ...
  • 1.簡述人工智慧、機器學習和深度學習三者的聯繫與區別。 人工智慧:機器學習和深度學習都是屬於一個領域的一個子集。但是人工智慧是機器學習的首要範疇。機器學習是深度學習的首要範疇。 機器學習:是人工智慧的子領域,也是人工智慧的核心。它包括了幾乎所有對世界影響最大的方法(包括深度學習)。機器學習理論主要是 ...
  • 1.補全代碼的聲明:alt + / 2.快速修複: ctrl + 1 3.批量導包:ctrl + shift + o 4.使用單行註釋:ctrl + / 5.使用多行註釋: ctrl + shift + / 6.取消多行註釋:ctrl + shift + \ 7.複製當前行的代碼上下移動:或 ctr ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...