In depth analysis of java reflection (IV) – Dynamic Proxy

Introduction to dynamic agents

With the emergence of Java dynamic proxy mechanism, Java developers do not need to write proxy classes manually. As long as they simply specify a set of interfaces and delegate class objects, they can dynamically obtain proxy classes. The proxy class is responsible for dispatching all method calls to the delegate object for reflection execution. In the process of dispatching execution, developers can also adjust the delegate object and its functions as needed. This is a very flexible and flexible proxy framework. In fact, Java Dynamic agent delays the establishment of the proxy relationship between the proxy object and the proxy object (real object) through reflection technology until after the program runs, and dynamically creates a new proxy class to complete the proxy operation on the real object (which can change the method behavior of the original real object), which has become the basis of the current mainstream AOP framework and delayed loading function. In this paper, jdk11 is used to view and write the code related to dynamic agent. However, the functions and interfaces related to JDK dynamic agent have been relatively stable. There is no need to worry about the compatibility problems caused by JDK version upgrade. However, it should be noted that due to the introduction of module concept in jdk9, there are many changes to the source code of dynamic agent. The following article first introduces the agent pattern in the design pattern, then analyzes the core class library, process and mechanism of JDK dynamic agent, and finally analyzes its underlying source code level implementation.

Agent pattern in design pattern

Proxy pattern is a common design pattern. Its purpose is to provide a proxy for other objects to control access to an object. The agent class is responsible for preprocessing messages for the delegate class, filtering and forwarding messages, and subsequent processing after the message is executed by the delegate class.

The agent mode mainly includes three roles:

Write the above class diagram into the following code:

public interface Subject {

	void doSomething();
}

public class RealSubject implements Subject {

	@Override
	public void doSomething() {
		System.out.println("RealSubject doSomething...");
	}
}

public class ProxySubject implements Subject {

	private final Subject subject;

	public ProxySubject(Subject subject) {
		this.subject = subject;
	}

	@Override
	public void doSomething() {
		subject.doSomething();
		doOtherThing();
	}

	private void doOtherThing() {
		System.out.println("ProxySubject doOtherThing...");
	}
}

public class Client {

	public static void main(String[] args) throws Exception {
		Subject subject = new RealSubject();
		ProxySubject proxySubject = new ProxySubject(subject);
		proxySubject.doSomething();
	}
}

Run client #main() output:

RealSubject doSomething...
ProxySubject doOtherThing...

Agent mode is often encountered in daily scenes. A common scene is game practice. A vivid example can be written by incorporating the above code:

public interface Player {

	void playGame();
}

public class I implements Player {

	@Override
	public void playGame() {
		System.out.println("操作Throwable游戏角色打怪升级");
	}
}

public class ProxyPlayer implements Player {

	private final Player player;

	public ProxyPlayer(Player player) {
		this.player = player;
	}

	@Override
	public void playGame() {
		login();
		this.player.playGame();
		logout();
	}

	private void login() {
		System.out.println("登录Throwable游戏角色");
	}

	private void logout() {
		System.out.println("退出Throwable游戏角色");
	}
}

Agent mode has several advantages:

The core API of JDK dynamic agent

The JDK dynamic proxy provides two main dependent classes for external use:

Proxy

java. lang.reflect. Proxy is the core class of JDK dynamic proxy. Its core function is to provide static methods to dynamically generate proxy classes for a group of interfaces and return proxy instance objects, which is similar to the factory class of proxy class instances. java. lang.reflect. Proxy mainly provides four public static methods:

// 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
public static InvocationHandler getInvocationHandler(Object proxy) 
 
// 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
public static Class<?> getProxyClass(ClassLoader loader,Class<?>[] interfaces) 
 
// 方法 3:该方法用于判断指定类对象是否是一个动态代理类
public static boolean isProxyClass(Class<?> cl) 
 
// 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

InvocationHandler

java. lang.reflect. Invocationhandler is a calling processor interface. It defines an invoke method to centrally handle method calls on dynamic proxy class objects. It usually implements proxy access to delegate classes in this method.

public interface InvocationHandler {
    Object invoke(Object proxy,Method method,Object[] args) throws Throwable;
}

Parameter Description:

Implement Java lang.reflect. Invocationhandler interface, you can add proxy access logic by implementing the invoke method. In this logic code block, you can not only call the methods of the delegate class, but also weave additional custom logic. This is how AOP is implemented.

JDK dynamic agent process

The usage process of JDK dynamic agent is as follows:

The pseudo code is as follows:

// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
// 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用
InvocationHandler handler = new InvocationHandlerImpl(..); 
 
// 通过Proxy为包括Interface接口在内的一组接口动态创建代理类的类对象
Class clazz = Proxy.getProxyClass(classLoader,new Class[] { Interface.class,... }); 
 
// 通过反射从生成的类对象获得构造函数对象
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class }); 
 
// 通过构造函数对象创建动态代理类实例
Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });

The above process is complex and can be simplified. The simplified pseudo code is as follows:

// InvocationHandlerImpl实现了InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
InvocationHandler handler = new InvocationHandlerImpl(..); 
 
// 通过Proxy直接创建动态代理类实例
Interface proxy = (Interface) Proxy.newProxyInstance(classLoader,new Class[] { Interface.class },handler);

Mechanism of JDK dynamic agent

The first is the characteristics of the agent class generated by JDK dynamic agent:

As can be seen from the figure, Java lang.reflect. The proxy class is the parent class of the proxy class. This rule applies to all proxy classes created by Java lang.reflect. Dynamic proxy class created by proxy. Moreover, the class also implements a set of interfaces it proxies, which is the fundamental reason why it can be safely typed to an interface it proxies.

Characteristics of proxy class instances

Each proxy class instance is associated with a calling processor object, which can be accessed through Java lang.reflect. The static method getinvocationhandler () provided by proxy obtains the calling processor object of the proxy class instance. When a proxy class instance calls the methods declared in its proxy interface, these methods will eventually be executed by the invoke method of the calling processor. In addition, it is worth noting that the root class of the proxy class is Java Three methods in lang.Object will also be dispatched to the invoke method of the calling processor for execution. They are hashcode, equals and toString. Possible reasons are:

Characteristics of a set of interfaces represented

First of all, it should be noted that there should be no duplicate interfaces to avoid compilation errors during dynamic proxy class code generation. Secondly, these interfaces must be visible to the class loader, otherwise the class loader will not be able to link them, which will lead to the failure of class definition. Thirdly, all non-public interfaces to be proxied must be in the same package, otherwise the proxy class generation will also fail. Finally, the number of interfaces cannot exceed 65535, which is the limit set by the JVM, which is also judged when the proxy class is generated.

exception handling

It can be seen from calling the method declared by the processor interface that theoretically it can throw any type of exception, because all exceptions inherit from the throwable interface, but is this the case? The answer is no, because we must abide by an inheritance principle: when a subclass overrides the parent class or the method implementing the parent interface, the exception thrown must be in the exception list supported by the original method. Therefore, although the calling processor can in theory, it is often limited in practice, unless the method in the parent interface supports throwing throwable exceptions. So what if an exception that is not supported in the interface method declaration does occur in the invoke method? Rest assured that the JDK dynamic proxy class has designed a solution for us: it will throw an undeclaredtowableexception. This exception is a runtimeException type, so it will not cause compilation errors. Through the getCause method of the exception, you can also obtain the original unsupported exception object for error diagnosis.

JDK dynamic agent source code analysis

Because the core logic of JDK dynamic proxy is in Java lang.reflect. In the proxy class, let's briefly analyze the source code of this class. Let's first look at several important static variables in the proxy class:

// 接口组中接口都为为public时候代理类创建的包路径:com.sun.proxy
private static final String PROXY_PACKAGE_PREFIX = ReflectUtil.PROXY_PACKAGE;

// 代理类的构造方法参数类型数组,可见代理类的构造参数只有InvocationHandler类型
private static final Class<?>[] constructorParams = { InvocationHandler.class };

// 缓存了所有已经调用过setAccessible(true)的代理类的构造(Constructor)实例
private static final ClassLoaderValue<Constructor<?>> proxyCache = new ClassLoaderValue<>();

Notice classloadervalue here. A very complex call chain of classloadervalue will be called below:

//intf是Class<?>类型
//loader是类加载器实例
return proxyCache.sub(intf).computeIfAbsent(
    loader,(ld,clv) -> new ProxyBuilder(ld,clv.key()).build()
);

public V computeIfAbsent(ClassLoader cl,BiFunction<? super ClassLoader,? super CLV,? extends V> mappingFunction) 
						throws IllegalStateException {

The function interface and lambda expression are used in the computeifabsent above. If lambda expression is skilled, it should look OK. Its function can be interpreted as: calculate the results obtained by constructing proxybuilder instance through interface type and class loader instance and calling proxybuilder#build(), If the result already exists, the cache is returned directly. In fact, computeifabsent defines the same method in the map interface, with similar functions.

Next, look at the proxy constructor:

protected InvocationHandler h;

private Proxy() {
}

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

It can be made clear that since all dynamic proxy classes are Java lang.reflect. Subclasses of proxy, they must have a constructor containing the invocationhandler parameter. Then check the source code of the ` ` method:

public static Object newProxyInstance(ClassLoader loader,InvocationHandler h) {
    // 空判断									  
    Objects.requireNonNull(h);
    // 当前调用类获取
    final Class<?> caller = System.getSecurityManager() == null
                                    ? null
                                    : Reflection.getCallerClass();
    // 获取代理类的构造器实例								
    Constructor<?> cons = getProxyConstructor(caller,loader,interfaces);
    // 生成代理实例
    return newProxyInstance(caller,cons,h);
}

First look at the getproxyconstructor method:

    private static Constructor<?> getProxyConstructor(Class<?> caller,ClassLoader loader,Class<?>... interfaces){
        // 这里需要区分代理接口数组中只有单个接口和多个接口的逻辑				  
        // 而基本的逻辑都是先校验当前调用类的权限,后续获取Constructor实例委托到ProxyBuilder
        if (interfaces.length == 1) {
            Class<?> intf = interfaces[0];
            if (caller != null) {
                checkProxyAccess(caller,intf);
            }
            return proxyCache.sub(intf).computeIfAbsent(
                loader,clv.key()).build()
            );
        } else {
            // 接口克隆
            final Class<?>[] intfsArray = interfaces.clone();
            if (caller != null) {
                checkProxyAccess(caller,intfsArray);
            }
            final List<Class<?>> intfs = Arrays.asList(intfsArray);
            return proxyCache.sub(intfs).computeIfAbsent(
                loader,clv.key()).build()
            );
        }
    }

It can be made clear that the core logic is completed by proxy's internal class proxybuilder. First look at the static member variables of proxybuilder:

// Unsafe实例
private static final Unsafe UNSAFE = Unsafe.getUnsafe();

// 代理类的简单类名的前置字符串
private static final String proxyClassNamePrefix = "$Proxy";

// 用于生成下一个代理类的数字计数器,记住它是静态的
private static final AtomicLong nextUniqueNumber = new AtomicLong();

// 记录了已经生成的代理类-Boolean的映射,已经生成过对应代理类则记录为true
private static final ClassLoaderValue<Boolean> reverseProxyCache = new ClassLoaderValue<>();
        // 单个代理接口的情况,其实也是把接口转换为List
        ProxyBuilder(ClassLoader loader,Class<?> intf) {
            this(loader,Collections.singletonList(intf));
        }
        // 多个代理接口的情况
        ProxyBuilder(ClassLoader loader,List<Class<?>> interfaces) {
            // 通过JVM参数强制关闭动态代理功能则抛出异常
            if (!VM.isModuleSystemInited()) {
                throw new InternalError("Proxy is not supported until "
                        + "module system is fully initialized");
            }
            // 代理接口数量不能超过65535,也就是最多代理65535个接口
            if (interfaces.size() > 65535) {
                throw new IllegalArgumentException("interface limit exceeded: "
                        + interfaces.size());
            }
            // 收集接口数组中所有接口的非静态方法的返回值类型、共享(shared)参数类型和共享(shared)异常类型,注释说是收集代理接口的方法签名
            Set<Class<?>> refTypes = referencedTypes(loader,interfaces);

            // 确保上一步得到的代理接口方法签名的类型都是"可见(其实就是类型都存在)"的,通过遍历调用Class.forName(type.getName(),false,ld)去判断
            validateProxyInterfaces(loader,interfaces,refTypes);

            this.interfaces = interfaces;
            // 获取代理类最终生成的模块,规则如下:
            // 1、所有代理接口的修饰符都为public,接口所在模块都能公开访问,则返回unnamed模块
            // 2、如果有任意的代理接口是包私有,则返回该包所在的模块		、
            // 3、所有代理接口的修饰符都为public,有任意至少一个接口所在模块不能公开访问,则返回该不能公开访问的模块,
            this.module = mapToModule(loader,refTypes);
            assert getLoader(module) == loader;
        }

The processing logic of a constructor is also relatively complex, mainly because the concept of module management is introduced. Next, look at the source code of proxybuilder #build():

        Constructor<?> build() {
            // 定义代理类,实际上是动态生成代理类字节码和缓存它的类型的过程
            Class<?> proxyClass = defineProxyClass(module,interfaces);
            final Constructor<?> cons;
            try {
            // 返回代理类的构造
                cons = proxyClass.getConstructor(constructorParams);
            } catch (NoSuchMethodException e) {
                throw new InternalError(e.toString(),e);
            }
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
            return cons;
        }

Finally, go to proxybuilder #defineproxyclass(), the generation process of the most logically complex proxy class:

        private static Class<?> defineProxyClass(Module m,List<Class<?>> interfaces) {
            String proxyPkg = null;     // package to define proxy class in
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
            // 这里就是定义代理类包路径的逻辑,规则如下:
            // 1、代理接口数组所有接口都是public修饰,则代理类包路径为com.sun.proxy
            // 2、代理接口数组有任意接口是包私有的,则代理类包路径为该私有包的路径			
            for (Class<?> intf : interfaces) {
                int flags = intf.getModifiers();
                if (!Modifier.isPublic(flags)) {
                    accessFlags = Modifier.FINAL;  // non-public,final
                    String pkg = intf.getPackageName();
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                        throw new IllegalArgumentException(
                                "non-public interfaces from different packages");
                    }
                }
            }
            // 下面几个if都是包路径的合法性判断
            if (proxyPkg == null) {
                // all proxy interfaces are public
                proxyPkg = m.isNamed() ? PROXY_PACKAGE_PREFIX + "." + m.getName()
                                       : PROXY_PACKAGE_PREFIX;
            } else if (proxyPkg.isEmpty() && m.isNamed()) {
                throw new IllegalArgumentException(
                        "Unnamed package cannot be added to " + m);
            }
            if (m.isNamed()) {
                if (!m.getDescriptor().packages().contains(proxyPkg)) {
                    throw new InternalError(proxyPkg + " not exist in " + m.getName());
                }
            }
            // 计数器加1返回新的计数值			
            long num = nextUniqueNumber.getAndIncrement();
            // 生成代理类全类名,一个常见的格式是:com.sun.proxy.$Proxy1
            String proxyName = proxyPkg.isEmpty()
                                    ? proxyClassNamePrefix + num
                                    : proxyPkg + "." + proxyClassNamePrefix + num;
            ClassLoader loader = getLoader(m);
            trace(proxyName,m,interfaces);
            // 动态生成代理类字节码字节数组			
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                    proxyName,interfaces.toArray(EMPTY_CLASS_ARRAY),accessFlags);
            try {
                // 通过Unsafe定义代理类-这里是通过字节码定义新的类				
                Class<?> pc = UNSAFE.defineClass(proxyName,proxyClassFile,proxyClassFile.length,null);
                // 缓存代理类已经生成过的标记												 
                reverseProxyCache.sub(pc).putIfAbsent(loader,Boolean.TRUE);
                return pc;
            } 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());
            }
        }

So far, the generation process of proxy class has been roughly analyzed. Proxygenerator involves a large number of bytecode operations, which will not be analyzed in depth here. Then go back to the first method, get the proxy class and its construction instance, and then generate the proxy instance:

    private static Object newProxyInstance(Class<?> caller,// null if no SecurityManager
                                           Constructor<?> cons,InvocationHandler h) {
        try {
            if (caller != null) {
                checkNewProxyPermission(caller,cons.getDeclaringClass());
            }
            // 这里简单反射调用Constructor#newInstance(h)
            return cons.newInstance(new Object[]{h});
        } catch (illegalaccessexception | InstantiationException e) {
            throw new InternalError(e.toString(),e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(),t);
            }
        }
    }

To summarize:

JDK dynamic proxy class source code

The generation process of proxy class has been analyzed earlier. Here is a simple use example, and observe the source code of the generated dynamic proxy class.

Use example:

// 接口
public interface Simple {

    void sayHello(String name);
}
// 接口实现
public class DefaultSimple implements Simple {

    @Override
    public void sayHello(String name) {
        System.out.println(String.format("%s say hello!",name));
    }
}
// 场景类
public class Main {

    public static void main(String[] args) throws Exception {
        Simple simple = new DefaultSimple();
        Object target = Proxy.newProxyInstance(Main.class.getClassLoader(),new Class[]{Simple.class},new InvocationHandler() {
            @Override
            public Object invoke(Object proxy,Object[] args) throws Throwable {
                System.out.println("Before say hello...");
                method.invoke(simple,args);
                System.out.println("After say hello...");
                return null;
            }
        });
        Simple proxy = (Simple) target;
        proxy.sayHello("throwable");
    }
}

Output after call:

Before say hello...
throwable say hello!
After say hello...

You can see that we weave custom logic before and after being called by the method of the proxy class defaultsimple instance, which is the underlying principle of realizing AOP through JDK dynamic proxy. Sun. Com can be used directly in jdk8 misc. Proxygenerator outputs the class file of the proxy class, but the proxy class generator in JDK 11 has become Java lang.reflect. Proxygenerator, and this class is package private, we can't use it, but it provides JDK proxy. ProxyGenerator. The VM parameter savegeneratedfiles allows us to save the class file of the agent class:

# JVM参数
-Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true

After configuring the VM parameters, you can call the mian method again to see the corresponding class com. Com under the top-level package path of the project sun. proxy.$ Proxy0, currently from Java lang.reflect. The proxygenerator source code cannot control the output path of the agent class file. The generated agent class is as follows:

public final class $Proxy0 extends Proxy implements Simple {
    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(String var1) throws  {
        try {
            super.h.invoke(this,m3,new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    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("club.throwable.jdk.sample.reflection.proxy.Simple").getmethod("sayHello",Class.forName("java.lang.String"));
            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());
        }
    }
}

The code of proxy class is relatively simple and has the following characteristics:

Summary

It is true that proxy has been beautifully designed, but there is still a little regret that it can not get rid of the shackles of only supporting interface proxy, because its design is doomed to this regret. Recall the inheritance diagrams of dynamically generated proxy classes. They are destined to have a common parent class called proxy. Java's single inheritance mechanism doomed these dynamic proxy classes to be unable to implement the dynamic proxy of the class (so they can only proxy the interface, which is actually weaving the method level logic based on reflection). There are many reasons to deny the necessity of class proxy, but there are also some reasons to believe that it will be better to support class dynamic proxy. However, imperfection is not equal to greatness. Greatness is an essence. JDK dynamic agent is an example.

reference material:

Personal blog

(end of this paper e-20181208 c-3-d)

The official account of Technology (Throwable Digest), which is not regularly pushed to the original technical article (never copied or copied):

Entertainment official account ("sand sculpture"), select interesting sand sculptures, videos and videos, push them to relieve life and work stress.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>