有點(diǎn)深度的聊聊JDK動(dòng)態(tài)代理

在接觸SpringAOP的時(shí)候,大家一定會(huì)被這神奇的功能所折服飞涂,想知道其中的奧秘旦部,底層到底是如何實(shí)現(xiàn)的祈搜。于是,大家會(huì)通過(guò)搜索引擎士八,知道了一個(gè)陌生的名詞:動(dòng)態(tài)代理容燕,慢慢的又知道了動(dòng)態(tài)代理有多種實(shí)現(xiàn)方式,比如 JDK動(dòng)態(tài)代理婚度,Cglib 等等蘸秘。今天我就來(lái)簡(jiǎn)單說(shuō)說(shuō)JDK動(dòng)態(tài)代理

JDK動(dòng)態(tài)代理的簡(jiǎn)單應(yīng)用

我們還是從一個(gè)最簡(jiǎn)單的例子著手:

首先我們需要定義一個(gè)接口:

public interface UserService {
    void query();
}

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

public class UserServiceImpl implements UserService {
    public void query() {
        System.out.println("查詢用戶信息");
    }
}

定義一個(gè)類蝗茁,需要實(shí)現(xiàn)InvocationHandler:

public class MyInvocationHandler implements InvocationHandler {

    Object target;

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

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("進(jìn)入了invoke");
        method.invoke(target);
        System.out.println("執(zhí)行了invoke");
        return null;
    }
}

然后就是Main方法了:

public class Main {
    public static void main(String[] args) {
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler(new UserServiceImpl());
        Object o = Proxy.newProxyInstance(Main.class.getClassLoader(),
                new Class[]{UserService.class}
                , myInvocationHandler);

        ((UserService)o).query();
    }
}

運(yùn)行:

image.png

可以看到醋虏,一切正常,成功的執(zhí)行了增強(qiáng)的邏輯哮翘,也執(zhí)行了目標(biāo)方法颈嚼。

三個(gè)疑惑

雖然說(shuō)這是最簡(jiǎn)單的一個(gè)例子了,但是在初學(xué)的時(shí)候饭寺,大家肯定和我一樣阻课,有不少疑惑:一是不知道為什么需要傳入接口,二是不知道為什么JDK動(dòng)態(tài)代理只能代理接口艰匙,三是不知道類加載器的作用限煞。還有,就是代碼比較復(fù)雜员凝。

這三個(gè)疑惑困擾我很久署驻,直到我跟著博客,自己手?jǐn)]一個(gè)閹割版的JDK動(dòng)態(tài)代理健霹,并且簡(jiǎn)單的看了下JDK最終生成的代碼以及源碼才明白硕舆。

寫一個(gè)閹割版的JDK動(dòng)態(tài)代理

我們先來(lái)分析下MyInvocationHandler類中的invoke方法,方法有三個(gè)參數(shù)骤公,第一個(gè)參數(shù)是代理類抚官,第二個(gè)參數(shù)是方法,第三個(gè)參數(shù)是 執(zhí)行方法需要用到的參數(shù)阶捆。方法內(nèi)部實(shí)現(xiàn)了兩個(gè)邏輯凌节,一個(gè)是增強(qiáng)邏輯 ,一個(gè)是執(zhí)行目標(biāo)方法洒试。我們不禁的想倍奢,如果我們可以自動(dòng)生成一個(gè)類,去調(diào)用MyInvocationHandler中的invoke方法是不是就可以實(shí)現(xiàn)動(dòng)態(tài)代理了垒棋。

人有多大膽卒煞,地有多大產(chǎn),這的確是一個(gè)大膽瘋狂的想法叼架,但是這確實(shí)可以辦到畔裕,主要有如下幾個(gè)步驟:

  1. 拼接代理類的代碼
  2. 輸出.java文件
  3. 編譯.java文件成.class文件
  4. 裝載.class文件
  5. 創(chuàng)建并返回代理類對(duì)象

為了方便衣撬,就不考慮返回值和帶參的情況了,我仿照現(xiàn)有的MyInvocationHandler 寫了一個(gè)閹割版的MockInvocationHandler類:

public class MockInvocationHandler {

    private Object targetObject;

    public MockInvocationHandler(Object targetObject) {
        this.targetObject = targetObject;

    }

    public void invoke(Method targetMethod) {
        try {
            System.out.println("進(jìn)入了invoke");
            targetMethod.invoke(targetObject, null);
            System.out.println("結(jié)束了invoke");
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

要調(diào)用到MockInvocationHandler 中的invoke方法扮饶,生成的代理類大概可能也許長(zhǎng)這個(gè)樣子:

public class $Proxy implements 需要代理的接口{
     MockInvocationHandler h;
     public $Proxy (MockInvocationHandler h ) {this.h = h; }
     public void query(){
      try{ 
        //method=需要的執(zhí)行方法
         this.h.invoke(method);
        }catch(Exception ex){}
    }
}

好了具练,接下來(lái)就是體力活了,直接貼上代碼:

public class MockProxy {

    final static String ENTER = "\n";
    final static String TAB = "\t";

    public static Object newProxyInstance(Class interfaceClass,MockInvocationHandler h) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("package com.codebear;");
        stringBuilder.append(ENTER);
        stringBuilder.append("import java.lang.reflect.*;");
        stringBuilder.append(ENTER);
        stringBuilder.append("public class $Proxy implements " + interfaceClass.getName() + "{");
        stringBuilder.append(ENTER);
        stringBuilder.append(TAB);
        stringBuilder.append(" MockInvocationHandler h;");
        stringBuilder.append(ENTER);
        stringBuilder.append(TAB);
        stringBuilder.append(" public $Proxy (MockInvocationHandler h ) {this.h = h; }");
        stringBuilder.append(ENTER);
        stringBuilder.append(TAB);
        for (Method method : interfaceClass.getMethods()) {
            stringBuilder.append(" public void " + method.getName() + "(){");
            stringBuilder.append(ENTER);
            stringBuilder.append(TAB);
            stringBuilder.append("  try{ ");
            stringBuilder.append(ENTER);
            stringBuilder.append(TAB);
            stringBuilder.append(TAB);
            stringBuilder.append(" Method method = " + interfaceClass.getName() + ".class.getMethod(\"" + method.getName() + "\");");
            stringBuilder.append(ENTER);
            stringBuilder.append(TAB);
            stringBuilder.append(TAB);
            stringBuilder.append(" this.h.invoke(method);");
            stringBuilder.append(ENTER);
            stringBuilder.append(TAB);
            stringBuilder.append(TAB);
            stringBuilder.append("}catch(Exception ex){}");
            stringBuilder.append(ENTER);
            stringBuilder.append(TAB);
            stringBuilder.append("}");
            stringBuilder.append(ENTER);
            stringBuilder.append("}");
        }
        String content = stringBuilder.toString();

        try {
            String filePath = "D:\\com\\codebear\\$Proxy.java";
            File file = new File(filePath);

            File fileParent = file.getParentFile();
            if (!fileParent.exists()) {
                fileParent.mkdirs();
            }

            FileWriter fileWriter = new FileWriter(file);
            fileWriter.write(content);
            fileWriter.flush();
            fileWriter.close();

            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager fileManager = compiler.getStandardFileManager
                    (null, null, null);
            Iterable iterable = fileManager.getJavaFileObjects(filePath);
            JavaCompiler.CompilationTask task = compiler.getTask
                    (null, fileManager, null, null, null, iterable);
            task.call();
            fileManager.close();

            URLClassLoader classLoader = new URLClassLoader(new URL[]{new URL("file:D:\\\\")});
            Class<?> clazz = classLoader.loadClass("com.codebear.$Proxy");
            Constructor<?> constructor = clazz.getConstructor(MockInvocationHandler.class);
            return constructor.newInstance(h);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }
}

然后測(cè)試一下:

public class Main {
    public static void main(String[] args) {
        MockInvocationHandler mockInvocationHandler=new MockInvocationHandler(new UserServiceImpl());
        UserService userService = (UserService)MockProxy.
                newProxyInstance(UserService.class, mockInvocationHandler);
        userService.query();
    }
}

運(yùn)行結(jié)果:


image.png

好了甜无,在不考慮性能扛点,可維護(hù)性,安全性的情況下岂丘,我們閹割版的動(dòng)態(tài)代理就完成了陵究。代碼難度不是很大,就是比較考驗(yàn)反射和耐心奥帘。

簡(jiǎn)單分析下JDK源碼

源碼基于JDK1.8

 public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        //安全驗(yàn)證
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * 得到代理類
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);//獲得構(gòu)造方法
            final InvocationHandler ih = h;
            //如果構(gòu)造器器不是公共的畔乙,需要修改訪問(wèn)權(quán)限,使其可以訪問(wèn)
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});//通過(guò)構(gòu)造方法翩概,創(chuàng)建對(duì)象,傳入InvocationHandler 對(duì)象
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

簡(jiǎn)單的看下源碼返咱,我們一下子就能把目光移動(dòng)到getProxyClass0方法了钥庇,這才是我們需要關(guān)心的,我們點(diǎn)進(jìn)去:

  private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        //當(dāng)接口大于65535報(bào)錯(cuò)
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        return proxyClassCache.get(loader, interfaces);
    }

這方法可以說(shuō)什么事情也沒(méi)干咖摹,但是通過(guò)最后的proxyClassCache.get可以很容易的知道JDK的動(dòng)態(tài)代理是用了緩存的评姨,我們需要關(guān)注的方法在get里面,繼續(xù)點(diǎn)進(jìn)去:

public V get(K key, P parameter) {
        Objects.requireNonNull(parameter);

        expungeStaleEntries();
        //通過(guò)上游方法萤晴,可以知道key是類加載器吐句,這里是通過(guò)類加載器可以獲得第一層key
       Object cacheKey = CacheKey.valueOf(key, refQueue);
        
       //我們查看map的定義,可以看到map變量是一個(gè)兩層的ConcurrentMap
       ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);//通過(guò)第一層key嘗試獲取數(shù)據(jù)
       //如果valuesMap 為空店读,就新建一個(gè)ConcurrentHashMap嗦枢,
       //key就是生成出來(lái)的cacheKey,并把這個(gè)新建的ConcurrentHashMap推到map
       if (valuesMap == null) {
            ConcurrentMap<Object, Supplier<V>> oldValuesMap
                = map.putIfAbsent(cacheKey,
                                  valuesMap = new ConcurrentHashMap<>());
            if (oldValuesMap != null) {
                valuesMap = oldValuesMap;
            }
        }

        //通過(guò)上游方法可以知道key是類加載器屯断,parameter是類本身文虏,這里是通過(guò)類加載器和類本身獲得第二層key
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        Supplier<V> supplier = valuesMap.get(subKey);
        Factory factory = null;

        while (true) {
            if (supplier != null) {
                //如果有緩存,直接調(diào)用get方法后返回殖演,當(dāng)沒(méi)有緩存氧秘,會(huì)繼續(xù)執(zhí)行后面的代碼,
                //由于while (true)趴久,會(huì)第二次跑到這里丸相,再get返回出去,
                //其中g(shù)et方法調(diào)用的是WeakCahce中的靜態(tài)內(nèi)部類Factory的get方法
                V value = supplier.get();
                if (value != null) {
                    return value;
                }
            }
            //當(dāng)factory為空彼棍,會(huì)創(chuàng)建Factory對(duì)象
            if (factory == null) {
                factory = new Factory(key, parameter, subKey, valuesMap);
            }

            if (supplier == null) {
                supplier = valuesMap.putIfAbsent(subKey, factory);
                if (supplier == null) {
                    //當(dāng)沒(méi)有代理類緩存的時(shí)候灭忠,會(huì)運(yùn)行到這里膳算,把Factory的對(duì)象賦值給supplier ,
                    //進(jìn)行下一次循環(huán)更舞,supplier就不為空了畦幢,可以調(diào)用get方法返回出去了,
                    //這個(gè)Factory位于WeakCahce類中缆蝉,是一個(gè)靜態(tài)內(nèi)部類
                    supplier = factory;
                }
            } else {
                if (valuesMap.replace(subKey, supplier, factory)) {
                    supplier = factory;
                } else {
                    supplier = valuesMap.get(subKey);
                }
            }
        }
    }

這里面的代碼比較復(fù)雜宇葱,簡(jiǎn)單的來(lái)說(shuō):

  • JDK動(dòng)態(tài)代理是用了兩層的map去緩存,第一個(gè)層是類加載器刊头,第二層是 類加載器+本身
  • 當(dāng)有緩存黍瞧,直接調(diào)用get并且返回,反之繼續(xù)執(zhí)行下面的代碼原杂,為supplier進(jìn)行賦值印颤,由于while (true),會(huì)第二次跑到這里穿肄,再調(diào)用get()返回出去年局。核心在于supplier.get(),它調(diào)用的是WeakCahce中的靜態(tài)內(nèi)部類Factory的get()咸产,里面就是 獲取代理類的方法了矢否。

讓我們看下supplier.get()方法:

 value = Objects.requireNonNull(valueFactory.apply(key, parameter));

核心在于這一句話,但是valueFactory是什么脑溢?我們可以查看它的定義:

 private final BiFunction<K, P, V> valueFactory;

我們?cè)倏聪滤腤eakCahce構(gòu)造方法:

 public WeakCache(BiFunction<K, P, ?> subKeyFactory,
                     BiFunction<K, P, V> valueFactory) {
        this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
        this.valueFactory = Objects.requireNonNull(valueFactory);
    }

我們肯定在哪邊調(diào)用過(guò)這個(gè)構(gòu)造方法了僵朗,在Proxy類中有這樣的定義:

 private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

這個(gè)proxyClassCache有沒(méi)有很熟悉, 是的屑彻,它就在getProxyClass0方法中用到了验庙,這里創(chuàng)建了WeakCache對(duì)象,并且調(diào)用了帶兩個(gè)參數(shù)的構(gòu)造方法社牲,第二個(gè)參數(shù)是ProxyClassFactory對(duì)象粪薛,也就對(duì)應(yīng)了WeakCache中第二個(gè)參數(shù)BiFunction<K, P, V> valueFactory,然后把值賦值給了final valueFactory搏恤,valueFactory.apply所以最終會(huì)調(diào)用ProxyClassFactory中的apply方法汗菜。關(guān)鍵在于:

 byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);//生成代理類的二進(jìn)制數(shù)組
            try {
                 //內(nèi)部是native標(biāo)記的方法,是用C或者C++實(shí)現(xiàn)的挑社,這里不深究
                //方法內(nèi)部就是通過(guò)類加載器和上面生成的代理類的二進(jìn)制數(shù)組等數(shù)據(jù)陨界,經(jīng)過(guò)處理,成為Class
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                throw new IllegalArgumentException(e.toString());
            }

generateProxyClass方法內(nèi)部生成了代理類的二進(jìn)制數(shù)組痛阻,具體是怎么生成的菌瘪,大家可以點(diǎn)進(jìn)去自己看看,這里就不再繼續(xù)往下了,因?yàn)槲覀兊哪繕?biāo)就是找到generateProxyClass方法俏扩,然后自己寫一個(gè)方法糜工,去執(zhí)行g(shù)enerateProxyClass,把返回的byte[]輸出到.class文件录淡,利用idea的反編譯功能捌木,看看最終生成出來(lái)的代理類是什么樣子的:

 byte[] $proxies = ProxyGenerator.generateProxyClass("$Proxy", new Class[]{UserService.class});
        File file=new File("D:\\$Proxy.class");
        FileOutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream(file);
            try {
                outputStream.write($proxies);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

運(yùn)行,發(fā)現(xiàn)D盤出現(xiàn)了$Proxy.class文件嫉戚,我們把它拖到idea里面刨裆,看看它的真面目,因?yàn)樯傻拇a還是比較長(zhǎng)的彬檀,我這里只把核心代碼貼出來(lái):

//繼承了Proxy類
public final class $Proxy extends Proxy implements UserService {
    public $Proxy(InvocationHandler var1) throws  {
        super(var1);
    }
    public final void query() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
}

這代碼有沒(méi)有很熟悉帆啃,很接近我們自己手寫動(dòng)態(tài)代理生成的代理類。

解開疑惑

好了窍帝,先是自己手寫了一個(gè)閹割版的動(dòng)態(tài)代理努潘,然后簡(jiǎn)單的看了下JDK動(dòng)態(tài)代理源碼,也看了下JDK動(dòng)態(tài)代理生成的代理類坤学。這樣疯坤,就可以解開上面的三個(gè)疑惑了:

  1. 類加載器是干嘛的:其一:JDK內(nèi)部需要通過(guò)類加載作為緩存的key 其二:需要類加載器生成class
  2. 為什么需要接口:因?yàn)樯傻拇眍愋枰獙?shí)現(xiàn)這個(gè)接口
  3. 為什么JDK動(dòng)態(tài)代理只能代理接口:因?yàn)樯傻拇眍愐呀?jīng)繼承了Proxy類,Java是單繼承的深浮,所以沒(méi)法再繼承另外一個(gè)類了压怠。

有一些博客上可能會(huì)說(shuō)cglib和JDK動(dòng)態(tài)代理的區(qū)別,cglib是通過(guò)操作字節(jié)碼去完成代理的略号,其實(shí)JDK動(dòng)態(tài)代理也操作了字節(jié)碼

經(jīng)過(guò)這么一分析洋闽,相信大家對(duì)JDK動(dòng)態(tài)代理有了一個(gè)新的認(rèn)識(shí)玄柠。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市诫舅,隨后出現(xiàn)的幾起案子羽利,更是在濱河造成了極大的恐慌,老刑警劉巖刊懈,帶你破解...
    沈念sama閱讀 211,348評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件这弧,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡虚汛,警方通過(guò)查閱死者的電腦和手機(jī)匾浪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)卷哩,“玉大人蛋辈,你說(shuō)我怎么就攤上這事。” “怎么了冷溶?”我有些...
    開封第一講書人閱讀 156,936評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵渐白,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我逞频,道長(zhǎng)纯衍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,427評(píng)論 1 283
  • 正文 為了忘掉前任苗胀,我火速辦了婚禮襟诸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘柒巫。我一直安慰自己励堡,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,467評(píng)論 6 385
  • 文/花漫 我一把揭開白布堡掏。 她就那樣靜靜地躺著应结,像睡著了一般。 火紅的嫁衣襯著肌膚如雪泉唁。 梳的紋絲不亂的頭發(fā)上鹅龄,一...
    開封第一講書人閱讀 49,785評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音亭畜,去河邊找鬼扮休。 笑死,一個(gè)胖子當(dāng)著我的面吹牛拴鸵,可吹牛的內(nèi)容都是我干的玷坠。 我是一名探鬼主播,決...
    沈念sama閱讀 38,931評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼劲藐,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼八堡!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起聘芜,我...
    開封第一講書人閱讀 37,696評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤兄渺,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后汰现,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體挂谍,經(jīng)...
    沈念sama閱讀 44,141評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,483評(píng)論 2 327
  • 正文 我和宋清朗相戀三年瞎饲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了口叙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,625評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡嗅战,死狀恐怖庐扫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤形庭,帶...
    沈念sama閱讀 34,291評(píng)論 4 329
  • 正文 年R本政府宣布铅辞,位于F島的核電站,受9級(jí)特大地震影響萨醒,放射性物質(zhì)發(fā)生泄漏斟珊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,892評(píng)論 3 312
  • 文/蒙蒙 一富纸、第九天 我趴在偏房一處隱蔽的房頂上張望囤踩。 院中可真熱鬧,春花似錦晓褪、人聲如沸堵漱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)勤庐。三九已至,卻和暖如春好港,著一層夾襖步出監(jiān)牢的瞬間愉镰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工钧汹, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留丈探,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓拔莱,卻偏偏與公主長(zhǎng)得像碗降,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子塘秦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,492評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 一讼渊、基本概念 1.什么是代理? 在闡述JDK動(dòng)態(tài)代理之前嗤形,我們很有必要先來(lái)弄明白代理的概念精偿。代理這個(gè)詞本身并不是計(jì)...
    小李彈花閱讀 16,433評(píng)論 2 40
  • title: Jdk動(dòng)態(tài)代理原理解析 tags:代理 categories:筆記 date: 2017-06-14...
    行徑行閱讀 19,239評(píng)論 3 36
  • 緩存是計(jì)算機(jī)技術(shù)中一種非常有用的技術(shù)弧圆,是一個(gè)通用的提升數(shù)據(jù)訪問(wèn)性能的思路赋兵,一般用來(lái)保存常用的數(shù)據(jù),容量較小搔预,但訪問(wèn)...
    淡淡的傷你閱讀 724評(píng)論 0 0
  • 理查德·塞勒(Richard Thaler)2017年獲得了諾貝爾經(jīng)濟(jì)獎(jiǎng)霹期,他的研究?jī)?nèi)容是行為經(jīng)濟(jì)學(xué),也可以稱為金融...
    1be9e6600e94閱讀 898評(píng)論 0 0
  • 每種花拯田,都有不同的意義與用處历造。 有一個(gè)淘氣的小男孩,他聽說(shuō)只要有人提著裝著女蛇王的籠子走過(guò)任何一朵...
    小昱籽兒閱讀 222評(píng)論 0 0