编程知识 cdmana.com

JDK dynamic proxy

There are three kinds of agency : Static proxy 、JDk A dynamic proxy 、CGLib A dynamic proxy ; The proxy model we often talk about is static proxy only ;mybatis Of mapper The main application of the interface is JDK A dynamic proxy ;Spring Of AOP Main application JDk Dynamic proxy sum CGLib A dynamic proxy . This paper mainly introduces JDK A dynamic proxy .

Static proxy

principle

Static proxies are represented by the delegate class , proxy class , The interface consists of three parts . Both the proxy class and the proxy class need to implement the interface , The proxy class implements the main business methods , The proxy class calls specific business methods by creating a business implementation class object .

Realization
1、 Define an interface , Both the proxy class and the proxy class need to implement this interface

/**
 *  Define an interface for a marriage candidate 
 */
public interface staticPerson {
    //  Looking for true love 
    void findLove();
}

2、 Define the surrogate class Zhang San , Realize the object interface of marriage seeking

public class staticZhangsan implements staticPerson{

    private String sex = " Woman ";
    private String name = " Zhang San ";

    // Abstract methods in implementation interface 
    @Override
    public void findLove() {
        System.out.println(" My name is " + this.name + ", Gender :" + this.sex + ", My request for a partner is \n");
        System.out.println(" Grosvenor LTD handsome ");
        System.out.println(" There are rooms and cars ");
        System.out.println(" The height requirement is 1.8 meters ");
    }

    get/set...
}

3、 Define the matchmaker proxy class , By combination , Create a business implementation class object in the proxy class to call specific business methods

public class staticMeipo implements staticPerson{

    // By combination , Create a surrogate class object to call specific business methods 
    private staticPerson staticPerson = new staticZhangsan();

    @Override
    public void findLove() {
        // Pretreatment operation 
        System.out.println(" I'm a matchmaker :" + " I have to find a man for you ");
        System.out.println(" Start audition ");
        System.out.println("--------------------------------");
        // Call real business methods 
        staticPerson.findLove();
        // Call post-processing 
        System.out.println("--------------------------------");
        System.out.println(" How to be suitable to love ");
    }
}

4、 test , When use , First create the surrogate class object , Then the proxy class object is used as a construction parameter to create a proxy class object , Finally, the business method is called through the proxy class object .

public static void main(String[] args){
        // Create the proxied class object 
        staticZhangsan staticZhangsan = new staticZhangsan();
        // Create a proxy class object , And take the surrogate class object as the constructor 
        staticMeipo staticMeipo = new staticMeipo(staticZhangsan);
        // Business method calls through proxy objects 
        staticMeipo.findLove();
    }

We can see that the advantage of static is that it is easy to complete the proxy operation on a class . But the disadvantages are obvious , Proxy classes can only implement one interface , So a proxy class can only serve one class , If there are many classes that need proxy , Then you need to define multiple proxy classes ;
and , If the proxy class preprocesses business methods 、 The post call operations are the same ( such as : Output prompt before calling 、 Automatically close the connection after calling ), Multiple proxy classes will have a lot of duplicate code .

So is there a way to solve this problem ? Dynamic agents can , Define a proxy class , It can proxy method calls of all implementation classes : Call according to the business implementation class and method name passed in . This is the dynamic proxy .

JDK A dynamic proxy

JDK The proxy class used by dynamic proxy is only used when the program calls the proxy class object JVM Actually creating ,JVM According to what was passed in Business implementation class object as well as Method name , Dynamically creates a proxy class's class File and executed by bytecode engine , Then make method calls through the proxy class object . What we need to do , You only need to specify the preprocessing of the proxy class 、 Operation after calling .
To make a comparison with static , We use the same structure as static .

1、 Define an interface , The proxy class needs to implement this interface , What's different from static is , Proxy does not need to implement this interface

public interface Person {

    //  Looking for true love 
    void findLove();
    
}

2、 Define the surrogate class Zhang San , Realize the object interface of marriage seeking

public class Zhangsan implements Person{

    private String sex = " Woman ";
    private String name = " Zhang San ";

    @Override
    public void findLove() {
        System.out.println(" My name is " + this.name + ", Gender :" + this.sex + ", My request for a partner is \n");
        System.out.println(" Grosvenor LTD handsome ");
        System.out.println(" There are rooms and cars ");
        System.out.println(" The height requirement is 1.8 meters ");
    }
    
   get/set...
}

3、 Define the matchmaker proxy class , By combination , Create a business implementation class object in the proxy class to call specific business methods , Different from static proxy, the interface implemented here is InvocationHandler
InvocationHandler What is it for ?InvocationHandler Is the core of dynamic agents . Each proxy class has an associated call handler (InvocationHandler). When a call is made to a proxy instance , The call to the method is encoded and assigned to its call handler (InvocationHandler) Of invoke Method . So the call to the proxy object instance method is through InvocationHandler Medium invoke Method to complete , and invoke Method based on the passed in proxy object 、 The method name and parameters determine which method to call the agent .

public class Meipo implements InvocationHandler {

    // Agent object 
    private Person target;

    /**
     *  Bind the business object and return a proxy class 
     *  Popular understanding is to obtain the personal data of the principal 
     */
    public Object getInstance(Person target){
        this.target = target;
        /**
         *  Method calls for all dynamic proxy classes , All will be handed over to InvocationHandler Interface implementation class invoke() How to deal with . This is the key to dynamic proxy .
         * 
         *  The first parameter :ClassLoader   Class loader , Metadata used to obtain business implementation classes , The wrapping method is to call the real business method 
         *  The second parameter :Class<?>[] interfaces   Interface 
         *  The third parameter :InvocationHandler   Who is acting for you , That is agent class meipo
         */
        Class clazz = target.getClass(); // Get the interface with reflection      //1、 Get the reference of the represented object , And get his interface 
        System.out.println(clazz);
        return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }


   /**
     *  Override wrapper call method : Pre treatment 、 Call post-processing 
     * 
     *  The first parameter :Object  This parameter specifies the dynamically generated proxy class 
     *  The second parameter :Method   This parameter represents all of the Method object    namely InvocationHandler.invoke() Method 
     *  The third parameter :args   This parameter corresponds to the current method Method 
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // Preprocessing and post call processing are collectively referred to as enhancement operations , stay Spring AOP in , A typical application scenario is to log operations before and after the execution of some sensitive methods .

        // Pretreatment operation 
        System.out.println(" I'm a matchmaker :" + " I have to find a man for you ");
        System.out.println(" Start audition ");
        System.out.println("--------------------------------");
        // Call real business methods 
        this.target.findLove();
        // Call post-processing 
        System.out.println("--------------------------------");
        System.out.println(" How to be suitable to love ");
        return null;
    }
}

4、 test , Define interface references ( The proxy generated object is forced to switch to the interface ) And use the proxy object .getInstance( Business implementation class object ) The return value of . What the port reference really points to is a proxy class object bound to a business class , So it is the proxy methods that are called by the interface method name

public static void main(String[] args) throws Exception{
       
        /**
         *  The proxy object here object It's not Zhang San , It is $proxy0(class com.sun.proxy.$Proxy0)
         *  The class of Zhang San's object should be :class com.example.demo.proxy.Zhangsan
         */
        Person object = (Person) new Meipo().getInstance(new Zhangsan());
        // Call business method through interface reference 
        object.findLove();// Dynamically proxy which method to call 

    }

Through comparison, we find the following significant differences :

1、 Implementation interface : Both the proxy class and the proxy class of static proxy implement the marriage object interface ;JDK The surrogate class of dynamic proxy implements the marriage object interface , The proxy class implements InvocationHandler Interface ;
2、 Proxy class implementation : Static proxy classes use new The real business method is called by the proxy class object ; and JDK Dynamic proxy is in getInstense The method is through Proxy.newProxyInstance Create and return a generated proxy class , And rewriting InvocationHandler In the abstract method of interface , Call the business method with the proxy class object .
3、 When use : The static proxy finally calls the business method with the proxy object .JDk The dynamic proxy finally uses the generated proxy class object with the business method .

Static agents and JDK Dynamic proxy differences

Static agents are created by direct encoding : That is to say, in static proxy, we need to create proxy class for which interface and which proxy class , So we need the proxy class to implement the same interface as the proxy class before compiling , And directly call the corresponding method of the agent class in the implementation method ;
JDK Dynamic proxy uses reflection mechanism to create proxy class at runtime : That is to say, we don't know which interface to target , Which proxy class is created by proxy class , Because it was created at runtime

How can proxy classes be created dynamically ?

stay JDK Dynamic proxy Meipo The proxy class getInsgetInstance In the method , We can see that it's through Proxy.newProxyInstance() To return the generated proxy class , Let's go in and have a look :

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        // If h Empty will throw an exception 
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();// Copy some interfaces implemented by proxy class , For later checks on permissions 
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            // Check some security permissions here , Make sure that we have permission to proxy the expected proxied class 
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        //------------------------------------------
        //  The following method will generate the proxy class 
        Class<?> cl = getProxyClass0(loader, intfs);
        //------------------------------------------
        
        /*
         *  Get the constructor object of the proxy class using the specified call handler 
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            // If the constructor of the proxy class is private Of , Just use reflection set accessible
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            // According to the constructor of the proxy class, generate the object of the proxy class and return 
            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);
        }
    }

So the proxy class is actually through getProxyClass Method to generate :

  /**
     * Generate a proxy class.  Must call the checkProxyAccess method
     * to perform permission checks before calling this.
     *  Generate proxy class . Must call CheckProxyAccess Method 
     *  Perform permission checks before calling .
     */
    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 a proxy class defined by a given loader implementation 
        // The given interface exists , This will only return the cached copy ;
        // otherwise , It will pass. proxyClassFactory Create a proxy class .
        return proxyClassCache.get(loader, interfaces);
    }

From the source code annotation, we can see that , If there is a corresponding proxy class in the cache , Then go straight back , Otherwise, the proxy class will be ProxyClassFactory To create . that ProxyClassFactory What is it? ?

/**
     *   There's a given ClassLoader and Interface To create the factory function of the proxy class   
     *
     */
    private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
    {
        //  The prefix of the name of the proxy class is the same as “$Proxy”
        private static final String proxyClassNamePrefix = "$Proxy";

        //  Each proxy class prefix is followed by a unique number , Such as $Proxy0、$Proxy1、$Proxy2
        private static final AtomicLong nextUniqueNumber = new AtomicLong();

        @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            for (Class<?> intf : interfaces) {
                /*
                 *  Verify that the class loader loads the interface to get whether the object is associated with the apply Function parameters are passed in the same 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 this Class Object is not an interface 
                 */
                if (!interfaceClass.isInterface()) {
                    throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
                }
                /*
                 *  Verify that this interface is duplicated 
                 */
                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                    throw new IllegalArgumentException(
                        "repeated interface: " + interfaceClass.getName());
                }
            }

            String proxyPkg = null;     //  Declare the... Where the proxy class is located package
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

            /*
             *  Record a package with a non-public proxy interface , To define the proxy class in the same package . Verify all nonpublic 
             *  The proxy interfaces are all 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 it's all public agent interfaces , So the generated proxy class is com.sun.proxy package Next 
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }

            /*
             *  Generate a... For the proxy class name  package name +  Prefix + Unique number 
             *  Such as  com.sun.proxy.$Proxy0.class
             */
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            //---------------------------------------------
            /*
             *  Generate the bytecode file of the specified proxy class 
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            //---------------------------------------------
            try {
                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());
            }
        }
    }

From the above code we can see , Proxy class bytecode files are created by ProxyGenerate Class generateProxyClass Method to complete ,generateProxyClass Method finally returns the proxy class bytecode file .

public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
        ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
       //  The method used to generate the proxy bytecode file is here 
        final byte[] var4 = var3.generateClassFile();
       //  Save the bytecode file of the proxy class 
        if(saveGeneratedFiles) {
            AccessController.doPrivileged(new PrivilegedAction() {
                public Void run() {
                    try {
                        int var1 = var0.lastIndexOf(46);
                        Path var2;
                        if(var1 > 0) {
                            Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar), 
                                                                                   new String[0]);
                            Files.createDirectories(var3, new FileAttribute[0]);
                            var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                        } else {
                            var2 = Paths.get(var0 + ".class", new String[0]);
                        }

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

        return var4;
    }

Let's take a look at the actual production of proxy class bytecode files generateClassFile() Method

private byte[] generateClassFile() {
        // Here's a series of addProxyMethod Method is to combine the methods in the interface with Object The methods in are added to the proxy methods (proxyMethod)
        this.addProxyMethod(hashCodeMethod, Object.class);
        this.addProxyMethod(equalsMethod, Object.class);
        this.addProxyMethod(toStringMethod, Object.class);
        Class[] var1 = this.interfaces;
        int var2 = var1.length;

        int var3;
        Class var4;
       // Get all the methods in the interface and add them to the proxy methods 
        for(var3 = 0; var3 < var2; ++var3) {
            var4 = var1[var3];
            Method[] var5 = var4.getMethods();
            int var6 = var5.length;

            for(int var7 = 0; var7 < var6; ++var7) {
                Method var8 = var5[var7];
                this.addProxyMethod(var8, var4);
            }
        }

        Iterator var11 = this.proxyMethods.values().iterator();
        // Verify that the return types of methods with the same method signature are consistent 
        List var12;
        while(var11.hasNext()) {
            var12 = (List)var11.next();
            checkReturnTypes(var12);
        }

        // The next series of steps are for writing proxy classes Class file 
        Iterator var15;
        try {
             // Build the constructor of the proxy class 
            this.methods.add(this.generateConstructor());
            var11 = this.proxyMethods.values().iterator();

            while(var11.hasNext()) {
                var12 = (List)var11.next();
                var15 = var12.iterator();

                while(var15.hasNext()) {
                    ProxyGenerator.ProxyMethod var16 = (ProxyGenerator.ProxyMethod)var15.next();
                    // Declare the proxy class field as Method, And the field modifier is  private static.
                   // because  10  yes  ACC_PRIVATE and ACC_STATIC And operations   So the fields of the agent class are  private static Method ***
                    this.fields.add(new ProxyGenerator.FieldInfo(var16.methodFieldName, 
                                   "Ljava/lang/reflect/Method;", 10));
                   // Methods for generating proxy classes 
                    this.methods.add(var16.generateMethod());
                }
            }
           // Generate static code blocks for proxy classes to initialize some fields 
            this.methods.add(this.generateStaticInitializer());
        } catch (IOException var10) {
            throw new InternalError("unexpected I/O Exception", var10);
        }

        if(this.methods.size() > '\uffff') { // The number of methods in the proxy class exceeds 65535 Just throw exceptions 
            throw new IllegalArgumentException("method limit exceeded");
        } else if(this.fields.size() > '\uffff') {//  The number of fields in the proxy class exceeds 65535 And throw exceptions 
            throw new IllegalArgumentException("field limit exceeded");
        } else {
            //  After that is the process of processing files 
            this.cp.getClass(dotToSlash(this.className));
            this.cp.getClass("java/lang/reflect/Proxy");
            var1 = this.interfaces;
            var2 = var1.length;

            for(var3 = 0; var3 < var2; ++var3) {
                var4 = var1[var3];
                this.cp.getClass(dotToSlash(var4.getName()));
            }

            this.cp.setReadOnly();
            ByteArrayOutputStream var13 = new ByteArrayOutputStream();
            DataOutputStream var14 = new DataOutputStream(var13);

            try {
                var14.writeInt(-889275714);
                var14.writeShort(0);
                var14.writeShort(49);
                this.cp.write(var14);
                var14.writeShort(this.accessFlags);
                var14.writeShort(this.cp.getClass(dotToSlash(this.className)));
                var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));
                var14.writeShort(this.interfaces.length);
                Class[] var17 = this.interfaces;
                int var18 = var17.length;

                for(int var19 = 0; var19 < var18; ++var19) {
                    Class var22 = var17[var19];
                    var14.writeShort(this.cp.getClass(dotToSlash(var22.getName())));
                }

                var14.writeShort(this.fields.size());
                var15 = this.fields.iterator();

                while(var15.hasNext()) {
                    ProxyGenerator.FieldInfo var20 = (ProxyGenerator.FieldInfo)var15.next();
                    var20.write(var14);
                }

                var14.writeShort(this.methods.size());
                var15 = this.methods.iterator();

                while(var15.hasNext()) {
                    ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo)var15.next();
                    var21.write(var14);
                }

                var14.writeShort(0);
                return var13.toByteArray();
            } catch (IOException var9) {
                throw new InternalError("unexpected I/O Exception", var9);
            }
        }
    }

Here is the interface with Object Some of the methods in are added to the proxy class addProxyMethod Method :

private void addProxyMethod(Method var1, Class<?> var2) {
        String var3 = var1.getName();// Get the method name 
        Class[] var4 = var1.getParameterTypes();// Get method parameter type 
        Class var5 = var1.getReturnType();// Get method return type 
        Class[] var6 = var1.getExceptionTypes();// Exception types 
        String var7 = var3 + getParameterDescriptors(var4);// Get method signature 
        Object var8 = (List)this.proxyMethods.get(var7);// According to the method, get proxyMethod Of value
        if(var8 != null) {// Handle duplicate methods in multiple proxy interfaces 
            Iterator var9 = ((List)var8).iterator();

            while(var9.hasNext()) {
                ProxyGenerator.ProxyMethod var10 = (ProxyGenerator.ProxyMethod)var9.next();
                if(var5 == var10.returnType) {
                    ArrayList var11 = new ArrayList();
                    collectCompatibleTypes(var6, var10.exceptionTypes, var11);
                    collectCompatibleTypes(var10.exceptionTypes, var6, var11);
                    var10.exceptionTypes = new Class[var11.size()];
                    var10.exceptionTypes = (Class[])var11.toArray(var10.exceptionTypes);
                    return;
                }
            }
        } else {
            var8 = new ArrayList(3);
            this.proxyMethods.put(var7, var8);
        }

        ((List)var8).add(new ProxyGenerator.ProxyMethod(var3, var4, var5, var6, var2, null));
    }

Here we are , Proxy class object generation .

版权声明
本文为[Wooden pine cat]所创,转载请带上原文链接,感谢

Scroll to Top