How do we use dynamic proxies in the JDK

Suppose you have a class called Girl that needs proxies and that implements the MakeTrouble interface.

The following code

public interface MakeTrouble {
    void makeTrouble(a);
}
Copy the code
public class Girl implements MakeTrouble{
    public void makeTrouble(a){
        System.out.println("girl makes trouble"); }}Copy the code

Create a proxy class, of course, to override the behavior of some methods. You first need to define this new behavior by implementing the InvocationHandler interface.

public class GirlProxy implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        System.out.println("girl proxy makes trouble");
        returnnull; }}Copy the code

The class above, however, is a fake proxy.

Because this class is completely new, except that the name is related to Girl, there is no real connection.

In fact, the agent must modify or add to the behavior of the original method. The original class must be involved.

So when we involve the Girl class, it looks like this.

public class GirlProxy implements InvocationHandler {
    private Girl girl;
    public GirlProxy(Girl girl){
        this.girl = girl;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        girl.makeTrouble();
        System.out.println("girl proxy makes more trouble");
        return null; }}Copy the code

But there’s a problem. If the Girl class has multiple methods that need to be propped,InvocationHandlerThere is only one invoke method. If all methods of the new proxy class invoke this invoke method, then all methods will behave the same.

Invoke can actually delegate the behavior of multiple methods. Which method is specified by the second and third arguments to the Invoke method.

So the correct way to write it is this

public class GirlProxy implements InvocationHandler {
    private Girl girl;
    public GirlProxy(Girl girl){
        this.girl = girl;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
        // Execute the target object's method
        Object result = method.invoke(girl, args);
        System.out.println("proxy method executed");
        return null; }}Copy the code

The behavior of the new proxy class is now defined. Logically, you should be able to create a proxy class for Girl.

The API for creating instances of the proxy class looks like this.

public static Object newProxyInstance(ClassLoader loader, Class
       [] interfaces, InvocationHandler h)
Copy the code

The proxy class is a new class, so you need to specify a ClassLoader. The second parameter defines the methods of the new proxy class. The third parameter defines the behavior of the proxy class.

MakeTrouble a = (MakeTrouble)Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), 
                        Girl.class.getInterfaces(),
                        girlProxy);
Copy the code

An anonymous proxy class that implements MakeTrouble is generated, and the output of the a.makeTrouble() call is as follows.

girl makes trouble
proxy method executed
Copy the code

Internal implementation of dynamic proxy

Take a look at the code for proxy.newProxyInstance

/** * Returns an instance of a proxy class for the specified interfaces * that dispatches method invocations to the specified invocation * handler. */
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader, Class
       [] interfaces, InvocationHandler h)
    throws IllegalArgumentException
{
    Objects.requireNonNull(h);

    finalClass<? >[] intfs = interfaces.clone();final SecurityManager sm = System.getSecurityManager();
    if(sm ! =null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    /* * Create the designated proxy class */Class<? > cl = getProxyClass0(loader, intfs);/* Invoke the designated Invocation handler to instantiate the designated Invocation class */
    try {
        if(sm ! =null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }
        ConstructorParams is a constant
        // private static final Class
      [] constructorParams = { InvocationHandler.class };
        // The proxy class constructor takes one argument
        finalConstructor<? > cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;
        if(! Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run(a) {
                    cons.setAccessible(true);
                    return null; }}); }// Pass the InvocationHandler instance to the proxy class.
        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 newInternalError(t.toString(), t); }}catch (NoSuchMethodException e) {
        throw newInternalError(e.toString(), e); }}Copy the code

A method annotation means that it returns an object that implements a proxy class for the specified interface. Each method of this proxy class is distributed to the InvocationHandler.

That makes it easy. A proxy class will have methods for all interfaces, and the implementation of those methods will be essentially the same. It’s just calling InvocationHandler.

Let’s look at the code to create the proxy class, which is Proxy#getProxyClass0()

/** * Generate a proxy class. Must call the checkProxyAccess method * to perform permission checks before calling this. * /
private staticClass<? > getProxyClass0(ClassLoader loader, Class<? >... interfaces) {// Check that the number of interfaces is not too large
    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
    // We can see that the proxy class has a caching mechanism
    return proxyClassCache.get(loader, interfaces);
}
Copy the code

The definition of proxyClassCache is as follows

/** * a cache of proxy classes */
private static finalWeakCache<ClassLoader, Class<? >[], Class<? >> proxyClassCache =new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
Copy the code

As you can see, the proxy class is created using ProxyClassFactory. So let’s look at the code for ProxyClassFactory.

/** * A factory function that generates, defines and returns the proxy class given * the ClassLoader and array of interfaces. */ private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<? >[], Class<? >> {// prefix for all proxy class names // proxy class name prefix private static Final String proxyClassNamePrefix = "$proxy "; // Next number to use for generation of unique proxy class names $Proxy1 Private Static Final AtomicLong nextUniqueNumber = new AtomicLong(); /** * interfaces create proxy classes */ @override public Class<? > apply(ClassLoader loader, Class<? >[] interfaces) { Map<Class<? >, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); // This loop is a bunch of checks 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()); } } String proxyPkg = null; // package to define proxy class in 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. */ 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 the. * the proxy class name * / long num = nextUniqueNumber. GetAndIncrement (); String proxyName = proxyPkg + proxyClassNamePrefix + num; /* * Generate the specified proxy class. Written to disk * / byte [] proxyClassFile = ProxyGenerator. GenerateProxyClass (proxyName, interfaces, accessFlags); 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()); }}}Copy the code

See ProxyGenerator. GenerateProxyClass ()

public static byte[] generateProxyClass(finalString var0, Class<? >[] var1,int var2) {
    ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
    final byte[] var4 = var3.generateClassFile();
    if (saveGeneratedFiles) {
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run(a) {
                try {
                    int var1 = var0.lastIndexOf(46);
                    Path var2;
                    if (var1 > 0) {
                        Path var3 = Paths.get(var0.substring(0, var1).replace('. ', File.separatorChar));
                        Files.createDirectories(var3);
                        var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                    } else {
                        var2 = Paths.get(var0 + ".class");
                    }

                    Files.write(var2, var4, new OpenOption[0]);
                    return null;
                } catch (IOException var4x) {
                    throw new InternalError("I/O exception saving generated file: "+ var4x); }}}); }return var4;
}
Copy the code

As I said, every method of the proxy class calls InvocationHandler. The InvocationHandler instance is actually a property of the Proxy class. Methods in Proxy are static methods, except for this property. All Proxy classes continue with the Proxy class.

Methods of the proxy class invoke the InvocationHandler property of the parent class.

This explains why dynamic proxies in the JDK must implement interfaces for propped classes.

Because a proxy class cannot be a subclass of a proxied class. Because the Proxy class already continues the Proxy class, multiple inheritance is not supported in Java.