Detailed explanation of JDK dynamic agent steps of source code analysis
- 2021-09-11 20:14:01
- OfStack
Dynamic proxy step
1. Create a class that implements the interface InvocationHandler, which must implement the invoke method
2. Create proxy classes and interfaces
3. Static method through Proxy
Static method through Proxy
ProxyObject proxyObject = new ProxyObject();
InvocationHandler invocationHandler = new DynamicProxy(proxyObject);
ClassLoader classLoader = proxyObject.getClass().getClassLoader();
ProxyObjectInterface proxy = (IRoom) Proxy.newProxyInstance(classLoader,new Class[]
{ProxyObjectInterface.class},invocationHandler);
proxy.execute();
public class DynamicProxy implements InvocationHandler {
private Object object;
public DynamicProxy(Object object){
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(object,args);
return result;
}
}
Create a proxy newProxyInstance
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
// Inspection h Is not empty, h Throw anomaly for null
Objects.requireNonNull(h);
// Class object copy of interface 1 Portions
final Class<?>[] intfs = interfaces.clone();
// Go on 1 Some security checks
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
* Query (already in the cache) or generate the specified proxy class class Object.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
// Get the constructor of the proxy class object, and the parameters of this constructor are determined by the constructorParams Specify
// Parameter constructorParames Is a constant value:
private static final Class<?>[] constructorParams = { InvocationHandler.class };
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
// The proxy object is generated here, and the passed parameters new Object[]{h} Talk later
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);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
First, the h is judged to be empty.
The core of this code is to get the Class object of proxy class through getProxyClass0 (loader, intfs), and then get the construction method through Class object, and then create proxy object. Look at the getProxyClass0 method in the next step. From 1, we can see that the interface class is obtained from the interface first, and when the number of interfaces exceeds 65535, an exception is reported.
// This method is also Proxy Methods under the class
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
// If the proxy class is specified by the class loader loader Defines and implements the given interface interfaces ,
// Then the cached proxy class object is returned, otherwise, the proxy class object is used ProxyClassFactory Create a proxy class.
return proxyClassCache.get(loader, interfaces);
}
proxyClassCache is a weak reference cache
When you see proxyClassCache here, you will know that it means cache when you have Cache, which just echoes the previous Look up or generate the designated proxy class. Query (already in the cache) or generate an class object for the specified proxy class.
Before entering the get method, let's look at what proxyClassCache is. High-energy warning, the code ahead may look messy, but we only need to pay attention to the key points.
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
//K Representative key The type of, P Represents the type of the parameter, V Representative value The type of.
// WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache Description proxyClassCache The stored value is Class<?> Object, which is exactly the proxy class object we need.
final class WeakCache<K, P, V> {
private final ReferenceQueue<K> refQueue
= new ReferenceQueue<>();
// the key type is Object for supporting null key
private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map
= new ConcurrentHashMap<>();
private final ConcurrentMap<Supplier<V>, Boolean> reverseMap
= new ConcurrentHashMap<>();
private final BiFunction<K, P, ?> subKeyFactory;
private final BiFunction<K, P, V> valueFactory;
public WeakCache(BiFunction<K, P, ?> subKeyFactory,
BiFunction<K, P, V> valueFactory) {
this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
this.valueFactory = Objects.requireNonNull(valueFactory);
}
Where the map variable is the core variable to implement caching, it is a dual Map structure: (key, sub-key)- > value. Where key is the wrapped object of the incoming Classloader, and sub-key is generated by KeyFactory () passed by the WeakCache constructor. value is the object that produces the proxy class, which is generated by ProxyClassFactory (), a descendant of the WeakCache constructor. As follows, review 1:
proxyClassCache is an object of the WeakCache class that calls proxyClassCache. get (loader, interfaces); You can get a cached proxy class or create a proxy class (in the case of no cache).
Explain that get is a method in WeakCache. Look at the definition of WeakCache class first (only the definition and constructor of variables are given here), and continue to look at its get ();
//K And P Is WeakCache Generics in the definition, key Is a class loader, parameter Is an array of interface classes
public V get(K key, P parameter) {
// Check parameter Not empty
Objects.requireNonNull(parameter);
// Clear invalid cache
expungeStaleEntries();
// cacheKey Is (key, sub-key) -> value In 1 Grade key ,
Object cacheKey = CacheKey.valueOf(key, refQueue);
// lazily install the 2nd level valuesMap for the particular cacheKey
// According to 1 Grade key Get ConcurrentMap<Object, Supplier<V>> Object. If it does not exist before, create a new 1 A ConcurrentMap<Object, Supplier<V>> And cacheKey ( 1 Grade key ) 1 Lifting and placing map Medium.
ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
if (valuesMap == null) {
ConcurrentMap<Object, Supplier<V>> oldValuesMap
= map.putIfAbsent(cacheKey,
valuesMap = new ConcurrentHashMap<>());
if (oldValuesMap != null) {
valuesMap = oldValuesMap;
}
}
// create subKey and retrieve the possible Supplier<V> stored by that
// subKey from valuesMap
// This is the part that calls the build sub-key The code of, we have already seen how to generate it above
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
// Pass sub-key Get supplier
Supplier<V> supplier = valuesMap.get(subKey);
//supplier Actually, this is it factory
Factory factory = null;
while (true) {
// If there is a cache supplier , then go directly through get Method, get the proxy class object, return, and it's over. 1 Temporal analysis get Method.
if (supplier != null) {
// supplier might be a Factory or a CacheValue<V> instance
V value = supplier.get();
if (value != null) {
return value;
}
}
// else no supplier in cache
// or a supplier that returned null (could be a cleared CacheValue
// or a Factory that wasn't successful in installing the CacheValue)
// lazily construct a Factory
// The purpose of all the following code is: If there is no supplier Is created 1 A Factory Object, put factory Secure assignment of objects in a multithreaded environment supplier .
// Because it was in while ( true ), go back to the above after the assignment is successful get Method, the return ends.
if (factory == null) {
factory = new Factory(key, parameter, subKey, valuesMap);
}
if (supplier == null) {
supplier = valuesMap.putIfAbsent(subKey, factory);
if (supplier == null) {
// successfully installed Factory
supplier = factory;
}
// else retry with winning supplier
} else {
if (valuesMap.replace(subKey, supplier, factory)) {
// successfully replaced
// cleared CacheEntry / unsuccessful Factory
// with our Factory
supplier = factory;
} else {
// retry with current supplier
supplier = valuesMap.get(subKey);
}
}
}
}
So let's look at the get method in the Factory class. Next, look at get () of supplier
public synchronized V get() { // serialize access
// re-check
Supplier<V> supplier = valuesMap.get(subKey);
// Re-check the results supplier Is the current object
if (supplier != this) {
// something changed while we were waiting:
// might be that we were replaced by a CacheValue
// or were removed because of failure ->
// return null to signal WeakCache.get() to retry
// the loop
return null;
}
// else still us (supplier == this)
// create new value
V value = null;
try {
// This is where the proxy class calls valueFactory Generated
//valueFactory Is what we passed in new ProxyClassFactory()
//1 Will we analyze ProxyClassFactory() Adj. apply Method
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
} finally {
if (value == null) { // remove us on failure
valuesMap.remove(subKey, this);
}
}
// the only path to reach here is with non-null value
assert value != null;
// wrap value with CacheValue (WeakReference)
// Put value Wrapped as a weak reference
CacheValue<V> cacheValue = new CacheValue<>(value);
// put into reverseMap
// reverseMap Is used to realize the validity of cache
reverseMap.put(cacheValue, Boolean.TRUE);
// try replacing us with CacheValue (this should always succeed)
if (!valuesMap.replace(subKey, this, cacheValue)) {
throw new AssertionError("Should not reach here");
}
// successfully replaced us with new CacheValue -> return the value
// wrapped by it
return value;
}
}
To the apply method of ProxyClassFactory, where the proxy class is generated.
First look at the definition of proxyClassCache WeakCache < ClassLoader, Class < ? > [], Class < ? > > In the generics, the first represents the loader K, the second represents the interface class P, and the third is the generated proxy class V. The generation of V is generated by ProxyClassFactory. Call its apply ();
// Here's BiFunction<T, U, R> Is a functional interface, which can be understood as using T , U Two types as parameters, and get R Return value of type
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// prefix for all proxy class names
// Prefix of all proxy class names
private static final String proxyClassNamePrefix = "$Proxy";
// next number to use for generation of unique proxy class names
// Counters used to generate proxy class names
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
// Verify the proxy interface without looking at
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());
}
}
// Package name of the generated proxy class
String proxyPkg = null; // package to define proxy class in
// Proxy class access control character : public ,final
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
/*
* 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.
*/
// Verify that all non-public interfaces are in the same 1 Within a package; Public ones don't need to be dealt with
// Logic for generating package name and class name, and the package name defaults to com.sun.proxy ,
// The class name defaults to $Proxy Plus 1 Self-increasing integer value
// If the proxy class is non-public proxy interface The interface with the proxy class is used 1 Sample package name
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");
}
}
}
if (proxyPkg == null) {
// if no non-public proxy interfaces, use com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
/*
* Choose a name for the proxy class to generate.
*/
long num = nextUniqueNumber.getAndIncrement();
// The fully qualified name of the proxy class, such as com.sun.proxy.$Proxy0.calss
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*
* Generate the specified proxy class.
*/
// The core part, which generates the bytecode of the proxy class
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
// Load the proxy class into the JVM At this point, the dynamic agent process basically ends
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());
}
}
}
Then call getMethod (), adding equals (), hashcode (), toString (), and so on. Then iterate through the methods of all interfaces and add them to the proxy class. Finally, these methods are sorted.
private static List<Method> getMethods(Class<?>[] interfaces) {
List<Method> result = new ArrayList<Method>();
try {
result.add(Object.class.getMethod("equals", Object.class));
result.add(Object.class.getMethod("hashCode", EmptyArray.CLASS));
result.add(Object.class.getMethod("toString", EmptyArray.CLASS));
} catch (NoSuchMethodException e) {
throw new AssertionError();
}
getMethodsRecursive(interfaces, result);
return result;
}
private static void getMethodsRecursive(Class<?>[] interfaces, List<Method> methods) {
for (Class<?> i : interfaces) {
getMethodsRecursive(i.getInterfaces(), methods);
Collections.addAll(methods, i.getDeclaredMethods());
}
}
Finally, the correlation proxy class is output
package com.zhb.jdk.proxy;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Proxy;
import com.zhb.jdk.dynamicProxy.HelloworldImpl;
import sun.misc.ProxyGenerator;
/**
* @author ZHB
* @date 2018 Year 8 Month 31 Sunday afternoon 11:35:07
* @todo TODO
*/
public class DynamicProxyTest {
public static void main(String[] args) {
IUserService target = new UserServiceImpl();
MyInvocationHandler handler = new MyInvocationHandler(target);
// No. 1 1 The parameter is the class loader that specifies the proxy class (we pass in the class loader of the current test class)
// No. 1 2 Parameters are the interfaces that the proxy class needs to implement (we pass in the interface implemented by the proxy class, so that the generated proxy class and the proxy class implement the same interface)
// No. 1 3 The parameters are invocation handler That handles calls to methods. Here we pass in our own implementation handler
IUserService proxyObject = (IUserService) Proxy.newProxyInstance(DynamicProxyTest.class.getClassLoader(),
target.getClass().getInterfaces(), handler);
proxyObject.add(" Old grain ");
String path = "D:/$Proxy0.class";
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", HelloworldImpl.class.getInterfaces());
FileOutputStream out = null;
try {
out = new FileOutputStream(path);
out.write(classFile);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3) fieldsfirst ansi space
import com.zhb.jdk.proxy.IUserService;
import java.lang.reflect.*;
public final class $Proxy0 extends Proxy
implements IUserService
{
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
// The constructor of the proxy class whose parameters are InvocationHandler Example,
//Proxy.newInstance Method is created by using this constructor to create a proxy instance
public $Proxy0(InvocationHandler invocationhandler)
{
super(invocationhandler);
}
// Object Class in the 3 Methods, equals , toString , hashCode
public final boolean equals(Object obj)
{
try
{
return ((Boolean)super.h.invoke(this, m1, new Object[] {
obj
})).booleanValue();
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString()
{
try
{
return (String)super.h.invoke(this, m2, null);
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
// Interface proxy method
public final void add(String s)
{
try
{
// invocation handler Adj. invoke Method is called here
super.h.invoke(this, m3, new Object[] {
s
});
return;
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode()
{
try
{
// Called here invoke Method.
return ((Integer)super.h.invoke(this, m0, null)).intValue();
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
// Static code blocks perform on variables 1 Some initialization work
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
Class.forName("java.lang.Object")
});
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.zhb.jdk.proxy.IUserService").getMethod("add", new Class[] {
Class.forName("java.lang.String")
});
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
}
catch (NoSuchMethodException nosuchmethodexception)
{
throw new NoSuchMethodError(nosuchmethodexception.getMessage());
}
catch (ClassNotFoundException classnotfoundexception)
{
throw new NoClassDefFoundError(classnotfoundexception.getMessage());
}
}
}
Above is JDK Dynamic Agent Steps Detailed Explanation (Source Code Analysis) details, more information about JDK Dynamic Agent Please pay attention to this site other related articles!