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
  • GoF之工廠模式 @目錄GoF之工廠模式每博一文案1. 簡單說明“23種設計模式”1.2 介紹工廠模式的三種形態1.3 簡單工廠模式(靜態工廠模式)1.3.1 簡單工廠模式的優缺點:1.4 工廠方法模式1.4.1 工廠方法模式的優缺點:1.5 抽象工廠模式1.6 抽象工廠模式的優缺點:2. 總結:3 ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 本章將和大家分享ES的數據同步方案和ES集群相關知識。廢話不多說,下麵我們直接進入主題。 一、ES數據同步 1、數據同步問題 Elasticsearch中的酒店數據來自於mysql資料庫,因此mysql數據發生改變時,Elasticsearch也必須跟著改變,這個就是Elasticsearch與my ...
  • 引言 在我們之前的文章中介紹過使用Bogus生成模擬測試數據,今天來講解一下功能更加強大自動生成測試數據的工具的庫"AutoFixture"。 什麼是AutoFixture? AutoFixture 是一個針對 .NET 的開源庫,旨在最大程度地減少單元測試中的“安排(Arrange)”階段,以提高 ...
  • 經過前面幾個部分學習,相信學過的同學已經能夠掌握 .NET Emit 這種中間語言,並能使得它來編寫一些應用,以提高程式的性能。隨著 IL 指令篇的結束,本系列也已經接近尾聲,在這接近結束的最後,會提供幾個可供直接使用的示例,以供大伙分析或使用在項目中。 ...
  • 當從不同來源導入Excel數據時,可能存在重覆的記錄。為了確保數據的準確性,通常需要刪除這些重覆的行。手動查找並刪除可能會非常耗費時間,而通過編程腳本則可以實現在短時間內處理大量數據。本文將提供一個使用C# 快速查找並刪除Excel重覆項的免費解決方案。 以下是實現步驟: 1. 首先安裝免費.NET ...
  • C++ 異常處理 C++ 異常處理機制允許程式在運行時處理錯誤或意外情況。它提供了捕獲和處理錯誤的一種結構化方式,使程式更加健壯和可靠。 異常處理的基本概念: 異常: 程式在運行時發生的錯誤或意外情況。 拋出異常: 使用 throw 關鍵字將異常傳遞給調用堆棧。 捕獲異常: 使用 try-catch ...
  • 優秀且經驗豐富的Java開發人員的特征之一是對API的廣泛瞭解,包括JDK和第三方庫。 我花了很多時間來學習API,尤其是在閱讀了Effective Java 3rd Edition之後 ,Joshua Bloch建議在Java 3rd Edition中使用現有的API進行開發,而不是為常見的東西編 ...
  • 框架 · 使用laravel框架,原因:tp的框架路由和orm沒有laravel好用 · 使用強制路由,方便介面多時,分多版本,分文件夾等操作 介面 · 介面開發註意欄位類型,欄位是int,查詢成功失敗都要返回int(對接java等強類型語言方便) · 查詢介面用GET、其他用POST 代碼 · 所 ...
  • 正文 下午找企業的人去鎮上做貸後。 車上聽同事跟那個司機對罵,火星子都快出來了。司機跟那同事更熟一些,連我在內一共就三個人,同事那一手指桑罵槐給我都聽愣了。司機也是老社會人了,馬上聽出來了,為那個無辜的企業經辦人辯護,實際上是為自己辯護。 “這個事情你不能怪企業。”“但他們總不能讓銀行的人全權負責, ...