java 代理模式-靜態代理與動態代理

来源:https://www.cnblogs.com/arebirth/archive/2019/09/10/javaproxyschema.html
-Advertisement-
Play Games

最近在研究SpringAOP,當然要學習AOP就要知道這麼健碩、強大的功能的背後究竟隱藏著怎樣不可告人的“秘密”?? 接下來就是查閱了許多資料詳細的研究了一下Java的代理模式,感覺還是非常非常重要的, 我們作為一個有“內涵的”程式員就更應該掌握啦!(本文需要細心、帶有審視的目光來甄別其中的內容) ...


最近在研究SpringAOP,當然要學習AOP就要知道這麼健碩、強大的功能的背後究竟隱藏著怎樣不可告人的“秘密??

接下來就是查閱了許多資料詳細的研究了一下Java的代理模式,感覺還是非常非常重要的,

我們作為一個有“內涵的”程式員就更應該掌握啦!(本文需要細心、帶有審視的目光來甄別其中的內容)


在學習代理模式的時候我首先要提出幾個問題,

  1、什麼是代理模式?

    舉個例子吧:我們生活中的租房問題。假如我們去租個房子,我們大多數情況下是不會知道房主(就是真正租房,一手貨源)的,我們是不是都是先去某些租房平臺,或者去找當地的中介去詢問何時的房子。我們通過九牛二虎之力在中介那裡找到了個物美價廉的房子後,你的租金是不是交給了中介,中介還會收取一些額外的推薦費啦,押金啦、手續費等之類的,那麼好,這樣的一小段,就已經出來了其中兩大核心對象了。

    房主(把房子交給中介的人):被代理對象

    中介(租給你房子的人):代理對象

  2、代理模式有哪些作用? 

    1.可以隱藏目標的的具體實現(還是拿上面租房的例子來說,房主把房子交給了中介,並和中介談好了價格我 7你3啊。然後當我們去租房子的時候,是中介正面把房子租給了我們,而真正背後賣房子的並未出面,這樣就隱藏了背後人的信息和提高了背後人的安全)

    2.可以在不修改目標類代碼的情況下,對其增加新的功能。(上面例子來說:房東把房子交給中介的時候價格可能只有1000,但是房東可以賣到5000,然後賣出去後在把1000給房東,自己收入4000,這樣原房東不但收到了應由的錢,中介還收入了更多的額外費用)。

  3、代理模式有哪幾種?分別都有什麼不同?

    在我們Java程式中代理模式分為:靜態代理和動態代理(動態代理又分為:JDK動態代理和CGLIB動態代理)

    至於什麼不同,接下來正式我們著重要學習的內容

 


 

靜態代理

什麼是靜態代理呢?

  • 顧名思義,就是靜態的,死的,一旦創建了就不允許修改了或者說很難修改(指的是程式運行期間)
  • 專業一些的解釋
    • 如若代理類在程式運行之前就已經存在,那麼這種代理方式被稱為靜態代理。這種情況下的代理類通常都是我們在Java代碼中定義的。
    • 通常情況下靜態代理類和目標類通常都會實現同一個介面或者派生自同一父類  

具體實現:

被代理類與代理類共同實現的介面

package cn.arebirth.staticproxy;

/**
 * 需要實現的共同介面
* 因為要保證代理類要不改變被代理類原來功能的基礎上增加新的功能
*/ public interface RentalHouse { /** * 出租房子 */ void rent(); }

被代理類(房東)

package cn.arebirth.staticproxy;

/**
 * 房東(目標類、被代理類)
 */
public class Host implements RentalHouse {

    @Override
    public void rent() {
        System.out.println("我是房東,出租500平米的大房子");
    }
}

代理類(中介)

package cn.arebirth.staticproxy;

/**
 * 靜態代理類(中介、代理類)
 * 註意:需要被代理類實現相同的介面
 */
public class StaticProxy implements RentalHouse {
    private RentalHouse rentalHouse;

    public StaticProxy(RentalHouse rentalHouse) {
        this.rentalHouse = rentalHouse;
    }

    @Override
    public void rent() {
        System.out.println("我是中介,收你500推薦費");
        //調用被代理類的租房方法
        rentalHouse.rent();
        System.out.println("我是中介,我又想收你1000塊錢!");
    }
}

測試類

package cn.arebirth.staticproxy;

public class Test {
    public static void main(String[] args) {
        //創建被代理度下行
        Host host = new Host();
        /**
         * 創建代理對象,並把被代理對象傳遞給代理對象,
         * 因為他們都實現相同的介面,實現了相同的方法,這樣的話傳遞的對象可以是房東1 房東2 ...
         */
        StaticProxy proxy = new StaticProxy(host);
        proxy.rent();
    }
}


輸出結果:
    我是中介,收你500推薦費    
    我是房東,出租500平米的大房子
    我是中介,我又想收你1000塊錢!

 

試想一下,如果有兩個房東,三個,四個,甚至更多個房東的話,我們怎麼寫?

 

被代理類

package cn.arebirth.staticproxy;

/**
 * 房東1(目標類、被代理類)
 */
public class Host1 implements RentalHouse {

    @Override
    public void rent() {
        System.out.println("我是房東1,出租500平米的大房子");
    }
}


package cn.arebirth.staticproxy;

/**
 * 房東2(目標類、被代理類)
 */
public class Host2 implements RentalHouse {

    @Override
    public void rent() {
        System.out.println("我是房東2,出租500平米的大房子");
    }
}

 

代理類(中介)

package cn.arebirth.staticproxy;

/**
 * 靜態代理類(中介、代理類)
 * 註意:需要被代理類實現相同的介面
 */
public class StaticProxy implements RentalHouse{
    //什麼價位的房子
    private int moneuy;

    public Agent(int moneuy) {
        this.moneuy = moneuy;
    }

    @Override
    public void renting() {
        //出租房東的租房
        //中介調用的租房方法仍然是房東的租房方法
        System.out.println("收取50元推薦費");
        if (moneuy <= 800) {//金額小於等於800的時候
            Host1 host = new Host1();
            host.rent();
        } else {
            Host2 host = new Host2();
            host.rent();
        }

        System.out.println("收取500元押金費用");
    }
}

測試類

public class Test{
    public static void main(String[] args) {
        StaticProxy proxy= new StaticProxy(1000);
        proxy.renting();
    }
}


輸出結果:
   我是房東2,出租500平米的大房子


 

靜態代理的缺點:

  我們仔細來觀察下,隨著我們的被代理對象的增多,也是就是房東越來越多,那我們的被代理類就會越來越冗餘,中介的壓力也就會越來越大。

 


 

動態代理

  常用的動態代理又分為JDK動態代理和CGLIB動態代理

那麼兩者的使用場景又是什麼呢??

  如果目標對象實現了介面,就是上面我們舉到的例子,房東和中介共同實現的介面類似,這樣的話就採用JDK動態代理

  如果目標對象沒有實現介面,必須採用CGLIB動態代理

 

具體實現:

 

要實現的共同介面

package cn.arebirth.jdkproxy;

/**
 * 需要實現的共同介面
 */
public interface RentalHouse {
    /**
     * 出租房子
     */
    void rent();
}

房東(被代理對象)

package cn.arebirth.jdkproxy;

/**
 * 房東(目標類、被代理類)
 */
public class Host implements RentalHouse {

    @Override
    public void rent() {
        System.out.println("我是房東,出租500平米的大房子");
    }
}

核心來了!(JDK動態代理實現類)package cn.arebirth.jdkproxy;

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

/**
 * JDK動態代理類
 */
public class JdkProxy implements InvocationHandler {
  //接收要被代理的類 RentalHouse rentalHouse;
public JdkProxy(RentalHouse rentalHouse) { this.rentalHouse = rentalHouse; } /** * @return 執行該方法就會產生代理類 */ public Object getProxy() { /** * Proxy.getProxyInstance(param1,param2,para3); * 參數一:傳入一個classLoader()對象 * 參數二:傳入被代理類所實現的介面的數組,因為代理類底層要根據介面產生 * 參數三:參數類型是InvocationHandler即可,this即當前類,我們當前類實現了此介面 */ Object proxy = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{RentalHouse.class}, this); return proxy; } /** * 這個方法類似於上面我所講述的靜態代理模式裡面的中介類的rent()方法 * @param proxy 代理對象--中介 * @param method 代理對象中的方法 * @param args 代理對象方法中的參數 * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("我是中介,收你500推薦費"); Object invoke = method.invoke(rentalHouse, args); System.out.println("我是中介,我又想收你1000塊錢!"); //返回執行方法後的返回值 return invoke; } }

測試類

package cn.arebirth.jdkproxy;

public class Test {
    public static void main(String[] args) {
        //創建JdkProxy動態代理對象類,並把需要被代理的對象傳遞進去
        JdkProxy jdkProxy = new JdkProxy(new Host());

        //獲得代理類 這裡一定要寫他們共同實現的介面  利用java多態的特性,如果直接寫真實類型是會報錯的
        RentalHouse  proxy= (RentalHouse)jdkProxy.getProxy();
        proxy.rent();
    }
}

輸出結果:
  我是中介,收你500推薦費   我是房東,出租500平米的大房子   我是中介,我又想收你1000塊錢!

 

我想當你看到這裡一定會感到困惑,為什麼我們獲取到了代理對象後執行的執行代理對象的方法,明明是房東的方法,怎麼顯示的好像是JDK動態代理類裡面的invoke()方法??或許還有其他的困惑,我們將在下邊一一講解如何實現的原理。

既然想知道如何實現的,那麼我們就要從底層出發,來看看,底層的Proxy代理到底幫我們生成了一個怎樣的代理類。

再開始之前我希望接下來的代碼與操作你是跟著我同步進行的,這樣子才會更深刻有更好的理解,當然不排除你是個人腦機器模擬器

開始吧!

 

我們想要看底層如何實現的,那麼我們首先就要獲得代理類的class文件

下麵是我寫的一個獲取JDKProxy動態代理所生成的代理文件的工具類

 

package cn.arebirth.jdkproxy;

import sun.misc.ProxyGenerator;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;

public class ProxyUtil {
    /**
     * @param proxyName  生成文件代理類的名字
     * @param interClass 代理類所實現的介面的class數組形式
     * @param path       寫出路徑
     */
    public static void writeProxyClassToHardDisk(String proxyName, Class[] interClass, String path) {
        byte[] bytes = ProxyGenerator.generateProxyClass(proxyName, interClass);

        FileOutputStream out = null;

        try {
            out = new FileOutputStream(path + File.separator + proxyName+".class");
            out.write(bytes);
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (out != null)
                    out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

然後我們在測試類裡面使用它

package cn.arebirth.jdkproxy;

public class Test {
    public static void main(String[] args) {
        //創建JdkProxy動態代理對象類,並把需要被代理的對象傳遞進去
        JdkProxy jdkProxy = new JdkProxy(new Host());

        //獲得代理類
        RentalHouse proxy = (RentalHouse) jdkProxy.getProxy();
        proxy.rent();
        //寫入JDK動態代理生成的代理類
        ProxyUtil.writeProxyClassToHardDisk(proxy.getClass().getSimpleName(), proxy.getClass().getInterfaces(), "F:\\Tools\\DevelopmentTools");
    }
}

 

最後生成出來的代理類是一個class位元組碼文件,我們需要使用反編譯工具來查看,我使用的是Luten(這個工具打開方式特殊,百度自行查找)

import cn.arebirth.jdkproxy.*;
import java.lang.reflect.*;

public final class $Proxy0 extends Proxy implements RentalHouse
{
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;
    
    public $Proxy0(final InvocationHandler invocationHandler) {
        super(invocationHandler);
    }
    
    public final boolean equals(final Object o) {
        try {
            return (boolean)super.h.invoke(this, $Proxy0.m1, new Object[] { o });
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }
    
    public final void rent() {//這個就是編譯器自動為我們實現了的介面中的方法
        try {
            super.h.invoke(this, $Proxy0.m3, null); //並調用了invoke
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }
    
    public final String toString() {
        try {
            return (String)super.h.invoke(this, $Proxy0.m2, null);
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }
    
    public final int hashCode() {
        try {
            return (int)super.h.invoke(this, $Proxy0.m0, null);
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }
    
//在類第一次載入的時候,給全局變數進行初始化,看看裡面有沒有我們稍微眼熟的東西
static { try { $Proxy0.m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); $Proxy0.m3 = Class.forName("cn.arebirth.jdkproxy.RentalHouse").getMethod("rent", (Class<?>[])new Class[0]);//這裡就是我們所實現的介面中的方法 $Proxy0.m2 = Class.forName("java.lang.Object").getMethod("toString", (Class<?>[])new Class[0]); $Proxy0.m0 = Class.forName("java.lang.Object").getMethod("hashCode", (Class<?>[])new Class[0]); } catch (NoSuchMethodException ex) { throw new NoSuchMethodError(ex.getMessage()); } catch (ClassNotFoundException ex2) { throw new NoClassDefFoundError(ex2.getMessage()); } } }

在看到這裡的時候,我們大概能瞭解到,原來我們在使用我們代理類調用租房的方法的時候,它裡面並沒有我們寫的代碼而是執行了一個

 super.h.invoke(this, $Proxy0.m3, null);

那麼super是誰?就是它自動繼承的Proxy類

那麼h是什麼?就是Proxy類的InvocationHandler

 

 那麼我們看這個InvocationHandler類是不是感覺有那麼一丟丟的眼熟啊,回過頭看,這就是我們在寫JDK動態代理類的時候實現的那個介面。

!!!!!!!!!!!!!

然後它又調用了invoke(this, $Proxy0.m3, null);

第一個參數:代理類對象

第二個參數:要執行的代理類對象的方  Method對象,這個Method對象的值已經由我們的編譯器幫我們構建好了,我們只需要第一個次載入這個類的時候他就會自動賦值了(static代碼塊)

第三個參數:方法裡面的參數,當沒有參數的時候就是NULL

 

 

帶著你的疑惑,從頭在開始好好敲一遍代碼,捋一遍,相信我,你將有更大的收貨!!

下麵是簡略畫的思路圖,試著用畫圖來畫出你的思維

 

 

 

那麼說完了這個JDK動態代理,我們有沒有發現什麼缺點??找茬時間開始

  是不是這個JDK動態代理必須要依賴介面才能實現,如果沒有介面的話,那麼JDK動態代理也就涼涼了對吧,!

 


 

那麼好,接下來我們將說一種更加強大的動態代理方式CGLIB,它的實現原理只要我們懂了JDK動態代理,那麼下麵的就是小兒科啦哈哈~

 

CGLIB的代理將不再需要介面也可以生成代理類,但是它需要導包!

 

 當然,你也可以選擇其他版本的JAR包!

===

開始

 

被代理類

package cn.arebirth.cglibproxy;

/**
 * 被代理類
 */
public class Host {
    public void rent() {
        System.out.println("Host:rental house");
    }
}

生成代理對象的cglib類

package cn.arebirth.cglibproxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibProxy implements MethodInterceptor {

    /**
     * 獲取代理類
     *
     * @return
     */
    public Object getProxy() {
        Enhancer enhancer = new Enhancer();

        //設置被代理對象
        enhancer.setSuperclass(Host.class);
        //設置回調方法  當前對象
        enhancer.setCallback(this);

        //創建代理對象
        Object o = enhancer.create();
        return o;
    }

    /**
     * @param o           被代理對象
     * @param method      被代理對象方法
     * @param objects     被代理對象方法中的參數
     * @param methodProxy 代理類中的方法
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("中介收取手續費");
        Object o1 = methodProxy.invokeSuper(o, objects);
        System.out.println("中介收取中介費");
        return o1;
    }
}

測試類

package cn.arebirth.cglibproxy;

public class Test {
    public static void main(String[] args) {
        CglibProxy cglibProxy = new CglibProxy();
        Host proxy = (Host) cglibProxy.getProxy();//強制轉換為我們的房主
        proxy.rent();
    }
}

輸出結果:
  中介收取手續費
  Host:rental house
  中介收取中介費

 

我們動態代理的最大好處就是,可以在沒有介面的情況下,只有一個類,我們就可以動態的代理,在程式運行的時候動態的為他創建代理類


 

 

最後讓我們大概的總結下:

 

代理模式:
靜態代理
動態代理:JDK動態代理 CGLIB動態代理

代理模式的三個要素
A.抽象的類或介面 完成一件怎樣的事情
B 被代理對象 事情操作具體內容
C 代理對象 幫助我們完成事情的同時 可以增加其他的東西

具體的列子:我們找中介租房子
A 抽象的類或者介面 租房子
B 被代理對象 房東
C 代理對象 中介

代理模式的好處
A 房東可以安心的做自己的事情 (被代理對象可以做自己的事情)
B 我們有了問題可以直接找中介 (被代理對象變得比較安全)
C 可以增強代碼的擴展性

JDK動態代理和CGLIB動態代理的使用場景

我們可以這樣記一下,只要被代理類沒有實現介面,我們就必須使用CGLIB動態代理

 


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

-Advertisement-
Play Games
更多相關文章
  • 為了更好的管理應用的配置,也為了不用每次更改配置文件都重啟,我們可以使用配置中心 關於eureka的服務註冊和rabbitMQ的安裝使用(自動更新配置需要用到rabbitMQ)這裡不贅述,只關註配置中心的內容 我們需要引入關鍵的包是這三個 需要在啟動類加上@EnableConfigServer註解, ...
  • 這裡建議先去看看路徑的問題,看看application.xml的裡面的導入的相應的配置文件的路徑有沒有問題,如下: 再者看看相應的註解有沒有加上,service和controller等的註解 如果再不行,在pon.xml文件加上下麵的: <build><resources> <!--編譯之後包含xm ...
  • 面試題 如何保證消息隊列的高可用? 面試官心理分析 如果有人問到你 MQ 的知識, 高可用是必問的 。 "上一講" 提到,MQ 會導致 系統可用性降低 。所以只要你用了 MQ,接下來問的一些要點肯定就是圍繞著 MQ 的那些缺點怎麼來解決了。 要是你傻乎乎的就乾用了一個 MQ,各種問題從來沒考慮過,那 ...
  • 安裝方法: 註:python環境一定要配置好。 1.第一步:下載 官方網站:http://www.pyinstaller.org/downloads.html 此處下載版本為穩定版。 2.第二步:下載完成後解壓,打開cmd。 例如:我的在F盤根目錄下。可更換目錄,建議不要有目錄不要帶有中文。 上圖: ...
  • 1. 為什麼用HashMap? 1. 簡述一下Map類繼承關係? 1. 解決哈希衝突的方法? 1. 為什麼HashMap線程不安全? 1. resize機制? 1. HashMap的工作原理是什麼? 1. 有什麼方法可以減少碰撞? 1. HashMap中hash函數怎麼是是實現的? 1. 拉鏈法導致 ...
  • Intellij IDEA在maven項目中添加外部Jar包運行,我們知道Intellij IDEA是非常好用的Java語言開發的集成環境。提供了非常多實用的功能,包括了智能代碼助手、代碼自動提示、代碼重構、各種插件等,當然也集成了maven,正常情況下,我們創建maven項目時,相關的jar包會自... ...
  • 前言 上網瀏覽網頁的時候,看見好的內容免不了要使用複製粘貼,但是我們看到的內容、心裡想要的內容和實際粘貼後的內容往往不一致。數據的獲取始於複製,終於粘貼,那麼問題來了,在這中間系統做了哪些操作,我們怎麼能控制它呢? 人生苦短,我用python,查閱相關資料之後發現有很多不一樣的實現方式,如利用內置c ...
  • range函數可創建一個整數列表。 如果需要知道當前元素在列表中的索引,推薦用enumerate代替range。 zip函數用於同時遍歷多個迭代器。 ...
一周排行
    -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 代碼 · 所 ...
  • 正文 下午找企業的人去鎮上做貸後。 車上聽同事跟那個司機對罵,火星子都快出來了。司機跟那同事更熟一些,連我在內一共就三個人,同事那一手指桑罵槐給我都聽愣了。司機也是老社會人了,馬上聽出來了,為那個無辜的企業經辦人辯護,實際上是為自己辯護。 “這個事情你不能怪企業。”“但他們總不能讓銀行的人全權負責, ...