원본 분석 JDK 동적 에이전트의 본질

13493 단어

jdk 동적 에이전트


일반 코드 크로스 정의 인터페이스
public interface ProxyInterface {
    void method();
}

인터페이스 구현 클래스 정의
public class ProxyInterfaceImpl implements ProxyInterface {
    @Override
    public void method() {
        System.out.println("ProxyInterfaceImpl method");
    }
}

사용자 정의handler, 사용자 정의 에이전트의 업무 논리 완성
public class ProxyHandler implements InvocationHandler {
    Object proxyObject =null;
    public ProxyHandler(Object object){
        this.proxyObject = object;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("    ");
        method.invoke(proxyObject,args);
        System.out.println("    ");
        return null;
    }

    public static void main(String[] args) {
        ProxyInterfaceImpl proxyInterface = new ProxyInterfaceImpl();
        ProxyHandler proxyHandler= new ProxyHandler(proxyInterface);
        ProxyInterface proxyInterface1 =(ProxyInterface) Proxy.newProxyInstance(proxyHandler.getClass().getClassLoader(),proxyInterface.getClass().getInterfaces(),proxyHandler);
        proxyInterface1.method();
    }
}


핵심 코드는 모두 다음과 같은 몇 가지 일을 했다

  • ProxyHandler의 invoke 방법에 추가할 업무 논리를 적으세요. 출력을 두 개 썼어요
  • Proxy 클래스의 정적 방법인 newProxyInstance를 통해 프록시 클래스를 생성합니다.
  • 에이전트를 호출하는 방법
  • 저희가 먼저 에이전트의 생성부터 분석을 해볼게요.
         /** Returns an instance of a proxy class for the specified interfaces
         * that dispatches method invocations to the specified invocation
         * handler.*/
        public static Object newProxyInstance(ClassLoader loader,
                                              Class>[] interfaces,
                                              InvocationHandler h)
    

    문서의 뜻: 이 방법은 특정한 인터페이스(그 방법은 특정한handler에 분배되어 번역이 정확하지 않음)를 위해 프록시 실례 방법이 받아들인 파라미터를 되돌려줍니다.
  • 생성된 프록시 클래스를 불러올 클래스를 지정합니다
  • 프록시 클래스가 구현한 인터페이스
  • 비즈니스 논리를 실현하는 InvocationHandler
  • newProxyInstance 메소드가 두 가지를 수행했습니다.
  • 에이전트 클래스의class 대상을 얻기(이것은 우리가 주목하는 중점이다. 에이전트 클래스의class 대상을 어떻게 생성하는지, 그 안의 방법 논리는 어떠한지, 이것을 얻은 후에 다음에 실례화하고 방법은 호출하면 된다)
  • 이class 대상을 실례화
  • 프록시 클래스의class 대상 생성 논리

     Class> cl = getProxyClass0(loader, intfs); //        
    
        /**
         * Generate a proxy class.  Must call the checkProxyAccess method
         * to perform permission checks before calling this.
         */
        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
            return proxyClassCache.get(loader, interfaces);
        }
    
    

    캐시를 사용했습니다. 캐시 생성은proxy에 있습니다.
        /**
         * a cache of proxy classes
         */
        private static final WeakCache[], Class>>
            proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
    

    프록시 클래스의 생성 논리는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[], Class>>
        {
            // prefix for all proxy class names
            private static final String proxyClassNamePrefix = "$Proxy";
            @Override
            public Class> apply(ClassLoader loader, Class>[] interfaces) {
              /*
                 * Choose a name for the proxy class to generate.
                 */
                long num = nextUniqueNumber.getAndIncrement();
                String proxyName = proxyPkg + proxyClassNamePrefix + num;
    
                /*
                 * Generate the specified proxy class.
                 */
                byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                    proxyName, interfaces, accessFlags);
                try {
                    return defineClass0(loader, proxyName,
                                        proxyClassFile, 0, proxyClassFile.length);
                } catch (ClassFormatError e) {
    
                }
            }
        }
    
    

    주로 다음과 같은 작업을 수행했습니다.
  • 프록시 클래스에name을 생성합니다. 프록시 클래스name에 $proxy
  • 가 포함되어 있음을 볼 수 있습니다.
  • 바이트 그룹을 생성하는데 말하자면 바이트 코드 파일이기 때문에 프록시 클래스 생성의 핵심은 바로 여기에 있다.ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags);이 방법은 우리가 필요로 하는 바이트 코드를 어떻게 만드는가.
  • 프록시 클래스class 대상을 되돌려주면 이class 대상을 통해 프록시 클래스를 실례화하고 방법을 호출할 수 있습니다.

  • 다음에는 ProxyGenerator에 중점을 두겠습니다.generateProxyClass () 는 주로 다음 코드에서 이루어진다
     private byte[] generateClassFile() {
            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;
            //          
            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();
            //            
            Iterator var15;
            try {
                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();
                      //       
                        this.fields.add(new ProxyGenerator.FieldInfo(var16.methodFieldName, "Ljava/lang/reflect/Method;", 10));
                        this.methods.add(var16.generateMethod());
                    }
                }
    
                this.methods.add(this.generateStaticInitializer());
            } catch (IOException var10) {
                throw new InternalError("unexpected I/O Exception", var10);
            }
    
            if (this.methods.size() > 65535) {
                throw new IllegalArgumentException("method limit exceeded");
            } else if (this.fields.size() > 65535) {
                throw new IllegalArgumentException("field limit exceeded");
            } else {
                this.cp.getClass(dotToSlash(this.className));
                this.cp.getClass("java/lang/reflect/Proxy");
                var1 = this.interfaces;
                var2 = var1.length;
                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);
                }
            }
        }
    
    

    이렇게 긴 코드가 도대체 무엇을 하고 있는지 정리해 보자. 사실 자바 바이트 코드의 격식에 대해 알고 있다면 한눈에 알 수 있다. 이 코드는 바이트 코드를 조합하여 방법, 필드, 상수도 등 정보를 순서대로 격식에 따라 하나씩 조합한다.예를 들면var14.writeInt(-889275714);첫 번째 필드, 4바이트를 쓰는 거야. 제인이 정의한 마수잖아.
    에이전트가 어떻게 생성되는지 알게 되면 생성된 에이전트가 어떻게 생겼는지 살펴보자
    package com.sun.proxy;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    
    public final class $Proxy0 extends Proxy implements ProxyInterface {
        private static Method m1;
        private static Method m2;
        private static Method m3;
        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 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 void method() throws  {
            try {
                super.h.invoke(this, m3, (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"));
                m2 = Class.forName("java.lang.Object").getMethod("toString");
                m3 = Class.forName("com.czj.proxy.ProxyInterface").getMethod("method");
                m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            } catch (NoSuchMethodException var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    }
    
    

    프록시 클래스는proxy 클래스를 계승하기 때문에 jdk의 동적 프록시는 계승을 통해 실현할 수 없습니다.저희는 주로 두 가지 방법에 주목을 해요.
  • 구조 방법.
  • method, 우리가 정의한 방법.

  • 구조 방법은 InvocationHandler를 받아들인다. 이handler는 우리가 실례화할 때 전송된 것이고 부류인 Proxy의 구조 방법을 호출한다.우리 스스로가 이룬method, 슈퍼를 보자.h.invoke(this, m3, (Object[])null);말하자면 핸들러를 실행하는 invoke 방법이야.
    여기는 틀림없이 우리가 정의한handler를 실행하고 코드를 붙인 것이다.
    public class ProxyHandler implements InvocationHandler {
        Object proxyObject =null;
        public ProxyHandler(Object object){
            this.proxyObject = object;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("    ");
            method.invoke(proxyObject,args);
            System.out.println("    ");
            return null;
        }
    
    

    여기서 invoke 방법 중의 invoke는 반사 원리를 사용했다. 그 중에서 매개 변수 method는 인터페이스 ProxyInterface 성명 방법의 대상이고 proxyObject는 이 인터페이스의 실현 클래스이다. 반사 원리를 이해하지 못한 친구들도 여기가 우리 인터페이스의 실현 클래스 ProxyObject를 호출하는 method 방법이라는 것을 알고 있을 것이다.
    자, 끝났으니 총결산을 합시다.
    jdk 동적 프록시 프로세스: 프록시 클래스를 계승하여 프록시 클래스의 실현된 인터페이스를 실현하고 값을 부여하는handler를 실행합니다. 실제 실행 방법이 호출될 때 프록시 클래스는 자신이 실행하지 않고handler 실행 방법을 통해 호출합니다.handler의 호출 방법인 invoke에는 자신이 프록시 클래스에 제공할 업무 논리를 포함하는 것 외에 반사적으로 프록시 클래스를 실행하는 방법도 필요합니다.
    이점:
  • 동적 에이전트는 정적 에이전트(장식 모드 유사)에 비해 모든 프록시 클래스에 프록시 클래스를 생성할 필요가 없고 하나의 인터페이스에handler를 작성하면 이 인터페이스를 실현하는 모든 클래스에 프록시 클래스를 생성할 수 있다.이 말은 매우 중요하니 자세히 이해해라.

  • 나쁜 점:
  • 인터페이스를 실현하지 않은 클래스 에이전트를 할 수 없습니다.

  • 사고: 동적 의미: 프록시 클래스의class는 프로그램이 실행될 때 동적으로 생성되며 컴파일할 때 존재하지 않습니다. 위의 프록시 클래스의class는 기본적으로 로컬 디스크에 쓰지 않습니다.
    handler와 에이전트 클래스의 서비스 영역: 하나의 에이전트 클래스는 특정한 인터페이스의 실현 클래스 서비스를 제공할 수 있고, 하나의handler는 모든 에이전트가 필요한 업무 논리와 같은 클래스 서비스를 제공할 수 있다.
    프록시 클래스를 생성할 때, 바이트 코드를 직접 생성하는 방식은 성능이 좋고 나쁨이나 더 좋은 방법이 있습니까?
    첫 번째 블로그에서 닭고기는 대신으로 가는 길에 더욱 분발해야 한다. 다음 분석은 cglib이다.

    좋은 웹페이지 즐겨찾기