자바 JDK 동적 에이전트 의 기본 원리 상세 소개

12990 단어 JDK동적 에이전트
JDK 동적 에이전트 상세 설명
본 고 는 주로 JDK 동적 에이전트 의 기본 원 리 를 소개 하여 사람들 로 하여 금 JDK Proxy 를 더욱 깊이 이해 하 게 하고 그 이 유 를 알 게 한다.JDK 동적 에이전트 의 진정한 원리 와 생 성 과정 을 알 고 있 습 니 다.앞으로 JDK Proxy 를 쓰 면 demo 를 찾 지 않 아 도 완벽 한 Proxy 를 맨손으로 쓸 수 있 습 니 다.다음은 먼저 간단 한 Demo 를 살 펴 보 겠 습 니 다.후속 적 인 분석 과정 은 모두 이 Demo 에 의 해 소개 되 고 예 를 들 어 JDK 1.8 로 실 행 됩 니 다.
JDK Proxy HelloWorld

package com.yao.proxy;
/**
 * Created by robin
 */
public interface Helloworld {
  void sayHello();
}

package com.yao.proxy;
import com.yao.HelloWorld;
/**
 * Created by robin
 */
public class HelloworldImpl implements HelloWorld {
  public void sayHello() {
    System.out.print("hello world");
  }
}

package com.yao.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
 * Created by robin
 */
public class MyInvocationHandler implements InvocationHandler{
  private Object target;
  public MyInvocationHandler(Object target) {
    this.target=target;
  }
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("method :"+ method.getName()+" is invoked!");
    return method.invoke(target,args);
  }
}

package com.yao.proxy;
import com.yao.HelloWorld;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;

/**
 * Created by robin
 */
public class JDKProxyTest {
  public static void main(String[]args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    //       ,             ,          。
    Class<?> proxyClass= Proxy.getProxyClass(JDKProxyTest.class.getClassLoader(),HelloWorld.class);
    final Constructor<?> cons = proxyClass.getConstructor(InvocationHandler.class);
    final InvocationHandler ih = new MyInvocationHandler(new HelloworldImpl());
    HelloWorld helloWorld= (HelloWorld)cons.newInstance(ih);
    helloWorld.sayHello();

    //           ,          
    /* 
    HelloWorld helloWorld=(HelloWorld)Proxy.
         newProxyInstance(JDKProxyTest.class.getClassLoader(),
            new Class<?>[]{HelloWorld.class},
            new MyInvocationHandler(new HelloworldImpl()));
    helloWorld.sayHello();
    */
  }

}

위의 코드 를 실행 하면 간단 한 JDK Proxy 가 실 현 됩 니 다.
에이전트 생 성 과정
우리 가 매일 JDK 동적 에이전트 라 고 부 르 는 이 유 는 이 에이전트 클 라 스 가 JDK 가 실행 할 때 동적 으로 생 성 되 기 때 문 입 니 다.프 록 시 생 성 과정 을 설명 하기 전에-dsun.misc.ProxyGenerator.saveGenerated Files=true 라 는 매개 변 수 를 JVM 시작 매개 변수 에 추가 합 니 다.JDK 가 동적 으로 생 성 한 proxy class 의 바이트 코드 를 하 드 디스크 에 저장 하여 구체 적 으로 proxy 를 생 성 하 는 내용 을 볼 수 있 도록 도와 주 는 역할 을 합 니 다.제 가 사용 하 는 Intellij IDEA 는 프 록 시 클 라 스 가 생 성 된 후에 프로젝트 의 루트 디 렉 터 리 에 직접 놓 여 있 고 구체 적 인 가방 이름 을 디 렉 터 리 구조 로 합 니 다.
프 록 시 클래스 생 성 과정 은 주로 두 부분 을 포함한다.
프 록 시 바이트 생 성
4.567917.바이트 코드 를 들 어 오 는 클래스 로 더 를 통 해 가상 컴퓨터 에 불 러 옵 니 다Proxy 클래스 의 getProxyClass 방법 입구:클래스 로 더 와 interface 가 필요 합 니 다.

그리고 getProxyClass 0 방법 을 호출 합 니 다.현재 인터페이스의 프 록 시 클래스 가 존재 하면 캐 시 에서 되 돌아 오고 존재 하지 않 으 면 ProxyClassFactory 를 통 해 만 듭 니 다.인터페이스 인터페이스 수량 에 대한 제한 이 있어 65535 를 초과 해 서 는 안 된다 는 것 을 분명히 볼 수 있다.그 중에서 proxyClassCache 의 구체 적 인 초기 화 정 보 는 다음 과 같 습 니 다.

proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
그 중에서 프 록 시 클래스 를 만 드 는 구체 적 인 논 리 는 ProxyClassFactory 의 apply 방법 을 통 해 만 든 것 입 니 다.

ProxyClassFactory 의 논 리 는 패키지 이름 의 생 성 논 리 를 포함 하고 ProxyGenerator 를 호출 합 니 다.generateProxyClass 는 프 록 시 클래스 를 생 성하 고 프 록 시 바이트 코드 를 JVM 에 불 러 옵 니 다.
1.패키지 이름 생 성 논 리 는 기본적으로 com.sun.proxy 입 니 다.프 록 시 클래스 가 non-Public proxy interface 라면 프 록 시 인터페이스 와 같은 패키지 이름 을 사용 합 니 다.클래스 이름 은 기본적으로$Proxy 에 자체 증가 하 는 정수 치 를 추가 합 니 다.
2.패키지 이름 클래스 가 준비 되면 프 록 시 Generator.generateProxyClass 를 통 해 구체 적 으로 들 어 오 는 인터페이스 에 따라 프 록 시 바이트 코드 를 만 듭 니 다.-dsun.misc.ProxyGenerator.saveGenerated Files=true 이 매개 변 수 는 이 방법 에 작용 합 니 다.true 라면 디스크 에 바이트 코드 를 저장 합 니 다.프 록 시 클래스 에서 모든 프 록 시 방법 논 리 는 invocationHander 를 호출 하 는 invoke 방법 입 니 다.이것 은 뒤에 구체 적 인 프 록 시 컴 파일 결 과 를 볼 수 있 습 니 다.
 3.들 어 오 는 클래스 로 더 를 통 해 JVM 에 바이트 코드 를 불 러 옵 니 다:defineClass 0(loader,proxy Name,proxy ClassFile,0,proxy ClassFile.length).

 private static final class ProxyClassFactory
    implements BiFunction<ClassLoader, Class<?>[], Class<?>>
  {
    // prefix for all proxy class names
    private static final String proxyClassNamePrefix = "$Proxy";

    // next number to use for generation of unique 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);
      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.
       */
      long num = nextUniqueNumber.getAndIncrement();
      String proxyName = proxyPkg + proxyClassNamePrefix + num;

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

우 리 는 프 록 시 클래스 의 바이트 코드 에 따라 역 컴 파일 할 수 있 습 니 다.다음 과 같은 결 과 를 얻 을 수 있 습 니 다.그 중에서 Hello World 는 sayHello 방법 만 있 지만 프 록 시 클래스 에는 Object 의 세 가지 방법 이 있 습 니 다.equals,toString,hashCode.
대리 의 대략적인 구 조 는 4 부분 을 포함한다.
4.567917.정적 필드:대 리 된 인터페이스 모든 방법 에 대응 하 는 정적 방법 변수 가 있 습 니 다4.567917.정적 블록:주로 반사 로 정적 방법 변 수 를 초기 화 합 니 다
  • 구체 적 인 모든 대리 방법:논리 적 차이 가 많 지 않 은 것 은 h.invoke 입 니 다.주로 우리 가 정의 한 invocationHandler 논 리 를 호출 하여 목표 대상 target 에 대응 하 는 방법 을 촉발 합 니 다
  • 구조 함수:여기 서 우리 Invocation Handler 논리 가 들 어 옵 니 다
    
    package com.sun.proxy;
    
    import com.yao.HelloWorld;
    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 HelloWorld {
      private static Method m1;
      private static Method m3;
      private static Method m2;
      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})).booleanValue();
        } catch (RuntimeException | Error var3) {
          throw var3;
        } catch (Throwable var4) {
          throw new UndeclaredThrowableException(var4);
        }
      }
    
      public final void sayHello() throws {
        try {
          super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
          throw var2;
        } catch (Throwable var3) {
          throw new UndeclaredThrowableException(var3);
        }
      }
    
      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 int hashCode() throws {
        try {
          return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } catch (RuntimeException | Error var2) {
          throw var2;
        } catch (Throwable var3) {
          throw new UndeclaredThrowableException(var3);
        }
      }
    
      static {
        try {
          m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
          m3 = Class.forName("com.yao.HelloWorld").getMethod("sayHello", new Class[0]);
          m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
          m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
        } catch (NoSuchMethodException var2) {
          throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
          throw new NoClassDefFoundError(var3.getMessage());
        }
      }
    }
    
    
    일반적인 질문:
    1.toString()hashCode()equal()방법 은 논 리 를 호출 합 니 다.이 세 가지 Object 의 방법 은 다른 인터페이스 방법 으로 논 리 를 처리 하 는 것 과 마찬가지 로 invocationHandler 논 리 를 거 쳐 위의 바이트 코드 결 과 를 통 해 뚜렷하게 볼 수 있 습 니 다.다른 Object 의 방법 은 대리 처리 논리 로 가지 않 고 Proxy 가 계승 하 는 Object 의 방법 논리 로 갑 니 다.
    2.interface 는 equals,toString hashCode 방법 을 포함 할 때 일반 인터페이스 방법 을 처리 하 는 것 과 마찬가지 로 invocation handler 논 리 를 실행 하고 대상 이 다시 쓴 논 리 를 기준 으로 트리거 방법 논 리 를 실행 합 니 다.
    3.interface 는 중복 되 는 방법 으로 서명 합 니 다.인터페이스 가 들 어 오 는 순 서 를 기준 으로 앞에서 누 구 를 사용 하 는 방법 입 니 다.대리 류 에 서 는 하나만 보류 하고 중복 되 는 방법 으로 서명 하지 않 습 니 다.
    읽 어 주 셔 서 감사합니다. 여러분 에 게 도움 이 되 기 를 바 랍 니 다.본 사이트 에 대한 여러분 의 지지 에 감 사 드 립 니 다!

    좋은 웹페이지 즐겨찾기