How difficult is it to master java Dynamic Proxy and its principle?

The jdk used is 1.7. You need to understand the reflection mechanism and the concept of generic bytecode registration!

1, Agent mode

Proxy mode is a commonly used java design mode. Its feature is that the proxy class has the same interface with the delegate class. The proxy class is mainly responsible for preprocessing messages for the delegate class, filtering messages, forwarding messages to the delegate class, and post-processing messages. There is usually an association relationship between proxy class and delegate class. The object of a proxy class is associated with the object of a delegate class. The object of the proxy class itself does not really realize the service, but provides a specific service by calling the relevant methods of the object of the delegate class.

2, Write a java Dynamic Proxy

Prepare two interfaces

They are ProductService (production interface) and FactoryService (factory interface)

The code is as follows:

package test.myproxy;

/**
 * <ul>
 * <li>Title: FactoryService</li>
 * </ul>
 *
 * [@author](https://my.oschina.net/arthor) ken
 * [@date](https://my.oschina.net/u/2504391) 2021/4/13 0013 10:05 am
 */
public interface FactoryService {

    void addProduce(int num);

}

package test.myproxy;

/**
 * <ul>
 * <li>Title: ProductService</li>
 * </ul>
 *
 * [@author](https://my.oschina.net/arthor) ken
 * [@date](https://my.oschina.net/u/2504391) 2021/4/12 0012 17:46 PM
 */
public interface ProductService {

    /**
     * Add product
     * [@param](https://my.oschina.net/u/2303379) productName
     */
    void addProduct(String productName);

}

2. MyServiceImpl class implements the above two interfaces

package test.myproxy;

/**
 * <ul>
 * <li>Title: MyServiceImpl</li>
 * <li>Description: TODO </li>
 * </ul>
 *
 * @author ken
 * @date 2021/4/12 0012 17:47 PM
 */
public class MyServiceImpl implements ProductService,FactoryService {

    @Override
    public void addProduct(String productName) {
        System.out.println("Adding"+productName);
    }


    @Override
    public void addProduce(int num) {
        System.out.println("Ready to build"+num+"Pieces of goods");
    }
}


3. Realize the function of dynamic agent

3.1 the interface InvocationHandler needs to be implemented in JDK and the invoke method needs to be rewritten
3.2 in many articles, in order to facilitate understanding, the parameters of target and getInstance in the following code are written as actual interfaces, such as ProductService above. I don't want to write this here, so that you can understand generics. In the interface of getInstance, I also clearly pointed out that the parameters are the inheritance relationship / implementation interface relationship with the proxy object.

The code is as follows:

package test.myproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * <ul>
 * <li>Title: JdkInvocationHandler</li>
 * <li>Description: TODO </li>
 * </ul>
 *
 * @author ken
 * @date 2021/4/12 0012 17:45 PM
 */
public class JdkInvocationHandler<T> implements InvocationHandler {
    //Proxy object
    private T target;
		
    public <B extends T> T getInstance(B target){
        this.target = target;
        Class clazz = this.target.getClass();
        // Parameter 1: class loader parameter of the proxy class 2: interface parameter of the proxy class 3
        return (T)Proxy.newProxyInstance(clazz.getClassLoader(),
                clazz.getInterfaces(),
                this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        String currentDate  = simpleDateFormat.format(new Date());
        System.out.println("Date["+currentDate + "]Added a product");
        return method.invoke(target,args);

    }
}

3.3 testing

The example here uses two methods: generic and strong conversion:

 public static void main(String[] args) throws Exception {
        ProductService proxy =  new JdkInvocationHandler<ProductService>().getInstance(new MyServiceImpl());
        proxy.addProduct("iphone");
				
        FactoryService proxyFactory = (FactoryService) new JdkInvocationHandler().getInstance(new MyServiceImpl());
        proxyFactory.addProduce(500);

        // Here, we output the proxy class generated by jdk for later analysis and use
       /* byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{productService.getClass()});
        FileOutputStream os = new FileOutputStream("Proxy0.class");
        os.write(bytes);
        os.close();*/
    }

4. Analyze how to generate dynamic agents

The above jdkivocationhandler class implements the InvocationHandler interface. The key line of code in the getInstance method is proxy newProxyInstance(clazz.getClassLoader(),clazz. getInterfaces(), this);

Follow this code to further analyze the content:

Here is the location: Java Lang.reflect class: Proxy

There are some member properties in this class. Let's first understand some of them that will be used later.

4.1 contents of newproxyinstance method

(delete some notes for simplicity)

 public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)
        throws IllegalArgumentException
    {
        if (h == null) {
            throw new NullPointerException();
        }

        /*
         * Finds or generates the specified proxy class.
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, interfaces); // stack walk magic: do not refactor
        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            SecurityManager sm = System.getSecurityManager();
            if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
                // create proxy instance with doPrivilege as the proxy class may
                // implement non-public interfaces that requires a special permission
                return AccessController.doPrivileged(new PrivilegedAction<Object>() {
                    public Object run() {
                        return newInstance(cons, ih);
                    }
                });
            } else {
                return newInstance(cons, ih);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString());
        }
    }

This code dynamically generates a reflection Class according to the constructor parameters through the Class information c1. It is very important here that in the line of getProxyClass0, the English meaning on the comment means to run on the heap, and this part of the code should not be refactored. We can also know that this part is very important. Let me show the code inside:

4.2 contents of getproxyclass0 method

(this is the comment on the getProxyClass0 method)

private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            final int CALLER_FRAME = 3; // 0: Reflection, 1: getProxyClass0 2: Proxy 3: caller
            final Class<?> caller = Reflection.getCallerClass(CALLER_FRAME);
            final ClassLoader ccl = caller.getClassLoader();
            checkProxyLoader(ccl, loader);
            ReflectUtil.checkProxyPackageAccess(ccl, interfaces);
        }

        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        Class<?> proxyClass = null;
        String[] interfaceNames = new String[interfaces.length];

        // for detecting duplicates
        Set<Class<?>> interfaceSet = new HashSet<>();

        for (int i = 0; i < interfaces.length; i++) {
            String interfaceName = interfaces[i].getName();
            Class<?> interfaceClass = null;
            try {
                interfaceClass = Class.forName(interfaceName, false, loader);
            } catch (ClassNotFoundException e) {
            }
            if (interfaceClass != interfaces[i]) {
                throw new IllegalArgumentException(
                    interfaces[i] + " is not visible from class loader");
            }

            if (!interfaceClass.isInterface()) {
                throw new IllegalArgumentException(
                    interfaceClass.getName() + " is not an interface");
            }

            if (interfaceSet.contains(interfaceClass)) {
                throw new IllegalArgumentException(
                    "repeated interface: " + interfaceClass.getName());
            }
            interfaceSet.add(interfaceClass);

            interfaceNames[i] = interfaceName;
        }

        List<String> key = Arrays.asList(interfaceNames);

        /*
         * Find or create the proxy class cache for the class loader.
         */
        Map<List<String>, Object> cache;
        synchronized (loaderToCache) {
            cache = loaderToCache.get(loader);
            if (cache == null) {
                cache = new HashMap<>();
                loaderToCache.put(loader, cache);
            }
        }

        synchronized (cache) {
            do {
                Object value = cache.get(key);
                if (value instanceof Reference) {
                    proxyClass = (Class<?>) ((Reference) value).get();
                }
                if (proxyClass != null) {
                    // proxy class already generated: return it
                    return proxyClass;
                } else if (value == pendingGenerationMarker) {
                    // proxy class being generated: wait for it
                    try {
                        cache.wait();
                    } catch (InterruptedException e) {
                    }
                    continue;
                } else {

                    cache.put(key, pendingGenerationMarker);
                    break;
                }
            } while (true);
        }

        try {
            String proxyPkg = null;     // package to define proxy class in
            for (int i = 0; i < interfaces.length; i++) {
                int flags = interfaces[i].getModifiers();
                if (!Modifier.isPublic(flags)) {
                    String name = interfaces[i].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 + ".";
            }
            {

                long num;
                synchronized (nextUniqueNumberLock) {
                    num = nextUniqueNumber++;
                }
                String proxyName = proxyPkg + proxyClassNamePrefix + num;
                byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                    proxyName, interfaces);
                try {
                    proxyClass = defineClass0(loader, proxyName,
                        proxyClassFile, 0, proxyClassFile.length);
                } catch (ClassFormatError e) {
                    throw new IllegalArgumentException(e.toString());
                }
            }
            // add to set of all generated proxy classes, for isProxyClass
            proxyClasses.put(proxyClass, null);

        } finally {

            synchronized (cache) {
                if (proxyClass != null) {
                    cache.put(key, new WeakReference<Class<?>>(proxyClass));
                } else {
                    cache.remove(key);
                }
                cache.notifyAll();
            }
        }
        return proxyClass;
    }


Let's analyze this Code: the meaning of this code

4.2.1. If the interfaces implemented by the class exceed 65535, the exception of "interface limit exceeded" is thrown directly. This part should be for performance. Generally, most of our implemented interfaces are less than 200, which can be ignored
4.2.2 instantiate all interfaces of the class and put them into the set, and then find or create the Proxy class cache of the class loader (loaderToCache is the cache storing the class loader, key is the class loader, value is map < list < string >, Object >, and the key of the map is the set of interfaces implemented by the class), The following is to set the package name of the Proxy class. If there is a non public interface [that is, the package name of this class is used for the interface package name, otherwise the default package name is com.sun.proxy, the class name is usually the class name prefix + num, and the class name prefix is $Proxy, which will be mentioned later], the final return is to generate the Proxy class [note that the Proxy class name is automatically generated], and then the method can be called with "this class name"
4.2.3 instantiation of all interfaces of class
  Class <?> interfaceClass = Class.forName(interfaceName, false, loader);
4.2.4 all the interfaces implemented by the class are put into the collection, which is later
		List<String> key = Arrays.asList(interfaceNames);
4.2.5 find or create proxy class cache of class loader.
 Map<List<String>, Object> cache;
        synchronized (loaderToCache) {
            cache = loaderToCache.get(loader);
            if (cache == null) {
                cache = new HashMap<>();
                loaderToCache.put(loader, cache);
            }
            /*
             * This mapping will remain valid for the duration of this
             * method, without further synchronization, because the mapping
             * will only be removed if the class loader becomes unreachable.
             */
        }
4.2.6 this part is to record the number of agents currently created to prevent duplication of "class name" of the generated agent class
				long num;
                synchronized (nextUniqueNumberLock) {
                    num = nextUniqueNumber++;
                }
                String proxyName = proxyPkg + proxyClassNamePrefix + num;
4.2.7 this block is the bytecode of creating proxy class. defineClass0 generates proxy class through class loader, proxy class name and bytecode.
		byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);
		proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);

[local method of c language to be called]

ps: proxygenerator is used in many articles Generateproxyclass method, and then use the file output stream to write the bytecode content into the file. After reading here, you should know why it is written like this.

Let's take a brief look at the output

All methods of this class are created by "reflection", and the targeted method is called when it is actually called.

Finally, we can see that a reflection is nothing more than giving its own "all methods" to another "out of thin air" object to use its own "rights".

This article comes from: programmer ken. The exclusive platforms include csdn, segment fault, Jianshu, oschina and nuggets. Please indicate the source for reprint.

Keywords: Java JDK Design Pattern f2c aslist

Added by chris_2001 on Mon, 07 Mar 2022 23:03:39 +0200