【干貨】JDK動態(tài)代理的實現(xiàn)原理以及如何手寫一個JDK動態(tài)代理

動態(tài)代理

代理模式是設(shè)計模式中非常重要的一種類型屑咳,而設(shè)計模式又是編程中非常重要的知識點萨赁,特別是在業(yè)務系統(tǒng)的重構(gòu)中,更是有舉足輕重的地位兆龙。代理模式從類型上來說杖爽,可以分為靜態(tài)代理和動態(tài)代理兩種類型。

在解釋動態(tài)代理之前我們先理解一下靜態(tài)代理:

首先你要明白靜態(tài)代理的作用

我們有一個字體提供類紫皇,有多種實現(xiàn)(從磁盤慰安,從網(wǎng)絡(luò),從系統(tǒng))

public interface FontProvider {
    Font getFont(String name);
}

public abstract class ProviderFactory {
    public static FontProvider getFontProvider() {
        return new FontProviderFromDisk();
    }
}

public class Main() {
    public static void main(String[] args) {
        FontProvider fontProvider = ProviderFactory.getFontProvider();
        Font font = fontProvider.getFont("微軟雅黑");
        ......
    }
}

現(xiàn)在我們希望給他加上一個緩存功能聪铺,我們可以用靜態(tài)代理來完成

public class CachedFontProvider implements FontProvider {
    private FontProvider fontProvider;
    private Map<String, Font> cached;

    public CachedFontProvider(FontProvider fontProvider) {
        this.fontProvider = fontProvider;
    }

    public Font getFont(String name) {
        Font font = cached.get(name);
        if (font == null) {
            font = fontProvider.getFont(name);
            cached.put(name, font);
        }
        return font;
    }
}


/* 對工廠類進行相應修改化焕,代碼使用處不必進行任何修改。
   這也是面向接口編程以及工廠模式的一個好處 */
public abstract class ProviderFactory {
    public static FontProvider getFontProvider() {
        return new CachedFontProvider(new FontProviderFromDisk());
    }
}

當然铃剔,我們直接修改FontProviderFromDisk類也可以實現(xiàn)目的撒桨,但是我們還有FontProviderFromNet, FontProviderFromSystem等多種實現(xiàn)類,一一修改太過繁瑣且易出錯键兜。況且將來還可能添加日志凤类,權(quán)限檢查,異常處理等功能顯然用代理類更好一點普气。

然而今天的重點是:我們都知道牛逼轟轟的Spring AOP的實現(xiàn)的一種方式是使用JDK的動態(tài)代理(另一種是cglib)谜疤,大部分人也會用jdk的動態(tài)代理,不過沒有研究過jdk的動態(tài)代理到底是怎么實現(xiàn)的。今天就來揭開他的神秘面紗夷磕;

1. 原理源碼剖析

首先我們先來講一下JDK動態(tài)代理的實現(xiàn)原理

1.拿到被代理對象的引用苇侵,然后獲取他的接口
2.JDK代理重新生成一個類,同時實現(xiàn)我們給的代理對象所實現(xiàn)的接口
3.把被代理對象的引用拿到了
4.重新動態(tài)生成一個class字節(jié)碼
5.然后編譯

然后先實現(xiàn)一個動態(tài)代理企锌,代碼很簡單了榆浓,就是實現(xiàn)
java.lang.reflect.InvocationHandler接口,并使用
java.lang.reflect.Proxy.newProxyInstance()方法生成代理對象

/**
 * @author mark
 * @date 2018/3/30
 */
public class JdkInvocationHandler implements InvocationHandler {

    private ProductService target;

    public Object getInstance(ProductService target){
        this.target = target;
        Class clazz = this.target.getClass();
        // 參數(shù)1:被代理類的類加載器 參數(shù)2:被代理類的接口 參數(shù)3
        return Proxy.newProxyInstance(clazz.getClassLoader(),
                clazz.getInterfaces(),
                this);
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        String currentDate  = simpleDateFormat.format(new Date());
        System.out.println("日期【"+currentDate + "】添加了一款產(chǎn)品");

        return method.invoke(this.target,args);
    }
}

被代理接口和實現(xiàn)

/**
 * 模仿產(chǎn)品Service
 * @author mark
 * @date 2018-03-30
 */
public interface ProductService {
    /**
     * 添加產(chǎn)品
     * @param productName
     */
    void addProduct(String productName);
}

/**
 * @author mark
 * @date 2018/3/30
 */
public class ProductServiceImpl implements ProductService{
    public void addProduct(String productName) {
        System.out.println("正在添加"+productName);
    }
}

測試類

public class Test {
    public static void main(String[] args) throws Exception {
        ProductService productService = new ProductServiceImpl();
        ProductService proxy = (ProductService) new JdkInvocationHandler().getInstance(productService);
        proxy.addProduct("iphone");

        // 這里我們將jdk生成的代理類輸出了出來撕攒,方便后面分析使用
        byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{productService.getClass()});

        FileOutputStream os = new FileOutputStream("Proxy0.class");
        os.write(bytes);
        os.close();
    }
}

結(jié)果輸出

日期【2018-03-30】添加了一款產(chǎn)品
正在添加iphone

Process finished with exit code 0

上面我們實現(xiàn)動態(tài)動態(tài)代理的時候輸出了代理類的字節(jié)碼文件陡鹃,現(xiàn)在來看一下字節(jié)碼文件反編譯過后的內(nèi)容

import com.gwf.jdkproxy.ProductServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

// 繼承了Proxy類
public final class $Proxy0 extends Proxy implements ProductServiceImpl {
    private static Method m1;
    private static Method m8;
    private static Method m2;
    private static Method m3;
    private static Method m5;
    private static Method m4;
    private static Method m7;
    private static Method m9;
    private static Method m0;
    private static Method m6;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

....
....

/**
* 這里是代理類實現(xiàn)的被代理對象的接口的相同方法
*/
    public final void addProduct(String var1) throws  {
        try {
            // super.h 對應的是父類的h變量,他就是Proxy.nexInstance方法中的InvocationHandler參數(shù)
           // 所以這里實際上就是使用了我們自己寫的InvocationHandler實現(xiàn)類的invoke方法
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

   

    public final Class getClass() throws  {
        try {
            return (Class)super.h.invoke(this, m7, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

....
....
// 在靜態(tài)構(gòu)造塊中抖坪,代理類通過反射獲取了被代理類的詳細信息萍鲸,比如各種方法
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m8 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("notify");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("addProduct", Class.forName("java.lang.String"));
            m5 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("wait", Long.TYPE);
            m4 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("wait", Long.TYPE, Integer.TYPE);
            m7 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("getClass");
            m9 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("notifyAll");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m6 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("wait");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

補充一下上面代母注釋中的super.h

protected InvocationHandler h;

protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }

// 這個方法是Proxy的newProxyInstance方法,主要就是生成了上面的動態(tài)字節(jié)碼文件
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
       
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
// 重點看這里擦俐,將我們傳來的InvocationHandler參數(shù)穿給了構(gòu)造函數(shù)
            return cons.newInstance(new Object[]{h});
        } 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);
        }
    }

以上就是jdk動態(tài)代理的內(nèi)部實現(xiàn)過程脊阴,最后再次將上面的原理聲明一遍,強化記憶
1.拿到被代理對象的引用蚯瞧,然后獲取他的接口 (Proxy.getInstance方法)
2.JDK代理重新生成一個類嘿期,同時實現(xiàn)我們給的代理對象所實現(xiàn)的接口 (上面的反編譯文件中實現(xiàn)了同樣的接口)
3.把被代理對象的引用拿到了(上面被代理對象中在靜態(tài)代碼塊中通過反射獲取到的信息,以及我們實現(xiàn)的JdkInvocationHandler中的target)
4.重新動態(tài)生成一個class字節(jié)碼
5.然后編譯

2.自己手寫一個動態(tài)代理

(聲明:本代碼只用作實例埋合,很多細節(jié)沒有考慮進去备徐,比如,多接口的代理類甚颂,Object類的其他默認方法的代理蜜猾,為確保原汁原味,一些模板引擎和commons工具類也沒有使用振诬;覺得不足的老鐵們可以隨意完善蹭睡,記得評論區(qū)留言完善方法哦)

我們使用jdk代理的類名和方法名定義,已經(jīng)執(zhí)行思路赶么,但是所有的實現(xiàn)都自己來寫肩豁;

首先先定義出類結(jié)構(gòu)

/**
 * 自定義類加載器
 * @author gaowenfeng
 * @date 2018/3/30
 */
public class MyClassLoader extends ClassLoader {

    /**
     * 通過類名稱加載類字節(jié)碼文件到JVM中
     * @param name 類名
     * @return 類的Class獨享
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        return super.findClass(name);
    }
}
/**
 * @desc 自己實現(xiàn)的代理類,用來生成字節(jié)碼文件禽绪,并動態(tài)加載到JVM中
 * @author gaowenfeng
 * @date 2018/3/30
 */
public class MyProxy {
    /**
     * 生成代理對象
     * @param loader 類加載器蓖救,用于加載被代理類的類文件
     * @param interfaces 被代理類的接口
     * @param h 自定義的InvocationHandler接口,用于具體代理方法的執(zhí)行
     * @return 返回被代理后的代理對象
     * @throws IllegalArgumentException
     */
    public static Object newProxyInstance(MyClassLoader loader,
                                          Class<?>[] interfaces,
                                          MyInvocationHandler h)
            throws IllegalArgumentException{
        /**
         * 1.生成代理類的源代碼
         * 2.將生成的源代碼輸出到磁盤,保存為.java文件
         * 3.編譯源代碼印屁,并生成.java文件
         * 4.將class文件中的內(nèi)容循捺,動態(tài)加載到JVM中
         * 5.返回被代理后的代理對象
         */

        return null;

    }
}
/**
 * 自定義類加載器
 * @author gaowenfeng
 * @date 2018/3/30
 */
public class MyClassLoader extends ClassLoader {

    /**
     * 通過類名稱加載類字節(jié)碼文件到JVM中
     * @param name 類名
     * @return 類的Class獨享
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        return super.findClass(name);
    }
}
/**
 * @author gaowenfeng
 * @date 2018/3/30
 */
public class CustomInvocationHandler implements MyInvocationHandler {
    private ProductService target;

    public Object getInstance(ProductService target){
        this.target = target;
        Class clazz = this.target.getClass();
        // 參數(shù)1:被代理類的類加載器 參數(shù)2:被代理類的接口 參數(shù)3
        // 這里的MyClassLoader先用new的方式保證編譯不報錯,后面會修改
        return MyProxy.newProxyInstance(new MyClassLoader(),
                clazz.getInterfaces(),
                this);
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        String currentDate  = simpleDateFormat.format(new Date());
        System.out.println("日期【"+currentDate + "】添加了一款產(chǎn)品");

        return method.invoke(this.target,args);
    }
}

接下來我們來按照步驟一步一步的完善我們的類

生成代理類的源文件

/**
     * 生成代理類的源代碼
     * @return
     */
    private static String genSesource(Class<?> interfaces){
        StringBuilder src = new StringBuilder();
        src.append("package com.gwf.custom;").append(ln)
                .append("import java.lang.reflect.Method;").append(ln)
                .append("public class $Proxy0 implements ").append(interfaces.getName()).append("{").append(ln)
                .append("private MyInvocationHandler h;").append(ln)
                .append("public $Proxy0(MyInvocationHandler h){").append(ln)
                .append("this.h=h;").append(ln)
                .append("}").append(ln);

        for(Method method:interfaces.getMethods()){
            src.append("public ").append(method.getReturnType()).append(" ").append(method.getName()).append("() {").append(ln)
                    .append("try {").append(ln)
                    .append("Method m = ").append(interfaces.getName()).append(".class.getMethod(\"").append(method.getName()).append("\");").append(ln)
                    .append("this.h.invoke(this, m, new Object[]{});").append(ln)
                    .append("}catch (Throwable e){").append(ln)
                    .append("e.printStackTrace();").append(ln)
                    .append("}").append(ln)
                    .append("}").append(ln);
        }
        src.append("}");

        return src.toString();

    }

2.將源文件保存到本地

// 1.生成代理類的源代碼
            String src = genSesource(interfaces);
            // 2.將生成的源代碼輸出到磁盤雄人,保存為.java文件
            String path = MyProxy.class.getResource("").getPath();
            File file = new File(path+"$Proxy0.java");

            FileWriter fw = new FileWriter(file);
            fw.write(src);
            fw.close();

3.編譯源代碼从橘,并生成.java文件

// 3.編譯源代碼念赶,并生成.java文件
            // 獲取java編譯器
            JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
            // 標注java文件管理器,用來獲取java字節(jié)碼文件
            StandardJavaFileManager manager = javaCompiler.getStandardFileManager(null,null,null);
            Iterable iterable = manager.getJavaFileObjects(file);

            // 創(chuàng)建task恰力,通過java字節(jié)碼文件將類信息加載到JVM中
            JavaCompiler.CompilationTask task = javaCompiler.getTask(null,manager,null,null,null,iterable);
            // 開始執(zhí)行task
            task.call();
            // 關(guān)閉管理器
            manager.close();

4.將class文件中的內(nèi)容叉谜,動態(tài)加載到JVM中

public class MyClassLoader extends ClassLoader {

    private String baseDir;

    public MyClassLoader(){
        this.baseDir = MyClassLoader.class.getResource("").getPath();
    }

    /**
     * 通過類名稱加載類字節(jié)碼文件到JVM中
     * @param name 類名
     * @return 類的Class獨享
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 獲取類名
        String className = MyClassLoader.class.getPackage().getName()+"."+name;
        if(null == baseDir) {
            throw new ClassNotFoundException();
        }

        // 獲取類文件
        File file = new File(baseDir,name+".class");
        if(!file.exists()){
            throw new ClassNotFoundException();
        }

        // 將類文件轉(zhuǎn)換為字節(jié)數(shù)組
        try(
        FileInputStream in = new FileInputStream(file);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ){
            byte[] buffer = new byte[1024];
            int len;
            while ((len = in.read(buffer))!=-1){
                out.write(buffer,0,len);
            }

            // 調(diào)用父類方法生成class實例
            return defineClass(className,out.toByteArray(),0,out.size());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

5.返回被代理后的代理對象

Constructor c = proxyClass.getConstructor(MyInvocationHandler.class);
            return c.newInstance(h);

最后看一下總體的MyProxy類 的 newProxyInstance方法

public static Object newProxyInstance(MyClassLoader loader,
                                          Class<?> interfaces,
                                          MyInvocationHandler h)
            throws IllegalArgumentException{
        /**
         * 1.生成代理類的源代碼
         * 2.將生成的源代碼輸出到磁盤,保存為.java文件
         * 3.編譯源代碼踩萎,并生成.java文件
         * 4.將class文件中的內(nèi)容停局,動態(tài)加載到JVM中
         * 5.返回被代理后的代理對象
         */
        try {
            // 1.生成代理類的源代碼
            String src = genSesource(interfaces);
            // 2.將生成的源代碼輸出到磁盤,保存為.java文件
            String path = MyProxy.class.getResource("").getPath();
            File file = new File(path+"$Proxy0.java");

            FileWriter fw = new FileWriter(file);
            fw.write(src);
            fw.close();

            // 3.編譯源代碼香府,并生成.java文件
            JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager manager = javaCompiler.getStandardFileManager(null,null,null);
            Iterable iterable = manager.getJavaFileObjects(file);

            JavaCompiler.CompilationTask task = javaCompiler.getTask(null,manager,null,null,null,iterable);
            task.call();
            manager.close();

            // 4.將class文件中的內(nèi)容董栽,動態(tài)加載到JVM中
            Class proxyClass = loader.findClass("$Proxy0");

            // 5.返回被代理后的代理對象
            Constructor c = proxyClass.getConstructor(MyInvocationHandler.class);
            return c.newInstance(h);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;

    }

激動人心的時刻:測試運行

public class CustomClient {
    public static void main(String[] args){
        ProductService productService = new ProductServiceImpl();
        ProductService proxy = (ProductService) new CustomInvocationHandler().getInstance(productService);
        proxy.addProduct();
    }
}

運行結(jié)果

日期【2018-03-30】添加了一款產(chǎn)品
正在添加iphone

Process finished with exit code 0

總結(jié):以上通過理解jdk動態(tài)代理的原理,自己手寫了一個動態(tài)代理企孩,里面涉及到的重點主要是代理類字節(jié)碼的生成(這里采用通過反射強行生成源文件并編譯的方法锭碳,其實應該可以直接生成字節(jié)碼文件的,有興趣的同學可以嘗試)和將生成的類動態(tài)加載到JVM中(本次試驗由于測試勿璃,比較簡單擒抛,直接將類名硬編碼到了系統(tǒng)里,正常應該是自動加載)补疑,雖然還不完善歧沪,但是對于理解原理應該是有很多幫助了,歡迎同學們評論區(qū)留言評論給出更好的建議

在互聯(lián)網(wǎng)公司面試中癣丧,架構(gòu)的底層一定是面試官會問問的問題槽畔,針對面試官一般會提到的問題,我錄制了一些分布式胁编,微服務,性能優(yōu)化等技術(shù)點底層原理的錄像視頻鳞尔,加群895244712可以免費獲取這些錄像嬉橙,里面還有些分布式,微服務寥假,性能優(yōu)化市框,spring,MyBatis的等源碼知識點的錄像視頻糕韧。

image

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末枫振,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子萤彩,更是在濱河造成了極大的恐慌粪滤,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件雀扶,死亡現(xiàn)場離奇詭異杖小,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門予权,熙熙樓的掌柜王于貴愁眉苦臉地迎上來昂勉,“玉大人,你說我怎么就攤上這事扫腺「谡眨” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵笆环,是天一觀的道長谴返。 經(jīng)常有香客問我,道長咧织,這世上最難降的妖魔是什么嗓袱? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮习绢,結(jié)果婚禮上渠抹,老公的妹妹穿的比我還像新娘。我一直安慰自己闪萄,他們只是感情好梧却,可當我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著败去,像睡著了一般放航。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上圆裕,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天广鳍,我揣著相機與錄音,去河邊找鬼吓妆。 笑死赊时,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的行拢。 我是一名探鬼主播祖秒,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼舟奠!你這毒婦竟也來了竭缝?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤沼瘫,失蹤者是張志新(化名)和其女友劉穎呆瞻,沒想到半個月后棉饶,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡介时,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖歌焦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情砚哆,我是刑警寧澤独撇,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站躁锁,受9級特大地震影響纷铣,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜战转,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一搜立、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧槐秧,春花似錦啄踊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至膀懈,卻和暖如春顿锰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背启搂。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工硼控, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人狐血。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓淀歇,卻偏偏與公主長得像,于是被迫代替她去往敵國和親匈织。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,486評論 2 348

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

  • 一牡直、基本概念 1.什么是代理缀匕? 在闡述JDK動態(tài)代理之前,我們很有必要先來弄明白代理的概念碰逸。代理這個詞本身并不是計...
    小李彈花閱讀 16,433評論 2 40
  • title: Jdk動態(tài)代理原理解析 tags:代理 categories:筆記 date: 2017-06-14...
    行徑行閱讀 19,239評論 3 36
  • 教程: https://wangdoc.com/javascript/basic/index.html 筆記 1....
    智勇雙全的小六閱讀 136評論 0 0
  • 今天的主人公就是春秋時代楚國著名的賢君――楚莊王饵史,其在位期間為公元前613年――前590年在位满钟。 他少年即位胜榔,面臨...
    日月新莫閱讀 345評論 0 0