preface

Recently, I looked at the Remote Method Invocation (RMI) and saw a problem with dynamic Invocation, so I decided to explore dynamic Invocation.

Here’s a primer on RMI.

RMI

RMI makes it possible for objects on one JVM to call methods on objects on another JVM, called remotely, as we normally write programs.

The interface definition

Define a Remote object interface that implements the Remote interface for markup.

public interface UserInterface extends Remote {
    void sayHello() throws RemoteException;
}
Copy the code

Remote object definition

Define a Remote object class that inherits UnicastRemoteObject to implement the Serializable and Remote interfaces and implement interface methods.

public class User extends UnicastRemoteObject implements UserInterface { public User() throws RemoteException {} @Override public void sayHello() { System.out.println("Hello World"); }}Copy the code

The service side

Start the server and register the User object in the registry.

public class RmiServer { public static void main(String[] args) throws RemoteException, AlreadyBoundException, MalformedURLException { User user = new User(); LocateRegistry.createRegistry(8888); Naming. Bind (rmi: / / "127.0.0.1:8888 / user", the user). System.out.println("rmi server is starting..." ); }}Copy the code

Start the server:

The client

Get the remote object from the server registry and call the sayHello() method on the server.

public class RmiClient { public static void main(String[] args) throws RemoteException, NotBoundException, MalformedURLException {UserInterface user = (UserInterface) Naming. Lookup (" rmi: / / 127.0.0.1:8888 / user "); user.sayHello(); }}Copy the code

Server running result:At this point, a simple RMI demo is complete.

A dynamic proxy

Ask questions

Looked at the RMI code, feel UserInterface this interface a bit redundant, if the client use Naming. The lookup () to obtain the object is not strong to UserInterface, is equivalent to the User can also directly, so tried, so did the following error:$Proxy0, which is familiar and a little strange, searched the dusty notes and found the knowledge point of dynamic proxy, but only a few notes were mentioned, so I decided to sort out dynamic proxy and rearrange a note.

Dynamic Proxy Demo

The interface definition

public interface UserInterface {
    void sayHello();
}
Copy the code

Real Character Definition

public class User implements UserInterface { @Override public void sayHello() { System.out.println("Hello World"); }}Copy the code

Invoke the processing class definition

When the proxy class calls the methods of the real role, it actually calls the invoke() method of the class object bound to the real role, which in turn invokes the methods of the real role.

You need to implement the InvocationHandler interface and the invoke() method.

public class UserHandler implements InvocationHandler { private User user; public UserProxy(User user) { this.user = user; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("invoking start...." ); method.invoke(user); System.out.println("invoking stop...." ); return user; }}Copy the code

Perform class

public class Main { public static void main(String[] args) { User user = new User(); // Handle class and real role binding UserHandler UserHandler = new UserHandler(user); / / open the proxy class class files saved to the local mode, at ordinary times can be omitted System. The getProperties (), put (" sun. Misc. ProxyGenerator. SaveGeneratedFiles ", "true"); / / dynamic Proxy generated Proxy objects $Proxy0 Object o = Proxy. NewProxyInstance (Main. Class. GetClassLoader (), new class [] {UserInterface. Class}. userHandler); Invoke () ((UserInterface)o).sayHello(); }Copy the code

Running results:The basic usage of this dynamic proxy is learned, but there are still a lot of questions.

  1. How is the invoke() method invoked by the dynamic proxy?

  2. What does the processing class UserHandler do?

  3. Why pass the class loader and interface class array as arguments to newProxyInstance?

If you were asked to implement dynamic proxy, what design ideas would you have?

guess

Does dynamic proxy have anything in common with static proxy, the design mode proxy pattern?

The proxy class holds the real role object, and the proxy class calls the method A of the real role object in the method A. Instantiate the proxy object in Main and call its A method, which indirectly calls the A method of the real actor.

“Implementation Code”

// Interface and real character objects use the above code // proxy class, Public class UserProxy implements UserInterface {private User User = new User(); @Override public void sayHello() { System.out.println("invoking start...." ); // Call the real role sayHello() user.sayHello() in sayHello() of the proxy object; System.out.println("invoking stop...." ); Public class Main {public static void Main (String[] args) {UserInterface userProxy = new UserProxy(); SayHello () userProxy.sayHello(); sayHello() userProxy.sayHello(); }Copy the code

Compare the dynamic proxy code with the static proxy code, the interface and the real role have, the difference is that there is a UserHandler class, there is a UserProxy class.

The invoke() method of UserHandler is the same as the sayHello() method of UserProxy. Create a New UserProxy class, implement the UserInterface interface and hold the UserHandler object. Invoke UserHandler’s invoke() method in the sayHello() method.

“That’s what the code looks like.”

// Guess the proxy class structure, $Proxy0 public class UserProxy implements UserInterface{// Private InvocationHandler handler; public UserProxy(InvocationHandler handler) { this.handler = handler; SayHello (); And calls the invoke () @ Override public void sayHello () {try {handler. Invoke (this, the UserInterface. Class. GetMethod (" sayHello "), null); } catch (Throwable throwable) { throwable.printStackTrace(); Public static void main(String[] args) {User User = new User(); UserHandler userHandler = new UserHandler(user); UserProxy proxy = new UserProxy(userHandler); proxy.sayHello(); }Copy the code

Output result:

The above Proxy code is written dead, whereas dynamic Proxy code is dynamically generated when you call proxy.newProxyinstance () based on the parameters you pass in. If I had to implement this, it would be the following process.

  1. Based on the array of Class[] interfaces you pass in, the proxy Class implements these interfaces and their methods (sayHello() in this case), takes the userHandler object you pass in, and writes the predefined package name, Class name, method name, and other lines of code to local disk using a file stream. Generate the $proxy0.java file

  2. Use the compiler to compile to proxy0.class

  3. Load $proxy0.class into JMV based on the ClassLoader you passed in

  4. Invoking proxy.newProxyInstance () returns an object of $Proxy0, and invoking sayHello() executes userHandler’s invoke().

The above is a guess of the dynamic proxy process, the following is through debug to see how to achieve the source code.

Learn to embrace source code in confused days

Embrace the source code

Call flow chart

Here with PPT draw a flow chart, you can follow the flow chart to see the source code behind.

The flow chart

“Set breakpoints from newProxyInstance()”

newProxyInstance()

The newProxyInstance() code is divided into two parts, the upper part is to get the class, and the lower part is to build the Proxy0 object through reflection.

“Upper Part of code”

newProxyInstance()

As the name suggests, getProxyClass0() is the core method, step into

getProxyClass0()

getProxyClass()

Inside called WeakCache object get() method, here pause debug, first talk about WeakCache class.

WeakCache

As the name implies, it is a weak reference cache. So what is a weak reference? Is there a strong reference?

A weak reference

WeakReference is a WeakReference class, which acts as a wrapper class to package other objects. When GC is performed, the wrapper object will be recycled, and the WeakReference object will be placed in the reference queue.

Here’s an example:

String str1 = new String("hello"); String str1 = new String("hello"); ReferenceQueue referenceQueue = new ReferenceQueue(); // When the garbage is collected, the wrapped object in str2 will be recycled, but the weak reference object in str2 will not be recycled, i.e. word will be recycled, but the weak reference object pointed to by STR2 will not be recycled. WeakReference<String> str2 = new WeakReference<>(new String("world"), referenceQueue); // execute gc system.gc (); Thread.sleep(3); // Print a weak reference to the recycled wrapper object: Java. Lang. Ref. WeakReference @ 2077 d4de / / look can debug, The weak reference object's referent variable points to a wrapper object that is already null system.out.println (referencequeue.poll ());Copy the code

The structure of WeakCache

In fact, the whole WeakCache works around the member variable map, building a <K,<K,V>> format of the second level cache, in the dynamic proxy corresponding type is < Class loader,< interface Class, proxy Class>>, they all use weak reference packaging, In this way, the garbage can be collected directly at garbage collection time, reducing the heap memory footprint.

Private final ReferenceQueue<K> refQueue = new ReferenceQueue<>() Private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map = new ConcurrentHashMap<>(); // If a value exists in the secondary cache, Private final ConcurrentMap<Supplier<V>, Boolean> reverseMap = new ConcurrentHashMap<>(); Private final BiFunction<K, P,? > subKeyFactory = new KeyFactory(); Value private final BiFunction<K, P, V> valueFactory = new ProxyClassFactory();Copy the code

WeakCache the get ()

Go back to debug and go to get() to see how KV is generated by map level 2 cache.

public V get(K key, P parameter) { Objects.requireNonNull(parameter); // Traverse the refQueue and delete expungeStaleEntries() from the cache map; CacheKey Object CacheKey = cachek. valueOf(key, refQueue); ConcurrentMap ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey); If (valuesMap == null) {// If (valuesMap == null) {// If (valuesMap == null) {// If (valuesMap == null) {// If (valuesMap == null) {// If (valuesMap == null) {// If (valuesMap == null) {// If (valuesMap == null) {// If (valuesMap == null) { ConCurrentMap<CacheKey, ConCurrentMap<Object, Supplier>> ConCurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>()); If (oldValuesMap! = null) { valuesMap = oldValuesMap; ConCurrentMap<CacheKey, ConCurrentMap<Key1, Supplier>> Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter)); // Obtain value Supplier<V> Supplier = valuesmap. get(subKey); Factory factory = null; / /!!!!!! It doesn't end until the second level cache Value is built, Value is weak reference $proxy0.class!! While (true) {// First loop: suppiler must be null, because there is no KV value in the secondary cache. Enter if if (supplier! = null) {// Second loop: actually generate proxy object, // scroll back to < generate level 2 cache Value>, core!!!!! $Proxy0. Class V value = supplier. Get (); if (value ! $Proxy = null) {return value = $Proxy; }} // First loop: If (factory == null) {factory = new Factory(key, parameter, subKey, valuesMap); If (supplier == null) {supplier = valuesmap. putIfAbsent(subKey, factory); If (supplier == null) {// First loop: suppiler is not empty after assignment, remember !!!!! supplier = factory; } } } } }Copy the code

Generate a level 2 cache Key

Call subkeyFactory.apply (key, parameter) in get() to generate a level 2 cache key based on the number of interface classes [] you passed in newProxyInstance(). Here we pass in a UserInterface. Class, so we return a Key1 object.

KeyFactory.apply()

No matter Key1, Key2 or KeyX, they all inherit WeakReference, which is a WeakReference Class that wraps the object as a Class. Here’s the code for Key1.

Key1

Generate a level 2 cache Value

In the above while loop, the first loop simply generates an empty Factory object into the second-level cache’s ConcurrentMap.

In the second loop, you start to actually build value through the get() method.

Don’t look back, keep looking down.

Factory.get() generates a weak reference to value

** “CacheValue” ** class is a weak reference to the Value of a level 2 cache wrapped around a class, in this case $proxy0.class

Public synchronized V get() {public synchronized V get() {// check whether it is synchronized, if it is synchronized, continue the while loop above and re-generate Factory Supplier<V> Supplier = valuesmap.get (subKey);  if (supplier ! = this) { return null; } // Class V value = null; Class value = objects.requirenonNULL (valueFactory.apply(key, parameter)); CacheValue<V> CacheValue = new CacheValue<>(value); <WeakCache get() >V value = suppler.get (); return value; }}Copy the code

CacheValue

Class file generation

Package name Definition and validation of class names

Go to the valueFactory.apply(key, parameter) method and see how the class file is generated.

private static final String proxyClassNamePrefix = "$Proxy"; public Class<? > apply(ClassLoader loader, Class<? >[] interfaces) { Map<Class<? >, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); Class for (Class<? > intf : interfaces) { Class<? > interfaceClass = null; InterfaceClass = class.forname (intf.getName(), false, loader); // If (! interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } String proxyPkg = null; int accessFlags = Modifier.PUBLIC | Modifier.FINAL; for (Class<? > intf : interfaces) { int flags = intf.getModifiers(); // Our UserInterface is public, so skip 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"); }} // If the interface class is public, use the default package if (proxyPkg == null) {// PROXY_PACKAGE = "com.sun.proxy"; proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } / / atom Int, this time the num = 0 long num = nextUniqueNumber. GetAndIncrement (); // com.sun.proxy.$Proxy0 String proxyName = proxyPkg + proxyClassNamePrefix + num; // !!!! Generate the class file, see <class file write local > core !!!! byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags); / /!!!!!! Come back to this line !!!! // After retrieving the byte array, the binary stream of class is loaded into the JVM and returns $proxy0.class, Returned to the Factory. The get () to return packaging defineClass0 (loader, proxyName, proxyClassFile, 0, proxyClassFile length); }}}Copy the code

DefineClass0 () is a custom class loading native method of Proxy class. It takes the binary stream of the class file and loads it into the JVM to get the corresponding class object. For this section, please refer to the JVM class loader.

The class file is written locally

The generateProxyClass() method writes the class binaries to the local directory and returns a stream of the class files loaded using the classloader you passed in, “Do you know what classloaders do here?”

public static byte[] generateProxyClass(final String name, Class[] interfaces) { ProxyGenerator gen = new ProxyGenerator(name, interfaces); Final byte[] classFile = Gen. GenerateClassFile (); final byte[] classFile = Gen. / / the class file is written to the local if (saveGeneratedFiles) {Java. Security. The AccessController. DoPrivileged (new java.security.PrivilegedAction<Void>() { public Void run() { try { FileOutputStream file = new FileOutputStream(dotToSlash(name) + ".class"); file.write(classFile); file.close(); return null; } catch (IOException e) { throw new InternalError( "I/O exception saving generated file: " + e); }}}); } // return $Proxy0. Class array, <class file generation > return classFile; }Copy the code

Generate a binary stream of class files

GenerateClassFile () generates a class file and stores it in a byte array.

Private byte[] generateClassFile() {// Add hashCode, equals, toString to the proxy class. Object.class); addProxyMethod(equalsMethod, Object.class); addProxyMethod(toStringMethod, Object.class); for (int i = 0; i < interfaces.length; i++) { Method[] methods = interfaces[i].getMethods(); for (int j = 0; j < methods.length; SayHello () addProxyMethod(methods[j], interfaces[I]); sayHello() addProxyMethod(methods[j], interfaces[I]) } } for (List<ProxyMethod> sigmethods : proxyMethods.values()) { checkReturnTypes(sigmethods); } // Add methods. Add (generateConstructor()); for (List<ProxyMethod> sigmethods : proxyMethods.values()) { for (ProxyMethod pm : Sigmethods) {/ / the above four methods are encapsulated into a member variable Method type fields. Add (new FieldInfo (PM) methodFieldName, Ljava/lang/reflect/Method; "" , ACC_PRIVATE | ACC_STATIC)); // generate code for proxy method and add it methods.add(pm.generateMethod()); }} / / static static block structure. The methods to the add (generateStaticInitializer ()); cp.getClass(dotToSlash(className)); cp.getClass(superclassName); for (int i = 0; i < interfaces.length; i++) { cp.getClass(dotToSlash(interfaces[i].getName())); } cp.setReadOnly(); ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream dout = new DataOutputStream(bout); / /!!!!!! The core is here! WriteInt (0xCAFEBABE); writeInt(0xCAFEBABE); try {// u4 magic; // u2 minor_version, 0 dout.writeShort(CLASSFILE_MINOR_VERSION); // u2 major_version, Java8 corresponds to 52; dout.writeShort(CLASSFILE_MAJOR_VERSION); // Constant pool cp.write(dout); / / other structures, can be the reference class file structure dout. WriteShort (ACC_PUBLIC | ACC_FINAL | ACC_SUPER); dout.writeShort(cp.getClass(dotToSlash(className))); dout.writeShort(cp.getClass(superclassName)); dout.writeShort(interfaces.length); for (int i = 0; i < interfaces.length; i++) { dout.writeShort(cp.getClass( dotToSlash(interfaces[i].getName()))); } dout.writeShort(fields.size()); for (FieldInfo f : fields) { f.write(dout); } dout.writeShort(methods.size()); for (MethodInfo m : methods) { m.write(dout); } dout.writeShort(0); } catch (IOException e) { throw new InternalError("unexpected I/O Exception", e); } // Return bout. ToByteArray (); }Copy the code

Build the $Proxy object

$proxy0.class = newProxyInstance(); $proxy0.class = newProxyInstance

newInstance

Cl is the proxy0.class obtained above, and H is the userHandler passed in above, which is used as a construction parameter to create the $Proxy0 object. Then grab the dynamic proxy object and call the sayHello() method, invoking UserHandler’s invoke(). “Here’s what UserHandler does!”

$Proxy. Class files

The $Proxy0 class file is found in the com/sum/proxy directory of the project.

“Look at the decompiled class.”

package com.sun.proxy; import com.test.proxy.UserInterface; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy0 extends Proxy implements UserInterface { private static Method m1; private static Method m3; private static Method m2; private static Method m0; public $Proxy0(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final void sayHello() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m3 = Class.forName("com.test.proxy.UserInterface").getMethod("sayHello"); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); }}}Copy the code

conclusion

Above is the debugging process of dynamic proxy source code, compared with the previous guess of the generation process of proxy class, dynamic proxy is directly generated class files, leaving out Java files and compilation of this piece.

At the beginning of the look may be more around, follow the notes and jump guidance, patience to see two times to understand. Dynamic proxy involves a lot of knowledge points. When I read it myself, I struggled with WeakCache for a while. In fact, I can treat it as a two-layer map, but all KV inside are wrapped by weak reference.

Hopefully every programmer reading this will end up as a full-haired coder;

Recommended reading

Why alibaba’s programmer growth rate so fast, read their internal data I understand

Bytedance’s summary of design patterns in PDF is on fire, and the full version is open for download

While swiping Github, I found a note of Ali’s algorithm! The star 70.5 K

Knowledge system and growth route of programmer 50W annual salary.

Java programmers who earn less than 30K a month may not understand this project;

Bytedance’s summary of design patterns in PDF is popular, and the full version is open for sharing

What you don’t know about violent recursive algorithms

Open up hongmeng, who do the system, talk about Huawei microkernel

Three things to watch ❤️

If you find this article helpful, I’d like to invite you to do three small favors for me:

Like, forward, have your “like and comment”, is the motivation of my creation.

Follow the public account “Java Doudi” to share original knowledge from time to time.

Also look forward to the follow-up article ing🚀