java動(dòng)態(tài)代理的原理及源碼分析

作者:tere

本系列文章主要是博主在學(xué)習(xí)spring aop的過程中了解到其使用了java動(dòng)態(tài)代理直焙,本著究根問底的態(tài)度,于是對(duì)java動(dòng)態(tài)代理的本質(zhì)原理做了一些研究吞琐,于是便有了這篇文章

為了盡快進(jìn)入正題捆探,這里先跳過spring aop和java動(dòng)態(tài)代理的使用流程的講解

不過,我們首先還是先看下java dynamic proxy的基本使用方法站粟,假定我們要代理的對(duì)象是一個(gè)Map徐许,則代碼如下:

Map proxyInstance = (Map) Proxy.newProxyInstance(
                HashMap.class.getClassLoader(),
                new Class[]{Map.class},
                new DynamicInvocationHandler());

之后proxyInstance就可以作為一個(gè)正常的Map對(duì)象進(jìn)行使用了

為了對(duì)生成對(duì)象的屬性做一個(gè)基本的了解,我們先打印一下proxyInstance的實(shí)際類型名稱

System.out.println(proxyInstance.getClass().getName());

得到結(jié)果

com.sun.proxy.$Proxy11

如果使用多了卒蘸,就會(huì)發(fā)現(xiàn)所有的代理類的名稱都是$Proxy加一個(gè)數(shù)字,且包名是com.sun.proxy

當(dāng)我們查看Proxy.newProxyInstance方法時(shí)翻默,會(huì)發(fā)現(xiàn)它返回的其實(shí)是一個(gè)Object對(duì)象

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

而在實(shí)際使用的過程中缸沃,它是可以被直接轉(zhuǎn)型成我們傳入的接口類型,因此可以推測出修械,該proxyInstance對(duì)象的實(shí)際類型肯定是實(shí)現(xiàn)了我們傳入的接口

我們打印一下該類實(shí)現(xiàn)的接口

for (Class intf : proxyInstance.getClass().getInterfaces()) {
    System.out.println(intf.getName());
}

得到結(jié)果

java.util.Map

符合我們之前的推測

接著我們?cè)俅蛴∫幌略擃惖母割?/p>

System.out.println(proxyInstance.getClass().getSuperclass().getName());

得到結(jié)果

java.lang.reflect.Proxy

因此總結(jié)一下趾牧,該proxyInstance對(duì)象有以下3個(gè)屬性
1.繼承了Proxy類
2.實(shí)現(xiàn)了我們傳入的接口
3.以$Proxy+隨機(jī)數(shù)字的命名

那么動(dòng)態(tài)生成代理類的功能究竟是如何實(shí)現(xiàn)的呢?接下去就來看java的源碼
因?yàn)樵创a有點(diǎn)多肯污,所以我只貼出關(guān)鍵的部分

入口自然是Proxy.newProxyInstance方法
其中有2個(gè)部分我們需要關(guān)心

第一部分翘单,類的創(chuàng)建

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

這個(gè)就是實(shí)際生成類的方法吨枉,后面我們會(huì)繼續(xù)深究,先略放一放

第二部分哄芜,實(shí)例的創(chuàng)建

final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
... 
return cons.newInstance(new Object[]{h});

最終對(duì)象的實(shí)例化過程就是通過之前生成的class貌亭,獲取其指定參數(shù)的構(gòu)造函數(shù),并將InvocationHandler對(duì)象傳入

查看constructorParams字段

/** parameter types of a proxy class constructor */
private static final Class<?>[] constructorParams =
        { InvocationHandler.class };

的確就是獲取InvocationHandler對(duì)象的一個(gè)構(gòu)造函數(shù)

回想一下之前類定義的第一條认臊,繼承了Proxy類圃庭,因此我們?nèi)roxy類中找一下

/**
 * Constructs a new {@code Proxy} instance from a subclass
 * (typically, a dynamic proxy class) with the specified value
 * for its invocation handler.
 *
 * @param  h the invocation handler for this proxy instance
 *
 * @throws NullPointerException if the given invocation handler, {@code h},
 *         is {@code null}.
 */
protected Proxy(InvocationHandler h) {
    Objects.requireNonNull(h);
    this.h = h;
}

在該構(gòu)造函數(shù)中就是將參數(shù)h賦值給了成員變量h,這里名稱h可以記一下失晴,在之后的文章中還會(huì)遇到

看完實(shí)例的創(chuàng)建剧腻,讓我們回到更重要的第一部分,類的生成
進(jìn)入getProxyClass0(loader, intfs)方法

/**
 * Generate a proxy class.  Must call the checkProxyAccess method
 * to perform permission checks before calling this.
 */
private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }

    // If the proxy class defined by the given loader implementing
    // the given interfaces exists, this will simply return the cached copy;
    // otherwise, it will create the proxy class via the ProxyClassFactory
    return proxyClassCache.get(loader, interfaces);
}

該方法很簡單涂屁,直接從一個(gè)cache中拿取對(duì)象

查看proxyClassCache對(duì)象

/**
 * a cache of proxy classes
 */
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

該對(duì)象本質(zhì)就是一個(gè)類似于Map的緩存书在,不過使用的是WeakCache,這個(gè)WeakCache本身的特性我們放到另一篇文章中討論拆又,本文專注于Proxy
我們可以看到該緩存的構(gòu)造函數(shù)獲取了2個(gè)Factory儒旬,顧名思義,第一個(gè)是生成key的遏乔,第二個(gè)是生成ProxyClass的义矛,自然我們需要繼續(xù)看第二個(gè)Factory

類的注解如下

/**
* A factory function that generates, defines and returns the proxy class given
* the ClassLoader and array of interfaces.
*/
private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>

這個(gè)就是我們要尋找的負(fù)責(zé)具體生成類的工廠了,查看其apply方法

首先其會(huì)對(duì)傳入的接口類型做一些校驗(yàn)盟萨,包括loader能否加載到傳入的接口凉翻,接口是否實(shí)際上是接口(因?yàn)閿?shù)組的類型是Class),接口是否有重復(fù)

Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
        for (Class<?> intf : interfaces) {
    /*
     * Verify that the class loader resolves the name of this
     * interface to the same Class object.
     */
    Class<?> interfaceClass = null;
    try {
        interfaceClass = Class.forName(intf.getName(), false, loader);
    } catch (ClassNotFoundException e) {
    }
    if (interfaceClass != intf) {
        throw new IllegalArgumentException(
                intf + " is not visible from class loader");
    }
    /*
     * Verify that the Class object actually represents an
     * interface.
     */
    if (!interfaceClass.isInterface()) {
        throw new IllegalArgumentException(
                interfaceClass.getName() + " is not an interface");
    }
    /*
     * Verify that this interface is not a duplicate.
     */
    if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
        throw new IllegalArgumentException(
                "repeated interface: " + interfaceClass.getName());
    }
}

接著設(shè)置類的默認(rèn)access_flag捻激,public final

int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

接著檢查傳入的接口數(shù)組中是否包含非public的接口制轰,如果有,則生成的類需要和該接口處于同一個(gè)package胞谭,且訪問屬性會(huì)去掉public垃杖,只保留final。如果有多個(gè)不同package中的非public接口丈屹,則報(bào)錯(cuò)
(具體原因大家應(yīng)該都可以理解)

/*
 * Record the package of a non-public proxy interface so that the
 * proxy class will be defined in the same package.  Verify that
 * all non-public proxy interfaces are in the same package.
 */
for (Class<?> intf : interfaces) {
    int flags = intf.getModifiers();
    if (!Modifier.isPublic(flags)) {
        accessFlags = Modifier.FINAL;
        String name = intf.getName();
        int n = name.lastIndexOf('.');
        String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
        if (proxyPkg == null) {
            proxyPkg = pkg;
        } else if (!pkg.equals(proxyPkg)) {
            throw new IllegalArgumentException(
                    "non-public interfaces from different packages");
        }
    }
}

如果沒有非public類调俘,則會(huì)使用默認(rèn)的package名,即com.sun.proxy

if (proxyPkg == null) {
    // if no non-public proxy interfaces, use com.sun.proxy package
    proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}

然后獲取一個(gè)靜態(tài)自增的int

/*
 * Choose a name for the proxy class to generate.
 */
long num = nextUniqueNumber.getAndIncrement();

固定的類名前綴

// prefix for all proxy class names
private static final String proxyClassNamePrefix = "$Proxy";

將上面三者組合成最終的類名(回想之前我們打印出的實(shí)例的類名)

String proxyName = proxyPkg + proxyClassNamePrefix + num;

上面這幾個(gè)步驟確定了類的名稱旺垒,但還是皮毛彩库,接下去是生成類的血肉:字節(jié)碼

/*
 * Generate the specified proxy class.
 */
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
        proxyName, interfaces, accessFlags);

具體的探究也先放一下,先看字節(jié)碼轉(zhuǎn)換成具體類的方法

try {
    return defineClass0(loader, proxyName,
            proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
    /*
     * A ClassFormatError here means that (barring bugs in the
     * proxy class generation code) there was some other
     * invalid aspect of the arguments supplied to the proxy
     * class creation (such as virtual machine limitations
     * exceeded).
     */
    throw new IllegalArgumentException(e.toString());
}

而該方法是一個(gè)native的方法先蒋,所以暫時(shí)就無法繼續(xù)探究了骇钦,不過知道了這個(gè)方法后,如果我們自己有需要竞漾,也可以利用這種機(jī)制實(shí)現(xiàn)自己的動(dòng)態(tài)類生成眯搭,后面會(huì)想辦法做一個(gè)demo窥翩,本文就不做探討了

private static native Class<?> defineClass0(ClassLoader loader, String name,
                                                byte[] b, int off, int len);

之前其實(shí)都是開胃菜,現(xiàn)在回到之前生成字節(jié)碼的方法鳞仙,查看方法源碼

public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
    ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
    final byte[] var4 = var3.generateClassFile();
    if (saveGeneratedFiles) {
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                try {
                    int var1 = var0.lastIndexOf(46);
                    Path var2;
                    if (var1 > 0) {
                        Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
                        Files.createDirectories(var3);
                        var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                    } else {
                        var2 = Paths.get(var0 + ".class");
                    }

                    Files.write(var2, var4, new OpenOption[0]);
                    return null;
                } catch (IOException var4x) {
                    throw new InternalError("I/O exception saving generated file: " + var4x);
                }
            }
        });
    }

    return var4;
}

中間if部分的代碼可以先忽略寇蚊,不過我們會(huì)在后面的文章中使用到這部分功能,這里先關(guān)注下面這2行代碼

ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
final byte[] var4 = var3.generateClassFile();

這里讓我們記一下
var0是類名
var1是接口
var3是access_flag
后面我會(huì)盡量將這些varX轉(zhuǎn)換成更實(shí)際的命名繁扎,方便大家理解

之后就是本文的最終的重點(diǎn)幔荒,也是難點(diǎn),即二進(jìn)制字節(jié)碼的實(shí)際生成過程梳玫,包括jvm操作指令爹梁,所以我們需要先對(duì)class文件的結(jié)構(gòu)和jvm操作指令有一個(gè)了解

jvm文檔地址:https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html
下面對(duì)字節(jié)碼的結(jié)構(gòu)簡單地做了個(gè)說明,大部分都是顧名思義

ClassFile {
    u4             magic;//固定的開頭提澎,值為0xCAFEBABE
    u2             minor_version;//版本號(hào)姚垃,用來標(biāo)記class的版本
    u2             major_version;//版本號(hào),用來標(biāo)記class的版本
    u2             constant_pool_count;//靜態(tài)池大小盼忌,是靜態(tài)池對(duì)象數(shù)量+1
    cp_info        constant_pool[constant_pool_count-1];//靜態(tài)池對(duì)象积糯,有效索引是1 ~ count-1
    u2             access_flags;//public、final等描述
    u2             this_class;//當(dāng)前類的信息
    u2             super_class;//父類的信息
    u2             interfaces_count;//接口數(shù)量
    u2             interfaces[interfaces_count];//接口對(duì)象
    u2             fields_count;//字段數(shù)量
    field_info     fields[fields_count];//字段對(duì)象
    u2             methods_count;//方法數(shù)量
    method_info    methods[methods_count];//方法對(duì)象
    u2             attributes_count;//屬性數(shù)量
    attribute_info attributes[attributes_count];//屬性對(duì)象
}

為了不成為一篇枯燥的文檔翻譯谦纱,并且盡快進(jìn)入Proxy的源碼看成,這里并不會(huì)對(duì)每一個(gè)部分做特別詳細(xì)的說明,以把握整體為主

接下去我們就可以進(jìn)入generateClassFile()方法了

首先把握整體跨嘉,我們先跳過一部分細(xì)節(jié)代碼川慌,先看下面這部分(這里我做了一個(gè)可讀性的變量名修改)

注意對(duì)照著Class的字節(jié)結(jié)構(gòu)來看

最終輸出的字節(jié)流

ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
DataOutputStream data = new DataOutputStream(byteStream);

寫入固定開頭magic,這里-889275714就是對(duì)應(yīng)0xCAFEBABE

data.writeInt(-889275714);

寫入版本號(hào)

data.writeShort(0);//minor_version
data.writeShort(49);//major_version

寫入常量池,這里cp就是指constant pool

this.cp.write(data);

這里我們需要進(jìn)入cp的write方法看一下,也先不要糾結(jié)Entry的細(xì)節(jié)跋理,我們還是先把握整體

public void write(OutputStream var1) throws IOException {
    DataOutputStream var2 = new DataOutputStream(var1);
    /**
     * 這里寫入cp的大小,注意size()+1小压,可以和之前Class結(jié)構(gòu)中的constant_pool_count對(duì)應(yīng)
     */
    var2.writeShort(this.pool.size() + 1);
    Iterator var3 = this.pool.iterator();
    /**
     * 遍歷cp中的對(duì)象,寫入詳細(xì)信息,對(duì)應(yīng)Class結(jié)構(gòu)中的cp_info
     */
    while(var3.hasNext()) {
        ProxyGenerator.ConstantPool.Entry var4 = (ProxyGenerator.ConstantPool.Entry)var3.next();
        var4.write(var2);
    }
}

接著我們回到外層方法,繼續(xù)往下看

寫入access_flag

data.writeShort(this.accessFlags);

寫入當(dāng)前類的信息

data.writeShort.writeShort(this.cp.getClass(dotToSlash(this.className)));

寫入父類的信息(回想類的屬性第一條蚓胸,繼承了Proxy類)

data.writeShort.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));

寫入接口數(shù)量

data.writeShort.writeShort(this.interfaces.length);

遍歷接口,寫入接口信息

Class[] interfaces = this.interfaces;
int interfaceLength = interfaces.length;
for (int i = 0; i < interfaceLength; ++i) {
    Class intf = interfaces[i];
    data.writeShort(this.cp.getClass(dotToSlash(intf.getName())));
}

寫入字段數(shù)量

data.writeShort(this.fields.size());

遍歷字段除师,寫入字段信息

fieldInerator = this.fields.iterator();
while(fieldInerator.hasNext()) {
    ProxyGenerator.FieldInfo fieldInfo = (ProxyGenerator.FieldInfo) fieldInerator.next();
    fieldInfo.write(data);
}

寫入方法數(shù)量

data.writeShort(this.methods.size());

遍歷方法赢织,寫入方法信息

methodIterator = this.methods.iterator();
while(methodIterator.hasNext()) {
    ProxyGenerator.MethodInfo methodInfo = (ProxyGenerator.MethodInfo) methodIterator.next();
    methodInfo.write(data);
}

因?yàn)樵擃悰]有特別的attribute,因此attribute數(shù)量直接寫0

data.writeShort(0);

正和之前的類結(jié)構(gòu)完全一一對(duì)應(yīng)馍盟,此時(shí)我們對(duì)proxy所做的事情就有了一個(gè)整體的把握


了解了整體之后,下面再深入介紹一下字節(jié)碼中部分對(duì)象的具體格式茧吊,為后面進(jìn)一步看Proxy的源碼做一些準(zhǔn)備
為了更好地理解下面的內(nèi)容贞岭,我們先定義一個(gè)簡單的類Test.java

public class Test implements TestInt {
    private int field = 1;

    public int add(int a, int b) {
        return a + b;
    }
}

interface TestInt {
}

生成.class文件

javac Test.java

查看.class文件

javap -v Test.class

得到結(jié)果

Classfile /Users/tianjiyuan/Documents/jvm/Test.class
  Last modified 2020-7-3; size 292 bytes
  MD5 checksum 1afecf9ea44088238bc8aa9804b28208
  Compiled from "Test.java"
public class Test implements TestInt
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#16         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#17         // Test.field:I
   #3 = Class              #18            // Test
   #4 = Class              #19            // java/lang/Object
   #5 = Class              #20            // TestInt
   #6 = Utf8               field
   #7 = Utf8               I
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               add
  #13 = Utf8               (II)I
  #14 = Utf8               SourceFile
  #15 = Utf8               Test.java
  #16 = NameAndType        #8:#9          // "<init>":()V
  #17 = NameAndType        #6:#7          // field:I
  #18 = Utf8               Test
  #19 = Utf8               java/lang/Object
  #20 = Utf8               TestInt
{
  public Test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iconst_1
         6: putfield      #2                  // Field field:I
         9: return
      LineNumberTable:
        line 1: 0
        line 2: 4

  public int add(int, int);
    descriptor: (II)I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: iload_1
         1: iload_2
         2: iadd
         3: ireturn
      LineNumberTable:
        line 5: 0
}
SourceFile: "Test.java"

我們先看下面這3個(gè)部分正對(duì)應(yīng)minor_version八毯,major_version,access_flags

minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER

接著看Constant Pool

Constant pool:
   #1 = Methodref          #4.#16         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#17         // Test.field:I
   #3 = Class              #18            // Test
   ...
   #6 = Utf8               field
   ...

  #16 = NameAndType        #8:#9          // "<init>":()V

其中有如下幾種類型

Methodref :方法的引用
Fieldref:字段的引用
Class :類的引用
Utf8 :字符串的引用
NameAndType 類型的描述

下面依據(jù)jvm文檔瞄桨,一個(gè)一個(gè)介紹

Class結(jié)構(gòu)

CONSTANT_Class_info {
    u1 tag;
    u2 name_index;
}

表示一個(gè)類的引用
tag:表示自身在常量池中的索引
name_index:必須是常量池中的有效索引话速,用來表示類的名字
例如

#3 = Class              #18            // Test

tag = 3,表示自身索引為3

name_index = 18芯侥,表示名字的索引是18

此時(shí)我們查看#18泊交,即這個(gè)類的名字是Test

#18 = Utf8               Test

Field、Method柱查、Interface結(jié)構(gòu)

文檔中這3者是放在一起的

CONSTANT_Fieldref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}

CONSTANT_Methodref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}

CONSTANT_InterfaceMethodref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}

表示一個(gè)字段廓俭、方法、接口方法的引用

tag:表示自身在常量池中的索引
class_index:表示常量池中的一個(gè)有效索引
  如果是Methodref_info必須是Class類型的
  如果是InterfaceMethodref_info則必須是一個(gè)Interface
  如果是Fieldref_info則可以是Class或者是Interface
name_and_type_index:表示常量池中的一個(gè)有效索引(表示方法的名字唉工、返回類型研乒、參數(shù))
  如果是Fieldref_info,則必須是一個(gè)對(duì)字段的描述淋硝,否則必須是一個(gè)對(duì)方法的描述

例如

#1 = Methodref          #4.#16         // java/lang/Object."<init>":()V

tag = 1雹熬,表示自身索引為1
class_index = 4,表示類型是索引為4的類
name_and_type_index = 16谣膳,表示方法的描述為索引16

查看4和16

   #4 = Class              #19            // java/lang/Object
  #16 = NameAndType        #8:#9          // "<init>":()V

即表示這個(gè)方法是Object類中的構(gòu)造函數(shù)

NameAndType結(jié)構(gòu)

CONSTANT_NameAndType_info {
    u1 tag;
    u2 name_index;
    u2 descriptor_index;
}

用來表示一個(gè)方法或者字段竿报,其中不包括該字段或方法所屬的類

tag:表示自身常量池的索引
name_index:常量池中的一個(gè)有效索引,必須是Utf8類型(表示方法或字段的名字)
descriptor_index:常量池中的一個(gè)有效索引继谚,必須是Utf8類型(表示方法的返回類型和參數(shù))

例如

#16 = NameAndType        #8:#9          // "<init>":()V

tag = 16
name_index = 8
descriptor_index = 9

查看索引8和9

   #8 = Utf8               <init>
   #9 = Utf8               ()V

方法名為<init>表示構(gòu)造函數(shù)烈菌,參數(shù)0個(gè),返回值為void

UTF-8結(jié)構(gòu)

CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

表示一個(gè)字符串常量

tag:表示自身在常量池中的索引
length:表示byte數(shù)組的長度
bytes[length]:表示具體數(shù)據(jù)內(nèi)容
這個(gè)部分其實(shí)還有很多細(xì)節(jié)犬庇,不過這里就不展開了僧界,有興趣的可以自行查看jvm文檔,后面會(huì)有文章詳細(xì)分析

常量池的內(nèi)容就介紹到這里臭挽,接下去我們還需要看下類結(jié)構(gòu)的其他成員

this_class捂襟,必須是一個(gè)有效的常量池索引,需要是CONSTANT_Class_info類型的
super_class欢峰,必須是一個(gè)有效的常量池索引葬荷,需要是CONSTANT_Class_info類型的或者為0,表示沒有父類
interfaces_count纽帖,接口數(shù)量宠漩,一個(gè)int值
interfaces[],接口數(shù)組懊直,數(shù)組中的值必須是一個(gè)常量池的有效索引扒吁,需要是CONSTANT_Class_info類型
fields_count,字段數(shù)量

fields[]室囊,字段數(shù)組雕崩,數(shù)組中的值都是field_info結(jié)構(gòu)

field_info {
    u2             access_flags;//access_flag
    u2             name_index;//常量池中的一個(gè)有效索引魁索,必須是Utf8類型(表示方法或字段的名字)
    u2             descriptor_index;//常量池中的一個(gè)有效索引,必須是Utf8類型(表示字段的描述)
    u2             attributes_count;//跳過盼铁,本文不涉及
    attribute_info attributes[attributes_count];//跳過粗蔚,本文不涉及
}

methods_count,方法數(shù)量
methods[]饶火,方法數(shù)組鹏控,結(jié)構(gòu)如下

method_info {
    u2             access_flags;//access_flag
    u2             name_index;//常量池中的一個(gè)有效索引,必須是Utf8類型(表示方法或字段的名字)
    u2             descriptor_index;//常量池中的一個(gè)有效索引肤寝,必須是Utf8類型(表示方法的描述)
    u2             attributes_count;//屬性數(shù)量
    attribute_info attributes[attributes_count];//屬性的具體內(nèi)容
}

class文件的一些基本結(jié)構(gòu)就介紹到這里当辐,接下去我們進(jìn)一步深入了解class的各種結(jié)構(gòu)究竟是怎么被構(gòu)造的

回到generateClassFile()方法的開頭

第一部分,Object方法的預(yù)處理

this.addProxyMethod(hashCodeMethod, Object.class);
this.addProxyMethod(equalsMethod, Object.class);
this.addProxyMethod(toStringMethod, Object.class);

首先無論是什么類醒陆,都是繼承自O(shè)bject的瀑构,因此Object中的方法是一定需要的
注意,這里addProxyMethod并非直接寫字節(jié)碼了刨摩,而是做了一些預(yù)處理
我們先看下3個(gè)方法中的第一個(gè)參數(shù)是個(gè)啥
在靜態(tài)構(gòu)造函數(shù)中寺晌,可以看到的確就是Object的3個(gè)方法

static {
    try {
        hashCodeMethod = Object.class.getMethod("hashCode");
        equalsMethod = Object.class.getMethod("equals", Object.class);
        toStringMethod = Object.class.getMethod("toString");
    } catch (NoSuchMethodException var1) {
        throw new NoSuchMethodError(var1.getMessage());
    }
}

我們進(jìn)入addProxyMethod方法,這里對(duì)變量名做了一個(gè)可讀性處理

String methodName = method.getName();
Class[] paramTypes = method.getParameterTypes();
Class returnType = method.getReturnType();
Class[] exceptionTypes = method.getExceptionTypes();
String cacheKey = methodName + getParameterDescriptors(paramTypes);
Object cache = (List)this.proxyMethods.get(cacheKey);
...
((List) cache).add(new ProxyGenerator.ProxyMethod(methodName, paramTypes, returnType, exceptionTypes, targetClass));

概括而言澡刹,就是根據(jù)方法的各個(gè)要素生成一個(gè)ProxyMethod對(duì)象呻征,然后將其加入一個(gè)緩存List中

接著我們進(jìn)入ProxyMethod的構(gòu)造函數(shù)查看

private ProxyMethod(String var2, Class<?>[] var3, Class<?> var4, Class<?>[] var5, Class<?> var6) {
    this.methodName = var2;
    this.parameterTypes = var3;
    this.returnType = var4;
    this.exceptionTypes = var5;
    this.fromClass = var6;
    this.methodFieldName = "m" + ProxyGenerator.this.proxyMethodCount++;
}

值得注意的是,在ProxyMethod的構(gòu)造函數(shù)中有2個(gè)字段罢浇,在后面會(huì)有用到
一個(gè)是methodName陆赋,表示方法名
另外一個(gè)是以m+遞增數(shù)字的methodFieldName,表示該方法在最終生成的類中的Method類型的字段的名稱

第二部分嚷闭,接口方法的預(yù)處理

Class[] interfaces = this.interfaces;
int interfaceLength = interfaces.length;

int i;
Class clazz;
for(i = 0; i < interfaceLength; ++i) {
    clazz = interfaces[i];
    Method[] methods = clazz.getMethods();
    int methodLength = methods.length;

    for(int j = 0; j < methodLength; ++j) {
        Method m = methods[j];
        this.addProxyMethod(m, clazz);
    }
}

既然生成的類實(shí)現(xiàn)了傳入的接口攒岛,因此循環(huán)接口,將接口的方法要素添加到proxyMethods中胞锰,和之前處理Object的方法一樣

第三部分灾锯,字段和方法的字節(jié)碼寫入

Iterator iterator;
try {
    this.methods.add(this.generateConstructor());
    iterator = this.proxyMethods.values().iterator();
    while(iterator.hasNext()) {
        list = (List) iterator.next();
        listIterator = list.iterator();

        while(listIterator.hasNext()) {
            ProxyGenerator.ProxyMethod proxyMethod = (ProxyGenerator.ProxyMethod) listIterator.next();
            this.fields.add(new ProxyGenerator.FieldInfo(proxyMethod.methodFieldName, "Ljava/lang/reflect/Method;", 10));
            this.methods.add(proxyMethod.generateMethod());
        }
    }

    this.methods.add(this.generateStaticInitializer());
} catch (IOException var10) {
    throw new InternalError("unexpected I/O Exception", var10);
}

這里的第一行,正是寫入構(gòu)造器的字節(jié)碼嗅榕,這一部分因?yàn)樯婕暗絡(luò)vm的執(zhí)行指令顺饮,我們放到之后再詳細(xì)看,所以這里先跳過

this.methods.add(this.generateConstructor());

直接看后面的while循環(huán)凌那,就是遍歷之前我們添加的Object和接口定義的方法兼雄,然后生成相應(yīng)的字段字節(jié)碼和方法字節(jié)碼

while(listIterator.hasNext()) {
    ProxyGenerator.ProxyMethod proxyMethod = (ProxyGenerator.ProxyMethod) listIterator.next();
    this.fields.add(new ProxyGenerator.FieldInfo(proxyMethod.methodFieldName, "Ljava/lang/reflect/Method;", 10));
    this.methods.add(proxyMethod.generateMethod());
}

下面先詳細(xì)看看字段字節(jié)碼的細(xì)節(jié)

第四部分,字段字節(jié)碼

this.fields.add(new ProxyGenerator.FieldInfo(proxyMethod.methodFieldName, "Ljava/lang/reflect/Method;", 10));

FieldInfo構(gòu)造函數(shù)中
第一個(gè)參數(shù)proxyMethod.methodFieldName是我們?cè)谥疤岬降膍+遞增數(shù)字生成的methodFieldName
第二個(gè)參數(shù)是類型描述
第三個(gè)參數(shù)是accessFlag帽蝶,10表示private static (Modifier.PRIVATE | Modifier.STATIC)

進(jìn)入構(gòu)造函數(shù)看一下

public FieldInfo(String var2, String var3, int var4) {
    this.name = var2;
    this.descriptor = var3;
    this.accessFlags = var4;
    ProxyGenerator.this.cp.getUtf8(var2);
    ProxyGenerator.this.cp.getUtf8(var3);
}

回想前文中的field_info類型(忽略attributes)

field_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
}

this.name赦肋、this.descriptor、this.accessFlags正好和field_info中的結(jié)構(gòu)一一對(duì)應(yīng)

同時(shí),由于name_index和descriptor_index都是常量池中的一個(gè)索引金砍,因此需要將其寫入常量池
這里的cp就是指Constant pool局蚀,把methodFieldName和descriptor寫入到靜態(tài)池

ProxyGenerator.this.cp.getUtf8(var2);
ProxyGenerator.this.cp.getUtf8(var3);

之后我們可以直接看,F(xiàn)ieldInfo中的write方法恕稠,這就是最后寫入的字節(jié)的方法

public void write(DataOutputStream var1) throws IOException {
    var1.writeShort(this.accessFlags);
    var1.writeShort(ProxyGenerator.this.cp.getUtf8(this.name));
    var1.writeShort(ProxyGenerator.this.cp.getUtf8(this.descriptor));
    var1.writeShort(0);
}

對(duì)照之前的field_info
第一個(gè)寫入access_flags
接著寫入name_index和descriptor_index,值都是索引

最后因?yàn)閍ttribute數(shù)量是0扶欣,因此直接寫0

此時(shí)一個(gè)完整的字段結(jié)構(gòu)就寫入完畢了

接著我們回頭查看ProxyGenerator.this.cp.getUtf8方法鹅巍,看看索引是如何確定的

public short getUtf8(String var1) {
    if (var1 == null) {
        throw new NullPointerException();
    } else {
        return this.getValue(var1);
    }
}

接續(xù)查看getValue方法

private short getValue(Object var1) {
    Short var2 = (Short)this.map.get(var1);
    if (var2 != null) {
        return var2;
    } else if (this.readOnly) {
        throw new InternalError("late constant pool addition: " + var1);
    } else {
        short var3 = this.addEntry(new ProxyGenerator.ConstantPool.ValueEntry(var1));
        this.map.put(var1, new Short(var3));
        return var3;
    }
}

這里用map做了一個(gè)緩存,key就是需要寫入的字段料祠,value就是索引值骆捧,如果命中了map,則直接返回value

如果沒有命中緩存髓绽,則需要addEntry
查看addEntry方法

private short addEntry(ProxyGenerator.ConstantPool.Entry var1) {
    this.pool.add(var1);
    if (this.pool.size() >= 65535) {
        throw new IllegalArgumentException("constant pool size limit exceeded");
    } else {
        return (short)this.pool.size();
    }
}

即將生成的entry添加入pool敛苇,并返回當(dāng)前pool的大小,也就是該常量在池中的索引

回想一下cp的結(jié)構(gòu)顺呕,其中cp數(shù)量是count+1枫攀,cp數(shù)組有效索引是從1開始的,因此這里直接返回pool的size株茶,而不是size-1

因此
ProxyGenerator.this.cp.getUtf8()方法做了2件事情
1.將值寫入常量池
2.返回該值在常量池中的索引

到這里来涨,字段的相關(guān)內(nèi)容就結(jié)束了,接下去我們查看方法的字節(jié)碼

第五部分启盛,方法字節(jié)碼

先看之前while循環(huán)中的代碼

this.methods.add(proxyMethod.generateMethod());

查看generateMethod方法

因?yàn)榉椒ǖ慕Y(jié)構(gòu)體其實(shí)包含兩個(gè)大部分蹦掐,第一部分是和field_info一樣的基礎(chǔ)屬性,第二部分是方法的執(zhí)行體僵闯,之后會(huì)單獨(dú)介紹方法的執(zhí)行體是怎么寫入的卧抗,這里我們先關(guān)注方法的基本結(jié)構(gòu)

String var1 = ProxyGenerator.getMethodDescriptor(this.parameterTypes, this.returnType);
ProxyGenerator.MethodInfo var2 = ProxyGenerator.this.new MethodInfo(this.methodName, var1, 17);

這里第一行是獲取方法的描述,類似于 ()V 描述方法的參數(shù)和返回參數(shù)鳖粟,這里()V表示獲取0個(gè)參數(shù)社裆,返回為void的方法

第二行就生成一個(gè)MethodInfo對(duì)象,查看其構(gòu)造函數(shù)

public MethodInfo(String var2, String var3, int var4) {
    this.name = var2;
    this.descriptor = var3;
    this.accessFlags = var4;
    ProxyGenerator.this.cp.getUtf8(var2);
    ProxyGenerator.this.cp.getUtf8(var3);
    ProxyGenerator.this.cp.getUtf8("Code");
    ProxyGenerator.this.cp.getUtf8("Exceptions");
}

同樣回顧前文的method_info

method_info {
    u2             access_flags;//access_flag
    u2             name_index;//常量池中的一個(gè)有效索引牺弹,必須是Utf8類型(表示方法或字段的名字)
    u2             descriptor_index;//常量池中的一個(gè)有效索引浦马,必須是Utf8類型(表示方法的描述)
    u2             attributes_count;//屬性數(shù)量
    attribute_info attributes[attributes_count];//屬性的具體內(nèi)容
}

和field_info不同,除了基礎(chǔ)的access_flags张漂、name_index晶默、descriptor_index外,MethodInfo的構(gòu)造函數(shù)還寫入了2個(gè)額外的常量池對(duì)象:Code和Exceptions航攒,表示2種attributes

Code表示執(zhí)行代碼

Exceptions表示方法會(huì)拋出的異常

同樣磺陡,我們接著就查看MethodInfo中的write方法

寫入access_flags、name_index、descriptor_index

var1.writeShort(this.accessFlags);
var1.writeShort(ProxyGenerator.this.cp.getUtf8(this.name));
var1.writeShort(ProxyGenerator.this.cp.getUtf8(this.descriptor));

寫入屬性的數(shù)量

var1.writeShort(2);

此時(shí)我們就需要看下attributes的基礎(chǔ)結(jié)構(gòu)了

attribute_info {
    u2 attribute_name_index;//名字在常量池的索引
    u4 attribute_length;//attribute的字節(jié)長度
    u1 info[attribute_length];//attribute的實(shí)際數(shù)據(jù)
}

這里我們就先了解2種具體的attribute币他,一個(gè)是Code坞靶,一個(gè)是Exception,正是之前在構(gòu)造函數(shù)中看到的
Code的結(jié)構(gòu)

Code_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 max_stack;
    u2 max_locals;
    u4 code_length;
    u1 code[code_length];
    u2 exception_table_length;
    {   u2 start_pc;
        u2 end_pc;
        u2 handler_pc;
        u2 catch_type;
    } exception_table[exception_table_length];
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}

此時(shí)我們對(duì)應(yīng)著代碼來看

首先寫入attribute_name_index

var1.writeShort(ProxyGenerator.this.cp.getUtf8("Code"));

寫入數(shù)據(jù)長度attribute_length蝴悉,這里的12和8會(huì)在本文后面解釋

var1.writeInt(12 + this.code.size() + 8 * this.exceptionTable.size());

寫入棧深max_stack和max_locals本地變量數(shù)量彰阴,這2個(gè)值在下一篇文章的generateMethod()方法詳細(xì)介紹中涉及到,這里就先不展開了

var1.writeShort(this.maxStack);
var1.writeShort(this.maxLocals);

寫入方法執(zhí)行體字節(jié)的長度code_length和方法執(zhí)行體具體字節(jié)code[code_length]拍冠,這2部分也會(huì)在generateMethod()方法詳細(xì)介紹中涉及到尿这,這里就先不展開了

var1.writeInt(this.code.size());
this.code.writeTo(var1);

此時(shí)我們看到寫入max_stack、max_locals庆杜、code_length時(shí)射众,字段的類型分別是short、short晃财、integer叨橱,加起共8個(gè)字節(jié)

寫入方法會(huì)拋出的異常數(shù)量exception_table_length

var1.writeShort(this.exceptionTable.size());

這個(gè)時(shí)候exception_table_length是一個(gè)short類型,加上之前的8個(gè)字節(jié)断盛,一共是10個(gè)字節(jié)

寫入異常的具體結(jié)構(gòu)

Iterator var2 = this.exceptionTable.iterator();

while(var2.hasNext()) {
    ProxyGenerator.ExceptionTableEntry var3 = (ProxyGenerator.ExceptionTableEntry)var2.next();
    var1.writeShort(var3.startPc);
    var1.writeShort(var3.endPc);
    var1.writeShort(var3.handlerPc);
    var1.writeShort(var3.catchType);
}

每一個(gè)異常都有4個(gè)字段罗洗,start_pc、end_pc郑临、handler_pc栖博、catch_type,都是short類型厢洞,因此一個(gè)Exception就會(huì)有8個(gè)字節(jié)仇让,這個(gè)8正對(duì)應(yīng)了上面attribute_length中的8

最后寫入attributes自身的attributes_count,因?yàn)闆]有躺翻,所以直接寫0

var1.writeShort(0);

這個(gè)數(shù)量是一個(gè)short類型丧叽,加上之前累積的10個(gè)字節(jié),一共12個(gè)字節(jié)公你,對(duì)應(yīng)了attribute_length中的12

接下去看Exception

Exception結(jié)構(gòu)

Exceptions_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 number_of_exceptions;
    u2 exception_index_table[number_of_exceptions];
}

這個(gè)結(jié)構(gòu)相對(duì)就簡單了很多踊淳,下面對(duì)應(yīng)代碼來看

先寫入常量池的索引attribute_name_index

var1.writeShort(ProxyGenerator.this.cp.getUtf8("Exceptions"));

寫入attribute長度attribute_length,這里的2個(gè)2也在后面解釋陕靠,不過我想大家自己也能想到分別代表什么了吧

var1.writeInt(2 + 2 * this.declaredExceptions.length);

寫入異常數(shù)量number_of_exceptions迂尝,類型是short,對(duì)應(yīng)了第一個(gè)2

var1.writeShort(this.declaredExceptions.length);

寫入具體的異常在常量池中的索引剪芥,每一個(gè)數(shù)據(jù)都是一個(gè)short垄开,對(duì)應(yīng)了第二個(gè)2

var1.writeShort(this.declaredExceptions.length);
short[] var6 = this.declaredExceptions;
int var7 = var6.length;

for(int var4 = 0; var4 < var7; ++var4) {
    short var5 = var6[var4];
    var1.writeShort(var5);
}

以上,字段和方法的寫入就基本解析就完成了
之后將探究generateMethod()方法最復(fù)雜的執(zhí)行體內(nèi)容

因?yàn)榉椒ǖ淖止?jié)碼涉及到了jvm的操作指令税肪,因此我們先做一個(gè)基礎(chǔ)性的了解

原文地址:https://dzone.com/articles/introduction-to-java-bytecode
jvm指令文檔:https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html
文中開始介紹的堆溉躲、棧榜田、方法區(qū)等概念這里就不詳細(xì)描述了,主要看它后面對(duì)一些簡單方法的字節(jié)碼的解析
首先我們定義一個(gè)簡單的類

public class Test {
    public static void main(String[] args) {
        int a = 1;
        int b = 2;
        int c = a + b;
    }
}

編譯生成Test.class

javac Test.java

查看字節(jié)碼結(jié)構(gòu)

javap -v Test.class

我們關(guān)注其中的main方法部分

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: iconst_1
         1: istore_1
         2: iconst_2
         3: istore_2
         4: iload_1
         5: iload_2
         6: iadd
         7: istore_3
         8: return
      LineNumberTable:
        line 3: 0
        line 4: 2
        line 5: 4
        line 6: 8

其中的Code正是方法的執(zhí)行體锻梳,下面按照順序圖解具體操作

iconst_1:將常量1壓入操作棧

img

istore_1:彈出棧頂?shù)牟僮鲾?shù)箭券,存入棧的本地變量數(shù)組的索引1,也就是變量a

img

iconst_2:將常量2壓入操作棧

img

istore_2:彈出棧頂?shù)牟僮鲾?shù)疑枯,存入棧的本地變量數(shù)組的索引2辩块,也就是變量b

img

iload_1:從本地變量索引1種讀取值,并壓入操作棧

img

iload_2:從本地變量索引2種讀取值荆永,并壓入操作棧

img

iadd:彈出棧頂?shù)?個(gè)操作數(shù)庆捺,相加后將結(jié)果壓入操作棧

img

istore_3:彈出棧頂?shù)牟僮鲾?shù),存入棧的本地變量數(shù)組的索引3屁魏,也就是變量c

img

return:從方法返回

如果我們?cè)陬愔卸x一個(gè)方法

public class Test {
    public static void main(String[] args) {
        int a = 1;
        int b = 2;
        int c = calc(a, b);
    }
    static int calc(int a, int b) {
        return (int) Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
    }
}

得到的字節(jié)碼如下,這次我把部分Constant pool也展示在下面

Constant pool:
   #1 = Methodref          #8.#19         // java/lang/Object."<init>":()V
   #2 = Methodref          #7.#20         // Test.calc:(II)I
   #3 = Double             2.0d
   #5 = Methodref          #21.#22        // java/lang/Math.pow:(DD)D
   #6 = Methodref          #21.#23        // java/lang/Math.sqrt:(D)D
public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: iconst_1
         1: istore_1
         2: iconst_2
         3: istore_2
         4: iload_1
         5: iload_2
         6: invokestatic  #2                  // Method calc:(II)I
         9: istore_3
        10: return
      LineNumberTable:
        line 3: 0
        line 4: 2
        line 5: 4
        line 6: 10

  static int calc(int, int);
    descriptor: (II)I
    flags: ACC_STATIC
    Code:
      stack=6, locals=2, args_size=2
         0: iload_0
         1: i2d
         2: ldc2_w        #3                  // double 2.0d
         5: invokestatic  #5                  // Method java/lang/Math.pow:(DD)D
         8: iload_1
         9: i2d
        10: ldc2_w        #3                  // double 2.0d
        13: invokestatic  #5                  // Method java/lang/Math.pow:(DD)D
        16: dadd
        17: invokestatic  #6                  // Method java/lang/Math.sqrt:(D)D
        20: d2i
        21: ireturn
      LineNumberTable:
        line 8: 0

這里我們主要看一下一些新出現(xiàn)的操作指令
在main方法中捉腥,編號(hào)6
invokestatic #2:調(diào)用靜態(tài)方法氓拼,方法在Constant Pool中索引為2,表示Test.calc方法(這里特別注意抵碟,調(diào)用的方法目標(biāo)必須是常量池中的一個(gè)有效索引)
在cacl方法中
i2d:將int類型的轉(zhuǎn)換成double類型的
ldc2_w:將long型或者double型(思考一下為何是這2種類型放在同一個(gè)操作指令中)從靜態(tài)池中壓入棧
dadd:將double相加
d2i:將double類型轉(zhuǎn)換成int類型
ireturn:返回一個(gè)int

將上面的jvm指令結(jié)合java代碼桃漾,就可以初步理解每一行java代碼究竟是如何被jvm執(zhí)行的了

接下去我們可以通過Proxy的代碼結(jié)合實(shí)際來看看

方法還是generateClassFile()
在之前“第三部分字節(jié)與方法字節(jié)碼的寫入”中,有提到

這里的第一行拟逮,正是寫入構(gòu)造器的字節(jié)碼撬统,這一部分因?yàn)樯婕暗絡(luò)vm的執(zhí)行指令,我們放到下篇文章再詳細(xì)看敦迄,所以這里先跳過

this.methods.add(this.generateConstructor());

此時(shí)我們就可以詳細(xì)看下generateConstructor方法究竟干了什么

private ProxyGenerator.MethodInfo generateConstructor() throws IOException {
    ProxyGenerator.MethodInfo var1 = new ProxyGenerator.MethodInfo("<init>", "(Ljava/lang/reflect/InvocationHandler;)V", 1);
    DataOutputStream var2 = new DataOutputStream(var1.code);
    this.code_aload(0, var2);
    this.code_aload(1, var2);
    var2.writeByte(183);
    var2.writeShort(this.cp.getMethodRef("java/lang/reflect/Proxy", "<init>", "(Ljava/lang/reflect/InvocationHandler;)V"));
    var2.writeByte(177);
    var1.maxStack = 10;
    var1.maxLocals = 2;
    var1.declaredExceptions = new short[0];
    return var1;
}

特別注意的是恋追,這里的var2表示的是方法的執(zhí)行體部分,也就是在上一篇文章中罚屋,我們提到的方法attributes中的一個(gè):Code

接下一行一行分析

初始化MethodInfo對(duì)象苦囱,3個(gè)參數(shù)分別是,方法名脾猛、方法描述撕彤、access_flag,1表示public(參見Modifier.java)

因?yàn)槭菢?gòu)造函數(shù)猛拴,所以方法名為<init>

方法的描述表示羹铅,該方法獲取一個(gè)java.lang.reflect.InvocationHandler類型的參數(shù),返回值為V(表示void)

方法的access_flag為1愉昆,表示public

ProxyGenerator.MethodInfo var1 = new ProxyGenerator.MethodInfo("<init>", 
                                                           "(Ljava/lang/reflect/InvocationHandler;)V", 1);

在Code中寫入aload_0和aload_1操作指令

this.code_aload(0, var2);
this.code_aload(1, var2);

在Code中寫入183號(hào)操作指令职员,查文檔得:invokespecial

調(diào)用實(shí)例方法,特別用來處理父類的構(gòu)造函數(shù)

var2.writeByte(183);

在Code中寫入需要調(diào)用的方法名和方法的參數(shù)

注意撼唾,這里的方法是通過this.cp.getMethodRef方法得到的廉邑,也就是說哥蔚,這里寫入的最終數(shù)據(jù),其實(shí)是一個(gè)符合該方法描述的常量池中的一個(gè)有效索引(這部分知識(shí)可以參看之前的3篇文章)

var2.writeShort(this.cp.getMethodRef("java/lang/reflect/Proxy", "<init>", 
                                     "(Ljava/lang/reflect/InvocationHandler;)V"));

在Code中寫入177號(hào)指令蛛蒙,查文檔得:return

返回void

var2.writeByte(177);

和上一篇文章中提到的一樣糙箍,最后還需要寫入棧深和本地變量數(shù)量,以及方法會(huì)拋出的異常數(shù)量牵祟,因?yàn)闃?gòu)造函數(shù)不主動(dòng)拋出異常深夯,所以異常數(shù)量直接為0

注意這里并非是直接writeByte,而是對(duì)MethodInfo的屬性做了一個(gè)設(shè)置诺苹,這部分的字節(jié)碼依然會(huì)在MethodInfo的write方法中寫入咕晋,參見上一篇文章

var1.maxStack = 10;
var1.maxLocals = 2;
var1.declaredExceptions = new short[0];

到此,一個(gè)構(gòu)造方法的結(jié)構(gòu)就完成了

此時(shí)我們總結(jié)一下該構(gòu)造函數(shù)的結(jié)構(gòu)收奔,當(dāng)我們查看class文件的結(jié)構(gòu)時(shí)掌呜,應(yīng)當(dāng)是下面這種結(jié)構(gòu)

aload_0;
aload_1;
invokespecial  #x  //這里x對(duì)應(yīng)Constant pool中構(gòu)造函數(shù)的編號(hào)
return;

驗(yàn)證一下,我們建立一個(gè)類

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

public class Test extends Proxy {
    protected TestClass(InvocationHandler h) {
        super(h);
    }
}

查看其字節(jié)碼

protected Test(java.lang.reflect.InvocationHandler);
    descriptor: (Ljava/lang/reflect/InvocationHandler;)V
    flags: ACC_PROTECTED
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: invokespecial #1                  // Method java/lang/reflect/Proxy."<init>":(Ljava/lang/reflect/InvocationHandler;)V
         5: return
      LineNumberTable:
        line 6: 0
        line 7: 5

正和我們之前總結(jié)的一模一樣

結(jié)合之前的一些jvm指令的基本描述坪哄,我們就可以對(duì)method_info的整體結(jié)構(gòu)有了更深入的了解

此時(shí)我們先停一停质蕉,思考這樣一個(gè)問題:
如果由我們自己通過代碼來定義一個(gè)Proxy的動(dòng)態(tài)類,我們?cè)撊绾稳ザx翩肌?
首先回顧一下第一篇文章中提到代理類的3個(gè)特性
1.繼承了Proxy類
2.實(shí)現(xiàn)了我們傳入的接口
3.以$Proxy+隨機(jī)數(shù)字的命名
假定我們現(xiàn)在定義一個(gè)簡單的接口模暗,并生成該接口的代理類
接口定義

public interface TestInterface {
    int put(String a);
}

滿足3個(gè)特性的代理類初步定義如下

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

public class $Proxy11 extends Proxy implements TestInterface {
    protected $Proxy11(InvocationHandler h) {
        super(h);
    }

    @Override
    public int put(String a) {
        return 0;
    }
}

然而在這種情況下h的代理是無法生效的,因?yàn)閜ut方法中并沒有h的參與
現(xiàn)在我們回顧一下InvocationHandler的invoke方法的定義

public Object invoke(Object proxy, Method method, Object[] args)

第一個(gè)proxy是代理自身念祭,method是被代理的方法兑宇,args是方法的參數(shù)
因此為了使得代理生效,我們可以修改方法粱坤,如下

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

public class $Proxy11 extends Proxy implements TestInterface {
    protected $Proxy11(InvocationHandler h) {
        super(h);
    }

    @Override
    public int put(String a) {
        try {
            return (int) h.invoke(this, TestInterface.class.getMethod("put", String.class), new Object[]{a});
        } catch (Throwable e) {
            return 0;
        }
    }
}

這樣我們就能使得h的代理生效了
當(dāng)然隶糕,這只是我們所設(shè)想的最基本的一種代理形式。有了這個(gè)思路之后比规,我們就可以看看源碼中是如何生成方法的字節(jié)碼

接著我們來看重點(diǎn)若厚,proxy方法的寫入
還是回到generateClassFile()方法中關(guān)注下面這行代碼

this.methods.add(var16.generateMethod());

這個(gè)方法就是proxy方法實(shí)際執(zhí)行的code部分了,因?yàn)榇a比較多蜒什,所以我就直接將注釋寫到代碼中

如果你前面的內(nèi)容都仔細(xì)閱讀且理解了测秸,那我想你一定會(huì)有興趣看完下面所有的代碼,并且會(huì)對(duì)proxy的實(shí)現(xiàn)和class字節(jié)碼有更深刻的理解

當(dāng)然灾常,如果你看到源碼就非常頭疼也沒有關(guān)系霎冯,可以跳過這部分源碼直接看最后的驗(yàn)證部分

private ProxyGenerator.MethodInfo generateMethod() throws IOException {
    /**
     * 獲取方法描述,如果還打開著之前javap的工具的話钞瀑,就能看到類似于
     * // java/lang/Object."<init>":()V
     * // Test.calc:(II)I
     */
    String methodDescriptor = ProxyGenerator.getMethodDescriptor(this.parameterTypes, this.returnType);
    /**
     * 這里和之前構(gòu)造器一樣沈撞,先生成一個(gè)MethodInfo對(duì)象
     * 這里17表示public final
     * Modifier.FINAL | Modifier.PUBLIC
     */
    ProxyGenerator.MethodInfo methodInfo = ProxyGenerator.this.new MethodInfo(this.methodName, methodDescriptor, 17);
    /**
     * 新建一個(gè)存放靜態(tài)池編號(hào)的數(shù)組
     */
    int[] parameterTypesOrders = new int[this.parameterTypes.length];
    /**
     * 這個(gè)值是指靜態(tài)池中的編號(hào),如果還打開著之前javap的話雕什,類似于
     * Constant pool:
     *    #1 = Methodref          #8.#19         // java/lang/Object."<init>":()V
     *    #2 = Methodref          #7.#20         // Test.calc:(II)I
     *    #3 = Double             2.0d
     *    #5 = Methodref          #21.#22        // java/lang/Math.pow:(DD)D
     * 前面的#1,#2,#3,#5
     * 我們注意到缺少了#4缠俺,因?yàn)閐ouble需要占用8個(gè)字節(jié)显晶,而其他的都只需要占用4個(gè)字節(jié)
     */
    int constantPoolNumber = 1;

    for(int i = 0; i < parameterTypesOrders.length; ++i) {
        parameterTypesOrders[i] = constantPoolNumber;
        /**
         * 如果是Long或者Double類型的參數(shù),則+2壹士,否則+1磷雇,因?yàn)長ong和Double都是占用8個(gè)字節(jié)
         */
        constantPoolNumber += ProxyGenerator.getWordsPerType(this.parameterTypes[i]);
    }

    DataOutputStream dataOutputStream = new DataOutputStream(methodInfo.code);
    /**
     * aload_0,加載棧幀本地變量表的第一個(gè)參數(shù)躏救,因?yàn)槭菍?shí)例方法唯笙,所以是就是指this
     */
    ProxyGenerator.this.code_aload(0, dataOutputStream);
    /**
     * getfield,獲取this的實(shí)例字段
     */
    dataOutputStream.writeByte(180);
    /**
     * 從Proxy類中盒使,獲取類型是InvocationHandler崩掘,字段名為h的對(duì)象
     */
    dataOutputStream.writeShort(ProxyGenerator.this.cp.getFieldRef("java/lang/reflect/Proxy", "h", "Ljava/lang/reflect/InvocationHandler;"));
    /**
     * aload_0
     */
    ProxyGenerator.this.code_aload(0, dataOutputStream);
    /**
     * getstatic,獲取靜態(tài)字段
     */
    dataOutputStream.writeByte(178);
    /**
     * 獲取當(dāng)前代理類少办,名字是methodFieldName苞慢,類型是Method的對(duì)象(之前在寫入靜態(tài)池的時(shí)候,用的也是methodFieldName)
     */
    dataOutputStream.writeShort(ProxyGenerator.this.cp.getFieldRef(ProxyGenerator.dotToSlash(ProxyGenerator.this.className), this.methodFieldName, "Ljava/lang/reflect/Method;"));
    /**
     * 準(zhǔn)備寫入?yún)?shù)
     */
    if (this.parameterTypes.length > 0) {
        /**
         * 寫入?yún)?shù)的數(shù)量英妓,如果再仔細(xì)看一下code_ipush
         * 當(dāng)length小于等于5時(shí)枉疼,寫入的命令是iconst_m1~iconst_5
         * 當(dāng)length在-128~127閉區(qū)間時(shí),寫入的命令是bipush
         * 否則就寫入sipush
         */
        ProxyGenerator.this.code_ipush(this.parameterTypes.length, dataOutputStream);
        /**
         * anewarray鞋拟,創(chuàng)建一個(gè)數(shù)組
         */
        dataOutputStream.writeByte(189);
        /**
         * 數(shù)組的類型是object
         */
        dataOutputStream.writeShort(ProxyGenerator.this.cp.getClass("java/lang/Object"));

        /**
         * 循環(huán)參數(shù)
         */
        for(int i = 0; i < this.parameterTypes.length; ++i) {
            /**
             * dup,復(fù)制棧頂?shù)牟僮鲾?shù)
             */
            dataOutputStream.writeByte(89);
            /**
             * iconst惹资、bipush贺纲、sipush
             */
            ProxyGenerator.this.code_ipush(i, dataOutputStream);
            /**
             * 對(duì)參數(shù)類型等做一個(gè)編碼
             */
            this.codeWrapArgument(this.parameterTypes[i], parameterTypesOrders[i], dataOutputStream);
            /**
             * aastore,將對(duì)象存入數(shù)組
             */
            dataOutputStream.writeByte(83);
        }
    } else {
        /**
         * 如果沒參數(shù)的話
         * aconst_null褪测,push一個(gè)null
         */
        dataOutputStream.writeByte(1);
    }
    /**
     * invokeinterface 調(diào)用接口方法
     */
    dataOutputStream.writeByte(185);
    /**
     * 找到InvocationHandler的invoke方法
     */
    dataOutputStream.writeShort(ProxyGenerator.this.cp.getInterfaceMethodRef("java/lang/reflect/InvocationHandler", "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;"));
    /**
     * iconst_1猴誊,將1壓入操作棧
     */
    dataOutputStream.writeByte(4);
    /**
     * nop,不做事情
     */
    dataOutputStream.writeByte(0);

    if (this.returnType == Void.TYPE) {
        /**
         * 如果是void方法
         * pop侮措,將棧頂?shù)牟僮鲾?shù)彈出
         */
        dataOutputStream.writeByte(87);
        /**
         * return
         */
        dataOutputStream.writeByte(177);
    } else {
        /**
         * 對(duì)返回值進(jìn)行編碼
         */
        this.codeUnwrapReturnValue(this.returnType, dataOutputStream);
    }

    byte startPc = 0;
    short handlerPc;
    short endPc = handlerPc = (short)methodInfo.code.size();
    /**
     * 獲取方法可能拋出的異常
     */
    List catchList = ProxyGenerator.computeUniqueCatchList(this.exceptionTypes);
    if (catchList.size() > 0) {
        Iterator exceptionIterator = catchList.iterator();

        /**
         * 對(duì)異常進(jìn)行預(yù)處理
         */
        while(exceptionIterator.hasNext()) {
            Class var12 = (Class)exceptionIterator.next();
            /**
             * 這里注意startPc, endPc, handlerPc參數(shù)懈叹,和pc register有關(guān),用于拋出Exception時(shí)能確定接下去要執(zhí)行的指令
             */
            methodInfo.exceptionTable.add(new ProxyGenerator.ExceptionTableEntry(startPc, endPc, handlerPc, ProxyGenerator.this.cp.getClass(ProxyGenerator.dotToSlash(var12.getName()))));
        }
        /**
         * athrow分扎,拋出異常
         */
        dataOutputStream.writeByte(191);
        /**
         * 重新獲取異常的處理點(diǎn)
         */
        handlerPc = (short)methodInfo.code.size();
        /**
         * 添加異常的基類
         */
        dataOutputStream.exceptionTable.add(new ProxyGenerator.ExceptionTableEntry(startPc, endPc, handlerPc, ProxyGenerator.this.cp.getClass("java/lang/Throwable")));
        /**
         * 根據(jù)constantPoolNumber的值
         * astore_0 = 75 (0x4b)
         * astore_1 = 76 (0x4c)
         * astore_2 = 77 (0x4d)
         * astore_3 = 78 (0x4e)
         * astore
         */
        ProxyGenerator.this.code_astore(constantPoolNumber, dataOutputStream);
        /**
         * new 創(chuàng)建一個(gè)新對(duì)象
         */
        dataOutputStream.writeByte(187);
        /**
         * 對(duì)象是UndeclaredThrowableException
         */
        dataOutputStream.writeShort(ProxyGenerator.this.cp.getClass("java/lang/reflect/UndeclaredThrowableException"));
        /**
         * dup 復(fù)制棧頂操作數(shù)
         */
        dataOutputStream.writeByte(89);
        /**
         * 根據(jù)constantPoolNumber的值
         * aload_0 = 42 (0x2a)
         * aload_1 = 43 (0x2b)
         * aload_2 = 44 (0x2c)
         * aload_3 = 45 (0x2d)
         * aload
         */
        ProxyGenerator.this.code_aload(constantPoolNumber, dataOutputStream);
        /**
         * invokespecial澄成,調(diào)用父類的方法
         */
        dataOutputStream.writeByte(183);
        /**
         * 父類的構(gòu)造函數(shù)
         */
        dataOutputStream.writeShort(ProxyGenerator.this.cp.getMethodRef("java/lang/reflect/UndeclaredThrowableException", "<init>", "(Ljava/lang/Throwable;)V"));
        /**
         * athrow,拋出異常
         */
        dataOutputStream.writeByte(191);
    }

    if (var2.code.size() > 65535) {
        throw new IllegalArgumentException("code size limit exceeded");
    } else {
        var2.maxStack = 10;
        var2.maxLocals = (short)(var4 + 1);
        var2.declaredExceptions = new short[this.exceptionTypes.length];

        for(int var14 = 0; var14 < this.exceptionTypes.length; ++var14) {
            var2.declaredExceptions[var14] = ProxyGenerator.this.cp.getClass(ProxyGenerator.dotToSlash(this.exceptionTypes[var14].getName()));
        }

        return var2;
    }
}

那么為了看看我們一開始對(duì)于方法的猜測是否正確,我們略微改造之前定義的接口和類畏吓,然后實(shí)際看看
接口和Proxy定義(因?yàn)樽止?jié)碼中還包含了一些異常的信息墨状,所以定義接口的時(shí)候特別定義了2個(gè)異常)

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.concurrent.TimeoutException;

public class Proxy11 extends Proxy implements TestInterface {
    protected Proxy11(InvocationHandler h) {
        super(h);
    }

    public void put(String a, Double b) throws TimeoutException {
        try {
            h.invoke(this, TestInterface.class.getMethod("put", String.class, Double.class), new Object[]{a, b});
        } catch (Throwable e) {
        }
    }

    public int get(String a, Long b) throws IndexOutOfBoundsException {
        try {
            return (int) h.invoke(this, TestInterface.class.getMethod("get", String.class, Long.class), new Object[]{a, b});
        } catch (Throwable e) {
            return 0;
        }
    }
}


interface TestInterface {
    void put(String a, Double b) throws TimeoutException;

    int get(String a, Long b) throws IndexOutOfBoundsException;
}

我們生成class后,將字節(jié)碼的指令集與我們之前的分析一一對(duì)比菲饼,雖然其中還是有些不同肾砂,不過大體上是符合之前源碼的順序

最后為了實(shí)際考察Proxy生成類的源碼,我們還是需要將Proxy的字節(jié)碼轉(zhuǎn)換回java文件

首先我們需要添加vm啟動(dòng)參數(shù)

-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

有了這個(gè)參數(shù)宏悦,當(dāng)我們使用Proxy時(shí)镐确,就會(huì)把class寫入到文件中了
寫入的目錄是項(xiàng)目下的com/sun/proxy/$Proxy11.class
為了更好地可讀性包吝,我們需要使用一個(gè)在線工具
http://www.javadecompilers.com/
傳入我們之前生成出來class文件
結(jié)果如下

package com.sun.proxy;

import java.util.concurrent.TimeoutException;
import java.lang.reflect.UndeclaredThrowableException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import cn.tera.aopproxy.TestInterface;
import java.lang.reflect.Proxy;

public final class $Proxy11 extends Proxy implements TestInterface
{
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m4;
    private static Method m0;
    
    public $Proxy11(final InvocationHandler h) {
        super(h);
    }
    
    public final boolean equals(final Object o) {
        try {
            return (boolean)super.h.invoke(this, $Proxy11.m1, new Object[] { o });
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable undeclaredThrowable) {
            throw new UndeclaredThrowableException(undeclaredThrowable);
        }
    }
    
    public final int get(final String s, final Long n) throws IndexOutOfBoundsException {
        try {
            return (int)super.h.invoke(this, $Proxy11.m3, new Object[] { s, n });
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable undeclaredThrowable) {
            throw new UndeclaredThrowableException(undeclaredThrowable);
        }
    }
    
    public final String toString() {
        try {
            return (String)super.h.invoke(this, $Proxy11.m2, null);
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable undeclaredThrowable) {
            throw new UndeclaredThrowableException(undeclaredThrowable);
        }
    }
    
    public final void put(final String s, final Double n) throws TimeoutException {
        try {
            super.h.invoke(this, $Proxy11.m4, new Object[] { s, n });
        }
        catch (Error | RuntimeException | TimeoutException error) {
            throw;
        }
        catch (Throwable undeclaredThrowable) {
            throw new UndeclaredThrowableException(undeclaredThrowable);
        }
    }
    
    public final int hashCode() {
        try {
            return (int)super.h.invoke(this, $Proxy11.m0, null);
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable undeclaredThrowable) {
            throw new UndeclaredThrowableException(undeclaredThrowable);
        }
    }
    
    static {
        try {
            $Proxy11.m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            $Proxy11.m3 = Class.forName("cn.tera.aopproxy.TestInterface").getMethod("get", Class.forName("java.lang.String"), Class.forName("java.lang.Long"));
            $Proxy11.m2 = Class.forName("java.lang.Object").getMethod("toString", (Class<?>[])new Class[0]);
            $Proxy11.m4 = Class.forName("cn.tera.aopproxy.TestInterface").getMethod("put", Class.forName("java.lang.String"), Class.forName("java.lang.Double"));
            $Proxy11.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());
        }
    }
}

是不是有一種恍然大悟的感覺,此時(shí)再回頭去看之前分析的方法字節(jié)碼源葫,就能更好地理解其含義了诗越,以及和我們自己定義的類的字節(jié)碼有區(qū)別的原因了。

當(dāng)然我們更可以直接查看生成的class文件臼氨,再通過javap去查看字節(jié)碼掺喻,然后返過去和前面的源碼再作對(duì)比,這個(gè)就留給讀者自己去分析了

至此储矩,java動(dòng)態(tài)代理的根本原理和相應(yīng)的class字節(jié)碼結(jié)構(gòu)的分析就結(jié)束了

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末感耙,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子持隧,更是在濱河造成了極大的恐慌即硼,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屡拨,死亡現(xiàn)場離奇詭異只酥,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)呀狼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門裂允,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人哥艇,你說我怎么就攤上這事绝编。” “怎么了貌踏?”我有些...
    開封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵十饥,是天一觀的道長。 經(jīng)常有香客問我祖乳,道長逗堵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任眷昆,我火速辦了婚禮蜒秤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘亚斋。我一直安慰自己垦藏,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開白布伞访。 她就那樣靜靜地躺著掂骏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪厚掷。 梳的紋絲不亂的頭發(fā)上弟灼,一...
    開封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天级解,我揣著相機(jī)與錄音,去河邊找鬼田绑。 笑死勤哗,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的掩驱。 我是一名探鬼主播芒划,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼欧穴!你這毒婦竟也來了民逼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤涮帘,失蹤者是張志新(化名)和其女友劉穎拼苍,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體调缨,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡疮鲫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了弦叶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片俊犯。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖伤哺,靈堂內(nèi)的尸體忽然破棺而出瘫析,到底是詐尸還是另有隱情,我是刑警寧澤默责,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站咸包,受9級(jí)特大地震影響桃序,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜烂瘫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一媒熊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧坟比,春花似錦芦鳍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至籍琳,卻和暖如春菲宴,著一層夾襖步出監(jiān)牢的瞬間贷祈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來泰國打工喝峦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留势誊,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓谣蠢,卻偏偏與公主長得像粟耻,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子眉踱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

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