preface

I don’t know. Is that a clickbait name? The content is very long, if you can read it patiently, I believe you will feel not the title party ~

I’ve always wanted to get to know the wave of dynamic agents, both from a technical and a working perspective. And with such a foreign name, learning is a must. Even if I starve, die, jump from here, I’m going to learn dynamic proxy.

If you feel too wordy, go straight to the end and read the summary

Personal understanding

First, let’s talk about our understanding of dynamic proxies. Many resources on the web like to compare dynamic and static proxies together. Here we will not do so, I feel that the static proxy itself is an idea, while this dynamic proxy focuses on thinking about the flow behind its code routines, so it is not put together. If you are interested in static proxy, you can directly learn about it

About dynamic proxy, I prefer to separate dynamic and proxy:

Dynamic: capable of changing at any time. For our programming, we can think of doing things at run time.

Agent: to take over our real business and perform it for us. There are many acting as agents in our life, such as letting agents.

Let’s start the process of dynamic agency with a demo in which a tenant rents a house through an intermediary. (After the demo, we will understand dynamic proxy from a source point of view.)

By the light

Effect of the Demo

At the beginning of the demo, we continue to follow the syntax rules of dynamic proxy. We have a tenant, carrying 5000 yuan, came to a strange city. He wanted to rent a house, but he didn’t know anyone, so he chose a real estate agent… As a result, the agent charged him 4500 yuan and our tenant was cheated…

Before we write the code, let’s take a look at the effects.

With this effect in mind, let’s take a step-by-step look at how tenants are being cheated

Start coding

The first step is to write out the defrauded tenants and define a tenant interface

public interface IRentHouseProcessor {
    String rentHouse(String price);
}
Copy the code

Next, implement this interface and act as our hapless tenant:

public class RentHouseProcessorImpl implements IRentHouseProcessor {
    @Override
    public String rentHouse(String price) {
        Log.d(MainActivity.TAG, "I still have:"+price+"Yuan");
        String content = "I'm a renter. I rent a house with an agent, and I feel ripped off.";
        returncontent; }}Copy the code

Next, we implement InvocationHandler to write our dynamic proxy’s main character.

Each Proxy instance has an associated InvocationHandler, as explained in the official Docs article. When a method is invoked on a proxy instance, the method is scheduled to invoke.

Which is the Proxy instance mentioned here? Don’t worry, look down.

public class RentHouseProcessorHandler implements InvocationHandler {
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        Log.d(MainActivity.TAG, "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -");
        Log.d(MainActivity.TAG, "I'm an agent. I have an offer to rent a house. Let's see how much he can offer." + args[0]);
        Log.d(MainActivity.TAG, "Since I am intermediary, that I accept him 4000 yuan of good treatment fee, 500 yuan to you set up a basement, not excessive? !!");
        Object result = method.invoke(target, new Object[]{"500"});
        Log.d(MainActivity.TAG, "Make a lot of money, feel good ~");
        returnresult; }}Copy the code

Start to perform

Start our dynamic proxy approach: instead of using a proxy, we invoke our own implemented interface directly from the tenant’s instance. There is nothing to say ~ just for the sake of the story, a better understanding of the flow.

RentHouseProcessorImpl dpImpl = new RentHouseProcessorImpl();
dpImpl.rentHouse("5000");
Log.d(TAG,"I'm going to ask an agent to set up a house.");
Copy the code

Using dynamic proxies:

RentHouseProcessorHandler handler = new RentHouseProcessorHandler(dpImpl);
IRentHouseProcessor proxy = (IRentHouseProcessor) Proxy.newProxyInstance(
        dpImpl.getClass().getClassLoader(),
        dpImpl.getClass().getInterfaces(),
        handler);

String content = proxy.rentHouse("5000");
Log.d(TAG, content);
Copy the code

Let’s take a look at the above question: where is the proxy instance? This Proxy instance is actually the return value of proxy.newProxyInstance (), which is the IRentHouseProcessor Proxy object. There’s a serious question here, right? The IRentHouseProcessor is an interface, and the interface cannot be new.

So proxy objects are special. That’s right: dynamic proxies, dynamically generated proxy instances. And this instance has the method structure of our interface object, so it can be our interface type, which in turn can call our interface methods.

As mentioned in the above docs, when we call the interface method in the Proxy object, we actually schedule it to the Invoke method in the InvocationHandler method.

The problem arises when methods are introduced to Invoke: Invoke was rewritten by us, which means we have supreme power!

So in our rental story, it was in this invoke method that the intermediary hacked our tenant’s money! Because it has absolute operation rights in the Invoke method. We can do what we want, and we can’t do anything about it without implementing what we really want to implement.

Into the deep

By now, I wonder if you have a clear feeling about the process of dynamic proxy. The dynamic proxy process is fairly routine: We implement an InvocationHandler class that receives information about a Method that is dispatched from a proxy object. Once the Method is invoked, we can do whatever we want. Our proxy class instance is created by the system for us, and we only need to process the invoked method.

Let’s take a look at the dynamically generated proxy class instance. How is it created

Start the code tour

As a first step, let’s start with the original dynamic Proxy method, proxy.newProxyInstance ().

The following code omits some null-try /try-catch procedures, so you can search for the source code yourself if you feel that omission is inappropriate.

public static Object newProxyInstance(ClassLoader loader, Class
       [] interfaces, InvocationHandler h) throws IllegalArgumentException {
    // omit: some nullation, permission check operations

    //[annotation 1]
    // Try to get a proxy Class objectClass<? > cl = getProxyClass0(loader, intfs);// omit: try-catch/ permission check
        
    // Get the constructor object of the proxy class whose parameter type is InvocationHandler.class
    finalConstructor<? > cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;

    // omit: cons.setaccessible (true) procedure
    
    // Pass in an instance of InvocationHandler to construct an instance of the proxy class
    return cons.newInstance(newObject[]{h}); }}Copy the code

[Annotation 1]

In this section of code, we can see that a method is called with a ClassLoader and an array of interface types. And the return value is a Class object. In fact c1 returned here is actually the Class object of our proxy Class. Why? Let’s check it out:

// Fetch the proxy Class object from the cache, if not through ProxyClassFactory->ProxyGenerator
private staticClass<? > getProxyClass0(ClassLoader loader, Class<? >... interfaces) {if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }

    // If there is a proxy class defined by a given loader implementing a given interface, only a cache copy is returned; Otherwise, it creates the proxy class through ProxyClassFactory
    return proxyClassCache.get(loader, interfaces);
}
Copy the code

Skip the cache and look behind

As we come in, we see that the amount of code is extremely small. This is obviously a Cache object to get the Class object we need. This part designs the dynamic proxy cache process, which uses the idea and the data structure more, will not expand for the moment. If you are interested, you can search for it.

The Cache get process is eventually directed to the ProxyClassFactory Class, which becomes the Class object of the required proxy Class.

// A factory function that generates, defines, and returns a proxy class given a ClassLoader and an interface array. private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<? >[], Class<? >> {private static Final String proxyClassNamePrefix = "$Proxy"; Private static Final AtomicLong nextUniqueNumber = new AtomicLong(); @Override public Class<? > apply(ClassLoader loader, Class<? >[] interfaces) { Map<Class<? >, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); for (Class<? > intf : Interfaces) {// Here we iterate over the array of interfaces, Intf is an interface //3. Intf is repeated in the array} // Generate the package name of the proxy class String proxyPkg = null; / / generated proxy class access, the default is public and final int accessFlags = Modifier. The public | Modifier. The final; for (Class<? > intf: interfaces) {//[annotation 1] // omit: verifies that all non-public proxy interfaces are in the same package. Throw new IllegalArgumentException("non-public interfaces from different packages"); throw new IllegalArgumentException("non-public interfaces from different packages"); Proxy if (proxyPkg == NULL) {proxyPkg = reflectutil. PROXY_PACKAGE + "."; } / / generated proxy class number of long num = nextUniqueNumber. GetAndIncrement (); $Proxy0 String proxyName = proxyPkg + proxyClassNamePrefix + num; // Generate the fully qualified name of the proxy class, such as com.sun.proxy. / /!!!!! And then we get to the point, Use ProxyGenerator to generate bytecode, deposit in the form of a byte [] byte [] proxyClassFile = ProxyGenerator. GenerateProxyClass (proxyName, interfaces, accessFlags); Return defineClass0(loader, proxyName, proxyClassFile, 0, proxyclassfile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); }}}Copy the code

[Annotation 1]

This part, I may have omitted a lot, because it is mainly about judgments. What this part does is iterate through all the interfaces to see if they’re public. If not, you need to check whether some interfaces are in the same package. If not, you need to throw exceptions. This is easy to understand, non-public interfaces are not in the same package, this is not a problem

Constructing a proxy Class

GenerateProxyClass (); generateProxyClass (); generateProxyClass ();

private byte[] generateClassFile() {
    // The first step is to assemble all methods into ProxyMethod objects
    // First generate toString, hashCode, equals proxy methods for the proxy class
    addProxyMethod(hashCodeMethod, Object.class);
    addProxyMethod(equalsMethod, Object.class);
    addProxyMethod(toStringMethod, Object.class);
    // Iterate over every method on every interface and generate a ProxyMethod object for it
    for (int i = 0; i < interfaces.length; i++) {
        Method[] methods = interfaces[i].getMethods();
        for (int j = 0; j < methods.length; j++) { addProxyMethod(methods[j], interfaces[i]); }}// For proxy methods with the same signature, verify that the method return value is compatible
    for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
        checkReturnTypes(sigmethods);
    }
    
    // In the second step, assemble all the field information and method information of the class file to be generated
    try {
        // Add the constructor method
        methods.add(generateConstructor());
        // Iterate through the proxy method in the cache
        for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
            for (ProxyMethod pm : sigmethods) {
                // Add static fields to the proxy class, such as private static Method m1;
                fields.add(new FieldInfo(pm.methodFieldName,
                        "Ljava/lang/reflect/Method;", ACC_PRIVATE | ACC_STATIC));
                // Add the proxy method of the proxy classmethods.add(pm.generateMethod()); }}// Add a static field initialization method for the proxy class
        methods.add(generateStaticInitializer());
    } catch (IOException e) {
        throw new InternalError("unexpected I/O Exception");
    }
    
    // The set of validation methods and fields cannot be larger than 65535
    if (methods.size() > 65535) {
        throw new IllegalArgumentException("method limit exceeded");
    }
    if (fields.size() > 65535) {
        throw new IllegalArgumentException("field limit exceeded");
    }

    // Step 3, write the final class file
    // Verify that the fully qualified name of the proxy class exists in the constant pool
    cp.getClass(dotToSlash(className));
    // Verify the presence of the fully qualified name of the Proxy class parent in the constant pool :" Java /lang/reflect/Proxy"
    cp.getClass(superclassName);
    // Verify that the constant pool has a fully qualified name for the proxy class interface
    for (int i = 0; i < interfaces.length; i++) {
        cp.getClass(dotToSlash(interfaces[i].getName()));
    }
    // Set the constant pool to read-only
    cp.setReadOnly();
    
    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    DataOutputStream dout = new DataOutputStream(bout);
    try {
        //1. Write the magic number
        dout.writeInt(0xCAFEBABE);
        //2. Write the minor version number
        dout.writeShort(CLASSFILE_MINOR_VERSION);
        //3. Write the main version number
        dout.writeShort(CLASSFILE_MAJOR_VERSION);
        //4. Write to constant pool
        cp.write(dout);
        //5. Write access modifiers
        dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);
        //6. Write the class index
        dout.writeShort(cp.getClass(dotToSlash(className)));
        //7. Write the index of the parent class to generate Proxy classes that inherit from Proxy
        dout.writeShort(cp.getClass(superclassName));
        //8. Write interface counts values
        dout.writeShort(interfaces.length);
        //9. Write the interface set
        for (int i = 0; i < interfaces.length; i++) {
            dout.writeShort(cp.getClass(dotToSlash(interfaces[i].getName())));
        }
        //10. Write fields to count values
        dout.writeShort(fields.size());
        //11. Write the field collection
        for (FieldInfo f : fields) {
            f.write(dout);
        }
        //12. Write methods count values
        dout.writeShort(methods.size());
        //13. Write method set
        for (MethodInfo m : methods) {
            m.write(dout);
        }
        The proxy class file has no attributes, so it is 0
        dout.writeShort(0);
    } catch (IOException e) {
        throw new InternalError("unexpected I/O Exception");
    }
    // Convert to binary array output
    return bout.toByteArray();
}
Copy the code

The content of the above comments, if you have seen the bytecode format, should be familiar. This section is to create the Class bytecode file for our proxy Class. ByteArrayOutputStream converts the manually generated bytecode content into byte[] and loads it into memory by calling defineClass0.

The last return method is a native method that loads our constructed byte[] into memory, and then obtains the corresponding Class object, that is, the Class of our proxy Class.


private static nativeClass<? > defineClass0(ClassLoader var0, String var1,byte[] var2, int var3, int var4);

Copy the code

conclusion

OK, at this point, the Class object for our proxy Class has been generated. So the class we return from proxy.newProxyInstance () is pretty clear. One: a brand new Class object with all the method structures of the interface classes we implement. That’s what we call a proxy class.

It is possible to call our methods because we have the method structure of our interface. During this process, however, the method we call is scheduled to the Invoke method in the InvocationHandler. Invoke: invoke: invoke: invoke: invoke: invoke: invoke: invoke: invoke To answer this question, we need to take a look at what our generated Proxy Proxy class looks like.

I’ve summarized the various ways to view the.class files generated by dynamic proxies on the web, and posted the least expensive way to do it: run our dynamic proxy method using Eclipse. Before running, add the following line:

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles"."true"); 
Copy the code

Of course, there is a high probability that the IDE will report an error

Exception in thread "main"java.lang.InternalError: I/O exception saving generated file: java.io.FileNotFoundException: Com \sun\proxy\$proxy0.class (system could not find the specified path.)Copy the code

So what to do? It is easy to create a three-level folder at the SRC level: com/sun/proxy. Then run it and see our $proxy0.class. Then we drag it into AndroidStudio:

public final class $Proxy0 extends Proxy implements IRentHouseProcessor {
    private static Method m3;
    private static Method m1;
    private static Method m0;
    private static Method m2;

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

    public final String rentHouse(String var1) throws  {
        try {
            return (String)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw newUndeclaredThrowableException(var4); }}public final boolean equals(Object var1) throws  {
        try {
            return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw newUndeclaredThrowableException(var4); }}public final int hashCode(a) throws  {
        try {
            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}public final String toString(a) throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw newUndeclaredThrowableException(var3); }}static {
        try {
            m3 = Class.forName("proxy.IRentHouseProcessor").getMethod("rentHouse".new Class[]{Class.forName("java.lang.String")});
            m1 = Class.forName("java.lang.Object").getMethod("equals".new Class[]{Class.forName("java.lang.Object")});
            m0 = Class.forName("java.lang.Object").getMethod("hashCode".new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString".new Class[0]);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw newNoClassDefFoundError(var3.getMessage()); }}}Copy the code

Looking at the Proxy code, it is clear why interface methods are scheduled to invoke methods.


conclusion

Little friends step by step to catch up with down, DO not know whether the process of dynamic proxy has a clearer understanding. In the next part, we will write the actual application scenarios for dynamic proxy. And analysis of Retrofit dynamic proxy.

Finally make an advertisement (we maintain the learning public account)

The official account is mainly aimed at junior/fresh graduates. The content includes the pits we stepped in when we changed from college graduates to career development, as well as our weekly learning plan and learning summary. The content will involve computer network, algorithm and other basics; Will also involve the front end, background, Android and other content ~

Other posts from our gay friends group:

Android Gay Friends