Author: tere

This series of articles is the result of some research into the nature of Java dynamic proxies by bloggers who learned about the use of Java dynamic proxies while studying Spring AOP

To get to the point quickly, I’ll skip spring AOP and Java dynamic proxies

However, let’s first look at the basic usage of Java Dynamic Proxy. Assuming that the object we want to proxy is a Map, the code looks like this:

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

ProxyInstance can then be used as a normal Map object

To get a basic understanding of the properties of the generated object, let’s first print the actual type name of the proxyInstance

System.out.println(proxyInstance.getClass().getName());
Copy the code

results

com.sun.proxy.$Proxy11
Copy the code

If you use it too much, you’ll find that all Proxy classes are named $Proxy with a number and the package name is com.sun.proxy

When we look at the proxy.newProxyInstance method, we see that it actually returns an Object

public static Object newProxyInstance(ClassLoader loader, Class
       [] interfaces, InvocationHandler h)
Copy the code

In the actual use process, it can be directly transformed into the interface type we passed in, so it can be inferred that the actual type of the proxyInstance object must implement the interface we passed in

Let’s print out the interface that this class implements

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

results

java.util.Map
Copy the code

It fits our earlier theory

And then we print the parent of that class

System.out.println(proxyInstance.getClass().getSuperclass().getName());
Copy the code

results

java.lang.reflect.Proxy
Copy the code

So to summarize, the proxyInstance object has the following three properties 1. Inherit Proxy class 2. Implement the interface we passed in 3. Name it as $Proxy+ random number

So how does the ability to dynamically generate proxy classes work? Next, I will look at the Java source code because there is a lot of source code, so I will only post the key parts

The entry point, of course, is the proxy.newProxyInstance method and there are two parts of it that we need to care about

The first part, class creation

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

So that’s how you actually generate a class, and we’re going to dig into that a little bit later

The second part, instance creation

finalConstructor<? > cons = cl.getConstructor(constructorParams);finalInvocationHandler ih = h; .return cons.newInstance(new Object[]{h});
Copy the code

The final object is instantiated by taking the constructor for its specified arguments from the previously generated class and passing in the InvocationHandler object

Check out the constructorParams field

/** parameter types of a proxy class constructor */
private static finalClass<? >[] constructorParams = { InvocationHandler.class };Copy the code

It’s really a constructor that gets the InvocationHandler object

Recall that the first part of the class definition inherits the Proxy class, so let’s go to the Proxy class

/**
 * 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;
}
Copy the code

In this constructor, the parameter h is assigned to the member variable h, and the name h can be memorized and will be encountered later in this article

Having seen the instance creation, let’s return to the more important first part, where the class generation goes into the getProxyClass0(loader, INTFS) method

/** * Generate a proxy class. Must call the checkProxyAccess method * to perform permission checks before calling this. * /
private staticClass<? > 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);
}
Copy the code

This method is simple, fetching objects directly from a cache

View the proxyClassCache object

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

The object is essentially a cache similar to Map, but uses WeakCache. The characteristics of WeakCache itself will be discussed in another article, which focuses on Proxy. We can see that the constructor of the cache obtains two factories. The second one is the one that generates the ProxyClass, so naturally we need to move on to the second Factory

Class is annotated below

/**
* 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<? >>Copy the code

This is the factory we are looking for that is responsible for generating the specific class. Look at the apply method

First, the interface type is checked by the loader, including whether the interface can be loaded into the interface, whether the interface is actually an interface (because the array type is Class), and whether the interface is duplicate

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()); }}Copy the code

Next, set the class’s default access_flag, public final

int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
Copy the code

It then checks whether the array of interfaces passed in contains non-public interfaces. If so, the generated class needs to be in the same package as the interface, and the access properties remove public, leaving only final. If there are multiple non-public interfaces in different packages, an error is reported (the reason should be understandable)

/* * 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"); }}}Copy the code

If there are no non-public classes, the default package name, com.sun.proxy, is used

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

Then get a statically incremented int

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

Fixed class name prefix

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

Combine the above three into the final class name (recall the class name of the instance we printed out earlier)

String proxyName = proxyPkg + proxyClassNamePrefix + num;
Copy the code

These steps determine the name of the class, but they are still skin deep, followed by the flesh and blood of the class: bytecode

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

Let’s look at how bytecode can be converted into a concrete class

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());
}
Copy the code

This method is a native method, so we cannot continue to explore it for the time being. However, after knowing this method, if we have the need, we can also use this mechanism to achieve our own dynamic class generation. We will try to make a demo later, so we will not discuss it in this paper

private static nativeClass<? > defineClass0(ClassLoader loader, String name,byte[] b, int off, int len);
Copy the code

Now go back to the bytecode generation method and look at the source code of the method

public static byte[] generateProxyClass(finalString 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(a) {
                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;
}
Copy the code

The intermediate if part of the code can be ignored for now, but we will use it in a later article. Focus on the following two lines

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

So let’s just remember that var0 is the class name and var1 is the interface and var3 is access_flag and I’m going to try to convert these varX’s into more practical names for you to understand

Then comes the final focus and difficulty of this article, that is, the actual generation process of binary bytecode, including JVM operation instructions, so we need to first have an understanding of the structure of the class file and JVM operation instructions

The JVM document address: docs.oracle.com/javase/spec… The following is a brief description of the structure of bytecode, much of it as the name suggests

ClassFile { u4 magic; // Fixed beginning, value is 0xCAFEBABE u2 minor_version; // Version number, used to mark the version of class u2 major_version; // The version number used to mark the version of class u2 constant_pool_count; Cp_info constant_pool[constant_pool_count-1]; cp_info constant_pool[constant_pool_count-1]; // Static pool object, valid index is 1 ~ count-1 u2 access_flags; //public, final, etc. // Information about the current class u2 super_class; // Parent information u2 interfaces_count; // The number of u2 interfaces[interfaces_count]; // u2 fields_count; Field_info fields[fields_count]; // field object u2 methods_count; Method_info methods[methods_count]; // Method object u2 attributes_count; // Number of attributes attribute_info attributes[attributes_count]; // Attribute object}Copy the code

In order not to become a boring document translation, and as soon as possible into the Proxy source, here will not do a special detailed description of each part, to grasp the overall

Next we can enter the generateClassFile() method

To get the big picture, let’s skip some of the details and look at the following part (here I made a readable change to the variable name)

Note the Class’s byte structure

The byte stream of final output

ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
DataOutputStream data = new DataOutputStream(byteStream);
Copy the code

Write the fixed beginning magic, where -889275714 corresponds to 0xCAFEBABE

data.writeInt(-889275714);
Copy the code

Write version number

data.writeShort(0);//minor_version
data.writeShort(49);//major_version
Copy the code

Write to the constant pool, where cp stands for constant pool

this.cp.write(data);
Copy the code

Here we need to enter the CP write method to have a look, also do not tangle with the details of Entry, we still grasp the overall

public void write(OutputStream var1) throws IOException {
    DataOutputStream var2 = new DataOutputStream(var1);
    /** * write the size of cp. Note that size()+1 corresponds to the */ constant_pool_count
    var2.writeShort(this.pool.size() + 1);
    Iterator var3 = this.pool.iterator();
    /** * iterate over the object in cp and write details corresponding to cp_info */ in the Class structure
    while(var3.hasNext()) { ProxyGenerator.ConstantPool.Entry var4 = (ProxyGenerator.ConstantPool.Entry)var3.next(); var4.write(var2); }}Copy the code

And then let’s go back to the outer method and keep going

Write access_flag

data.writeShort(this.accessFlags);
Copy the code

Writes information about the current class

data.writeShort.writeShort(this.cp.getClass(dotToSlash(this.className)));
Copy the code

Write information from the parent class (recall the first property of the class, which inherits the Proxy class)

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

Number of write interfaces

data.writeShort.writeShort(this.interfaces.length);
Copy the code

Traverses the interface and writes the interface information

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())));
}
Copy the code

Number of fields written

data.writeShort(this.fields.size());
Copy the code

Iterate over the field and write the field information

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

Number of write methods

data.writeShort(this.methods.size());
Copy the code

Traversal method, write method information

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

Because the class has no special attribute, the number of attributes is written to 0

data.writeShort(0);
Copy the code

It corresponds exactly to the previous class structure, and now we have an overall grasp of what the proxy is doing


Now that we know the whole picture, let’s take a closer look at the format of some of the objects in bytecode to prepare for a further look at Proxy source code. To better understand what’s going on, let’s define a simple class called test.java

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

    public int add(int a, int b) {
        returna + b; }}interface TestInt {}Copy the code

Generate. Class files

javac Test.java
Copy the code

Look at the.class file

javap -v Test.class
Copy the code

results

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"Copy the code

The following three parts correspond to minor_version, major_version, and access_flags

minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Copy the code

Let’s look at 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
Copy the code

There are several types

Methodref: reference to a method Fieldref: reference to a field Class: reference to a Class Utf8: reference to a string NameAndType Description of the type

The following are described one by one, according to the JVM documentation

The Class structure

CONSTANT_Class_info {
    u1 tag;
    u2 name_index;
}
Copy the code

Tag: indicates its own index in the constant pool. Name_index: must be a valid index in the constant pool

#3 = Class              #18            // Test
Copy the code

Tag = 3, indicating that its index is 3

Name_index = 18, indicating that the index of the name is 18

At this point we look at #18, the name of the class is Test

#18 = Utf8               Test
Copy the code

Field, Method, Interface structure

The three are grouped together in the document

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;
}
Copy the code

Represents a reference to a field, method, interface method

Tag: indicates its own index in the constant pool class_index: Indicates a valid index in the constant pool that must be of type Class if Methodref_info and an Interface if InterfaceMethodref_info If Fieldref_info can be Class or Interface name_AND_type_index: Represents a valid index in the constant pool (name of method, return type, parameter), if Fieldref_info, must be a description of the field, otherwise it must be a description of the method

For example,

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

Tag = 1 indicates that its index is 1. Class_index = 4 indicates that the class type is 4. Name_and_type_index = 16 indicates that the method description is index 16

Look at 4 and 16

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

This means that the method is a constructor in the Object class

NameAndType structure

CONSTANT_NameAndType_info {
    u1 tag;
    u2 name_index;
    u2 descriptor_index;
}
Copy the code

Represents a method or field that does not include the class to which the field or method belongs

Name_index: a valid index in the constant pool, must be of type Utf8 (indicating the name of the method or field) descriptor_index: a valid index in the constant pool, must be of type Utf8 (indicating the method return type and parameter) descriptor_index: a valid index in the constant pool, must be of type Utf8 (indicating the method return type and parameter)

For example,

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

tag = 16 name_index = 8 descriptor_index = 9

Look at indexes 8 and 9

   #8 = Utf8               <init>
   #9 = Utf8               ()V
Copy the code

The method name represents the constructor. It takes 0 arguments and returns void

Utf-8 structure

CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}
Copy the code

Represents a string constant

Bytes [length] : Specifies the data content. This section provides more details, but I won’t go into detail here. If you are interested, check out the JVM documentation for more details

That’s it for the constant pool, but then we need to look at the other members of the class structure

This_class, must be a valid constant pool index, super_class of type CONSTANT_Class_info, must be a valid constant pool index, CONSTANT_Class_info or 0, Interfaces [], an int value that must be a valid index of a constant pool. The value in the array must be fields_count, CONSTANT_Class_info, and the number of fields

Fields [], array of fields, the values in the array are field_info structures

field_info { u2 access_flags; //access_flag u2 name_index; // a valid index in the constant pool, which must be of type Utf8 (representing the name of the method or field) U2 descriptor_index; // a valid index in the constant pool, which must be of type Utf8 (representing the description of the field) u2 attributes_count; // Skip this article without attribute_info attributes[attributes_count]; // Skip this article.Copy the code

Methods_count methods[

method_info { u2 access_flags; //access_flag u2 name_index; // a valid index in the constant pool, which must be of type Utf8 (representing the name of the method or field) U2 descriptor_index; // a valid index in the constant pool, which must be of type Utf8 (representing the description of the method) u2 attributes_count; // Number of attributes attribute_info attributes[attributes_count]; // The content of the attribute}Copy the code

The basic structure of the class file is covered here, and we will take a closer look at how the various structures of the class are constructed

Go back to the beginning of the generateClassFile() method

The first part, Object method preprocessing

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

First of all, no matter what class it is, it inherits from Object, so methods in Object are absolutely necessary. Notice that addProxyMethod doesn’t write section codes directly, it does some pre-processing. Let’s look at what the first parameter of the three methods is. You can see that there are really three methods of Object

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

We go to the addProxyMethod method, where we do a readability on the variable name

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));
Copy the code

To summarize, generate a ProxyMethod object based on the elements of the method and add it to a cache List

Next we go to the constructor of ProxyMethod

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++;
}
Copy the code

It is worth noting that there are two fields in the constructor of ProxyMethod, which will be useful later. One is methodName, which represents the Method name, and the other is methodFieldName, which represents the name of the Method type field in the resulting class with an m+ increasing number

The second part, the preprocessing of interface method

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); }}Copy the code

Since the generated class implements the interface passed in, we loop through the interface, adding the interface’s method elements to proxyMethods, just as we did with Object

The third part is bytecode writing of fields and methods

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);
}
Copy the code

The first line here is the bytecode written to the constructor, which we’ll skip because it involves executing instructions for the JVM later

this.methods.add(this.generateConstructor());
Copy the code

The while loop iterates through the Object and interface defined methods we added earlier and generates the corresponding field and method bytecodes

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());
}
Copy the code

Let’s look at the details of the field bytecode

The fourth part, field bytecode

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

FieldInfo. The first constructor parameter proxyMethod methodFieldName we mentioned m + increasing Numbers generated methodFieldName the second parameter is the type description The third parameter is accessFlag, 10 said private static (Modifier. Private | Modifier. The static)

Let’s go into the constructor

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);
}
Copy the code

Recall the field_info type from the previous article (ignore attributes)

field_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
}
Copy the code

Descriptor this.name, this.descriptor, and this.accessFlags correspond exactly to structures in field_info

Also, since name_index and Descriptor_index are both indexes in the Constant pool, you need to write them to the Constant pool where cp means Constant pool, and write methodFieldName and Descriptor to the static pool

ProxyGenerator.this.cp.getUtf8(var2);
ProxyGenerator.this.cp.getUtf8(var3);
Copy the code

Then we can look directly at the write method in FieldInfo, which is how the last byte is written

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);
}
Copy the code

Compare field_info to access_flags first and then name_index and descriptor_index, both indexes

And then finally, because the number of attributes is 0, we just write 0

A complete field structure has been written

Then we go back see ProxyGenerator. This. Cp. GetUtf8 method, see how indexes are determined

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

Then look at the getValue method

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));
        returnvar3; }}Copy the code

The key is the field that needs to be written, the value is the index value, and if a map is hit, the value is returned

If the cache is not hit, you need addEntry to look at the addEntry method

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(); }}Copy the code

Add the generated entry to the pool and return the current pool size, which is the index of the constant in the pool

Recall the structure of cp, where cp number is count+1 and cp array valid index starts at 1, so pool size is returned directly instead of size-1

Therefore ProxyGenerator. This. Cp. GetUtf8 did 2 things 1 () method. Write a value to a constant pool. 2. Return the index of the value in the constant pool

This is the end of the field, and we look at the bytecode of the method

The fifth part, method bytecode

Let’s look at the code in the while loop

this.methods.add(proxyMethod.generateMethod());
Copy the code

Look at the generateMethod method

Because the structure of a method actually consists of two main parts, the first part is the basic properties like field_info, and the second part is the method’s execution body. We’ll talk about how the method’s execution body is written separately later

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

Here the first line is the description of the method to get, similar to ()V to describe the parameters and return parameters of the method, where *()V* represents the method to get 0 parameters and return void

The second line generates a MethodInfo object and looks at its constructor

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");
}
Copy the code

Review method_info as well

method_info { u2 access_flags; //access_flag u2 name_index; // a valid index in the constant pool, which must be of type Utf8 (representing the name of the method or field) U2 descriptor_index; // a valid index in the constant pool, which must be of type Utf8 (representing the description of the method) u2 attributes_count; // Number of attributes attribute_info attributes[attributes_count]; // The content of the attribute}Copy the code

Unlike field_info, the MethodInfo constructor writes two additional constant pool objects in addition to the basic access_flags, name_index, and descriptor_index: Code and Exceptions represent two attributes

Code means to execute Code

Exceptions represent the Exceptions that the method throws

Again, let’s look at the write method in MethodInfo next

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));
Copy the code

Number of attributes written

var1.writeShort(2);
Copy the code

This is where we need to take a look at the Attributes infrastructure

attribute_info { u2 attribute_name_index; // Name in the constant pool index u4 attribute_length; // Attribute length u1 info[attribute_length]; // Attribute actual data}Copy the code

Here we look at two specific attributes, Code and Exception, which are the structure of Code we saw earlier in the constructor

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];
}
Copy the code

Now let’s look at the code

Attribute_name_index is written first

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

Write data length attribute_length, where 12 and 8 are explained later in this article

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

Write the number of local variables max_stack and max_locals to the stack depth, which will be covered in detail in the generateMethod() method in the next article, but will not be expanded here

var1.writeShort(this.maxStack);
var1.writeShort(this.maxLocals);
Copy the code

Code_length and code[code_length]. GenerateMethod () ¶

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

When writing max_stack, max_locals, and code_length, the types of fields are short, short, and INTEGER, which add up to 8 bytes

The number of exceptions thrown by the write method exception_table_length

var1.writeShort(this.exceptionTable.size());
Copy the code

This time exception_table_length is a short, plus the previous 8 bytes, a total of 10 bytes

Write the concrete structure of the exception

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);
}
Copy the code

Each Exception has four fields: start_pc, end_pc, handler_pc, and catch_type, all of which are short, so an Exception has eight bytes, which corresponds to the eight in attribute_length

Finally, we write the Attributes’ own attributes_count, because we don’t have it, so we just write 0

var1.writeShort(0);
Copy the code

This amount is a short, plus the 10 bytes accumulated before, making 12 bytes, which corresponds to 12 in Attribute_length

Next, Exception

The Exception structure

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

This structure is relatively simple, so let’s look at the corresponding code

The constant pool index attribute_name_index is written first

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

I’ll say attribute length attribute_length, and I’ll explain the two twos here, but I think you can see what they are, right

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

Number of write exceptions number_of_exceptions, type short, corresponds to the first 2

var1.writeShort(this.declaredExceptions.length);
Copy the code

Write specific exceptions to the index in the constant pool, each of which is a short corresponding to the second 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);
}
Copy the code

Above, the fields and methods write basic parsing is done and then we will explore the most complex executable content of the generateMethod() method

Because the method’s bytecode involves the JVM’s instructions, let’s start with a basic overview

Original address: dzone.com/articles/in… The JVM instruction document: docs.oracle.com/javase/spec… The concepts of heap, stack, method area and so on introduced at the beginning of the article are not described in detail here, mainly see it after some simple method bytecode parsing first we define a simple class

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

Compile to generate test.class

javac Test.java
Copy the code

View the bytecode structure

javap -v Test.class
Copy the code

Let’s focus on the main method part

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
Copy the code

The Code is the execution body of the method, which is illustrated in sequence below

Iconst_1: Pushes constant 1 onto the stack

Istore_1: the operand that pops to the top of the stack, index 1 of the array of local variables stored on the stack, which is variable A

Iconst_2: pushes constant 2 onto the stack

Istore_2: the operand that pops to the top of the stack, index 2 of the array of local variables stored on the stack, which is variable B

Iload_1: Indexes a read value from a local variable and pushes it onto the operation stack

Iload_2: Indexes two read values from local variables and pushes them onto the operation stack

Iadd: Pops two operands onto the top of the stack, adds them together and pushes the result onto the stack

Istore_3: the operand that pops to the top of the stack, index 3 of the array of local variables stored on the stack, that is, variable C

Return: Returns from a method

If we define a method in a class

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)); }}Copy the code

The resulting bytecode looks like this, and THIS time I’ve shown part of the Constant pool below

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 Invokestatic #5 // Method Java /lang/ math.pow :(DD)D 8: iloAD_1 9: i2d 10: ldc2_w #3 // double 2.0 D 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: 0Copy the code

Here we’ll look at some of the new instructions in the main method, numbered 6 invokestatic #2: Calc = test. calc = test. calc = test. calc = test. calc = test. calc = test. calc Push long or double (think about why they are in the same operation) from the static pool dadd: adds doubles d2i: converts doubles to ints iReturn: returns an int

By combining the ABOVE JVM instructions with Java code, you can begin to understand how each line of Java code is executed by the JVM

Next we can take a look at Proxy code in combination with the actual situation

Method or generateClassFile() was mentioned earlier in “Writing bytes and method bytecodes in Part 3.

The first line here is the bytecode that writes to the constructor, which will be skipped because it involves executing instructions for the JVM in the next article

this.methods.add(this.generateConstructor());
Copy the code

Now we can look in detail at what does the generateConstructor method do

private ProxyGenerator.MethodInfo generateConstructor(a) 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;
}
Copy the code

Note, in particular, that var2 represents the executable part of the method, which is one of the method Attributes we mentioned in the previous article: Code

And then line by line

Initialize MethodInfo with the method name, method description, access_flag, and 1 indicating public (see modifier.java).

The method is named because it is a constructor

The description of the method, said the method to obtain a Java lang. Reflect. InvocationHandler types of parameters and return values for V (void)

The access_flag of the method is 1, which means public

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

Write aload_0 and aload_1 operation instructions in Code

this.code_aload(0, var2);
this.code_aload(1, var2);
Copy the code

Write operation instruction 183 in Code, look up the document to get: Invokespecial

Call instance methods, specifically to handle the parent class constructor

var2.writeByte(183);
Copy the code

Write the name of the method to be called and its parameters in Code

Note that this method is obtained by the this.cp.getmethodref method, which means that the final data written here is actually a valid index in a constant pool that matches the description of this method.

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

Write instruction 177 in Code and look at the documentation to get: return

Return void

var2.writeByte(177);
Copy the code

Finally, as mentioned in the previous article, you also need to write the stack depth and the number of local variables, as well as the number of exceptions that the method will throw, which is zero because the constructor does not actively throw exceptions

Note that this is not directly writeByte, but rather a setting for MethodInfo’s properties. This part of the bytecode will still be written to MethodInfo’s write method, see the previous article

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

At this point, the structure of a constructor is complete

At this point we summarize the structure of the constructor, which should look like this when we look at the structure of the class file

aload_0; aload_1; Invokespecial #x // where x corresponds to the Constant pool constructor number return;Copy the code

To verify, let’s create a class

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

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

Look at its bytecode

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: 5Copy the code

Exactly what we concluded earlier

Combined with the basic descriptions of some previous JVM instructions, we can get a better idea of the overall structure of method_info

Let’s pause for a moment and consider this question: how would we define a Proxy dynamic class if we defined it ourselves in code? First, let’s review the three features of Proxy classes mentioned in the first article. 1. Inherits Proxy class 2. Assume we now define a simple interface and generate a Proxy class interface definition for that interface, using the name $Proxy+ random number

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

A proxy class that meets the three characteristics is preliminarily defined as follows

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; }}Copy the code

However, h’s proxy does not take effect in this case, since H does not participate in the PUT method. Now let’s review the definition of the Invoke method of InvocationHandler

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

The first proxy is the proxy itself, method is the propped method, and args is the parameter of the method. Therefore, to make the proxy work, we can modify the method as follows

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; }}}Copy the code

So we can make h’s agency work and of course this is just the most basic form of agency that we can imagine. With that in mind, how does the source code generate the method’s bytecode

Now let’s focus on writing the proxy method back to generateClassFile() and focus on the following line of code

this.methods.add(var16.generateMethod());
Copy the code

This method is the actual code part of the proxy method, because there’s a lot of code, so I’ll just write the comments directly into the code

If you’ve read and understood the previous sections carefully, I’m sure you’ll be interested to see all the code below and gain a deeper understanding of proxy implementation and class bytecode

Of course, it doesn’t matter if you have a headache looking at the source code, you can skip this section of the source code and go to the final validation section

private ProxyGenerator.MethodInfo generateMethod(a) throws IOException {
    "
      
       ":()V */ / test.calc :(II)I */
      
    String methodDescriptor = ProxyGenerator.getMethodDescriptor(this.parameterTypes, this.returnType);
    / * * * here and before the constructor, as Mr Into a MethodInfo object * 17 here said the public final * Modifier in final | Modifier. The public * /
    ProxyGenerator.MethodInfo methodInfo = ProxyGenerator.this.new MethodInfo(this.methodName, methodDescriptor, 17);
    /** * Create an array of static pool numbers */
    int[] parameterTypesOrders = new int[this.parameterTypes.length];
    /** * This value refers to the static pool number, if the previous javap is still open, similar to * Constant pool: * #1 = Methodref #8.#19 // Java /lang/Object."
      
       ":()V * #2 = Methodref #7 #5 = Methodref #21.#22 // Java /lang/Math
      
    int constantPoolNumber = 1;

    for(int i = 0; i < parameterTypesOrders.length; ++i) {
        parameterTypesOrders[i] = constantPoolNumber;
        /** * +2 if Long or Double, +1 otherwise, since both Long and Double take 8 bytes */
        constantPoolNumber += ProxyGenerator.getWordsPerType(this.parameterTypes[i]);
    }

    DataOutputStream dataOutputStream = new DataOutputStream(methodInfo.code);
    /** * aload_0, the first argument to load the stack frame local variable table
    ProxyGenerator.this.code_aload(0, dataOutputStream);
    /** * getField, get this instance field */
    dataOutputStream.writeByte(180);
    /** * From the Proxy class, get an object of type InvocationHandler and field h */
    dataOutputStream.writeShort(ProxyGenerator.this.cp.getFieldRef("java/lang/reflect/Proxy"."h"."Ljava/lang/reflect/InvocationHandler;"));
    /** * aload_0 */
    ProxyGenerator.this.code_aload(0, dataOutputStream);
    /**
     * getstatic,获取静态字段
     */
    dataOutputStream.writeByte(178);
    /** * gets the current proxy class, named methodFieldName, and an object of type Method (methodFieldName was also used when writing to the static pool) */
    dataOutputStream.writeShort(ProxyGenerator.this.cp.getFieldRef(ProxyGenerator.dotToSlash(ProxyGenerator.this.className), this.methodFieldName, "Ljava/lang/reflect/Method;"));
    /** * Ready to write argument */
    if (this.parameterTypes.length > 0) {
        Code_ipush * if length is less than or equal to 5, write iconst_m1 to iconst_5 * If length is between -128 and 127. The command to write is bipush * or sipush */
        ProxyGenerator.this.code_ipush(this.parameterTypes.length, dataOutputStream);
        /** * anewarray, create an array */
        dataOutputStream.writeByte(189);
        /** * The array type is object */
        dataOutputStream.writeShort(ProxyGenerator.this.cp.getClass("java/lang/Object"));

        /** * loop parameter */
        for(int i = 0; i < this.parameterTypes.length; ++i) {
            /** * dup, copy the operand at the top of the stack */
            dataOutputStream.writeByte(89);
            /** * iconst, bipush, sipush */
            ProxyGenerator.this.code_ipush(i, dataOutputStream);
            /** * code the type of the argument */
            this.codeWrapArgument(this.parameterTypes[i], parameterTypesOrders[i], dataOutputStream);
            /** * aastore, store the object in array */
            dataOutputStream.writeByte(83); }}else {
        /** * push a null */ if no arguments * aconst_null
        dataOutputStream.writeByte(1);
    }
    /** * invokeInterface invokes the interface method */
    dataOutputStream.writeByte(185);
    /** * Find the invoke method of InvocationHandler */
    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, push 1 on stack */
    dataOutputStream.writeByte(4);
    /** * nop, do not do things */
    dataOutputStream.writeByte(0);

    if (this.returnType == Void.TYPE) {
        /** * if void * pop, pop the top operand */
        dataOutputStream.writeByte(87);
        /** * return */
        dataOutputStream.writeByte(177);
    } else {
        /** * encode the return value */
        this.codeUnwrapReturnValue(this.returnType, dataOutputStream);
    }

    byte startPc = 0;
    short handlerPc;
    short endPc = handlerPc = (short)methodInfo.code.size();
    /** * Get the exception that the method may throw */
    List catchList = ProxyGenerator.computeUniqueCatchList(this.exceptionTypes);
    if (catchList.size() > 0) {
        Iterator exceptionIterator = catchList.iterator();

        /** * Preprocess the exception */
        while(exceptionIterator.hasNext()) {
            Class var12 = (Class)exceptionIterator.next();
            /** * The startPc, endPc, and handlerPc parameters are related to the PC register, which is used to determine which instruction to execute */ when an Exception is thrown
            methodInfo.exceptionTable.add(new ProxyGenerator.ExceptionTableEntry(startPc, endPc, handlerPc, ProxyGenerator.this.cp.getClass(ProxyGenerator.dotToSlash(var12.getName()))));
        }
        /** * athrow throws an exception */
        dataOutputStream.writeByte(191);
        /** * Retrieve the exception handling point */
        handlerPc = (short)methodInfo.code.size();
        /** * Add the base class for the exception */
        dataOutputStream.exceptionTable.add(new ProxyGenerator.ExceptionTableEntry(startPc, endPc, handlerPc, ProxyGenerator.this.cp.getClass("java/lang/Throwable")));
        /** * 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 Creates a new object */
        dataOutputStream.writeByte(187);
        / * * * object is UndeclaredThrowableException * /
        dataOutputStream.writeShort(ProxyGenerator.this.cp.getClass("java/lang/reflect/UndeclaredThrowableException"));
        /** * dUP copy stack top operand */
        dataOutputStream.writeByte(89);
        /** * According to the value of 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, call the parent class method */
        dataOutputStream.writeByte(183);
        /** * the parent constructor */
        dataOutputStream.writeShort(ProxyGenerator.this.cp.getMethodRef("java/lang/reflect/UndeclaredThrowableException"."<init>"."(Ljava/lang/Throwable;) V"));
        /** * athrow throws an exception */
        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()));
        }

        returnvar2; }}Copy the code

So to see if our initial guess about the method is correct, let’s modify the interface and class a little bit, and then actually look at the interface and Proxy definitions (since the bytecode also contains some exception information, we specifically defined two exceptions when defining the interface).

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;
}
Copy the code

After we generate the class, we compare the bytecode instruction set to our previous analysis, and although there are still some differences, they generally follow the order of the previous source code

Finally, to actually see the source code of the proxy-generated class, we need to convert the Proxy bytecode back to a Java file

First we need to add the VM startup parameters

-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
Copy the code

With this parameter, when we use Proxy, we will write the class to a file in the directory of our project com/sun/ Proxy /$proxy11. class We need to use an online tool www.javadecompilers.com/ to generate the class file before passing it in to us. The result is as follows

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 newUndeclaredThrowableException(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 newUndeclaredThrowableException(undeclaredThrowable); }}public final String toString(a) {
        try {
            return (String)super.h.invoke(this, $Proxy11.m2, null);
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable undeclaredThrowable) {
            throw newUndeclaredThrowableException(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 newUndeclaredThrowableException(undeclaredThrowable); }}public final int hashCode(a) {
        try {
            return (int)super.h.invoke(this, $Proxy11.m0, null);
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable undeclaredThrowable) {
            throw newUndeclaredThrowableException(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 newNoClassDefFoundError(ex2.getMessage()); }}}Copy the code

It’s a bit of an Epiphany when you go back and look at the bytecode of the method you analyzed earlier to better understand what it means and why it’s different from the bytecode of our own class.

Of course, we can directly view the generated class file, and then through javap to view the bytecode, and then go back and the previous source code for comparison, this is left to the reader to analyze

This concludes our analysis of the fundamentals of Java dynamic proxies and the corresponding class bytecode structure