> Java > java지도 시간 > 본문

Java 동적 프록시 샘플 코드에 대한 심층적인 이해

黄舟
풀어 주다: 2017-03-16 10:10:58
원래의
1230명이 탐색했습니다.

이 글은 Java 동적 프록시에 대한 심층적인 이해를 위한 관련 정보를 주로 소개합니다. 필요한 친구가 참고하면 좋습니다.

Java 동적 프록시를 이해하려면 먼저 프록시가 무엇인지 이해하고 익숙해져야 합니다. 의 친구들은 Gof가 정리한 23개의 디자인 패턴 중에 Proxy라는 객체 구조 패턴이 있다는 것을 알아야 합니다. .

제 생각에는 23가지 디자인 패턴 중 소위

에이전트 모드와 "데코레이션 모드"는 같은 것 같아요. 23개의 디자인 패턴 중 두 가지 패턴으로 간주되는 경우도 있는데, 이 두 패턴의 유사점과 차이점을 자세히 살펴보면 인위적으로 두 패턴을 구별하는 것이 가능하다는 글도 있습니다. 어떤 수준에서는 이 두 패턴이 완전히 동일하지 않다고 생각합니다. 따라서 프록시 모드를 배우면 데코레이션 모드도 마스터하게 됩니다.

정적프록시

먼저 코드를 살펴보겠습니다.

package common;

public class Test {
  static interface Subject{
    void sayHi();
    void sayHello();
  }

  static class SubjectImpl implements Subject{

    @Override
    public void sayHi() {
      System.out.println("hi");
    }

    @Override
    public void sayHello() {
      System.out.println("hello");
    }
  }

  static class SubjectImplProxy implements Subject{
    private Subject target;

    public SubjectImplProxy(Subject target) {
      this.target=target;
    }

    @Override
    public void sayHi() {
      System.out.print("say:");
      target.sayHi();
    }

    @Override
    public void sayHello() {
      System.out.print("say:");
      target.sayHello();
    }
  }

  public static void main(String[] args) {
    Subject subject=new SubjectImpl();
    Subject subjectProxy=new SubjectImplProxy(subject);
    subjectProxy.sayHi();
    subjectProxy.sayHello();
  }
}
로그인 후 복사
이 코드는 먼저 두 가지 메소드를 갖는 Subject

Interface

를 정의합니다.

그런 다음 Subject 인터페이스를 구현하기 위해 SubjectImpl 클래스를 정의하고 두 가지 메소드를 구현합니다. 여기서는 확실히 문제가 없습니다.

이제 Subject 인터페이스도 구현하는 또 다른 SubjuectImplProxy 클래스를 정의합니다. 이 SubjectImplProxy 클래스의 목적은 SubjectImpl 클래스의 인스턴스를 래핑하는 것입니다. 이는 SubjectImpl의 인스턴스를 저장하기 위해 내부적으로

변수 대상을 정의합니다. SubjectImplProxy는 인터페이스에 지정된 두 가지 메소드도 구현하며 구현 버전에서는 SubjectImpl 구현을 호출하지만 자체 처리 로직을 추가합니다.

이 코드는 이해하기 어렵지 않다고 생각합니다. SubjectImpl을 래핑하여 출력 콘텐츠에 접두사를 추가하는 기능을 구현합니다. 이 프록시 방법을 정적 프록시라고 합니다.

동적 프록시

위의 데모에서 정적 프록시의 단점을 확인하는 것은 어렵지 않습니다. SubjectImpl의 두 가지 메서드는 동일한 방식으로 래핑되어 있지만 그러나 SubjectImplProxy에서 동일한 패키징 로직을 두 번 작성해야 하며, Subject 인터페이스가 나중에 새로운 메소드를 추가하는 경우 SubjectImplProxy는 새로운 구현도 추가해야 합니다. 단, SubjectImplProxy는 모든 메소드를 동일하게 래핑할 수 있습니다.

이제 위 예의 정적 프록시를 동적 프록시로 변경합니다. 차이점을 살펴보겠습니다.

package common;

import java.lang.invoke.MethodHandle;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Test {
  static interface Subject{
    void sayHi();
    void sayHello();
  }

  static class SubjectImpl implements Subject{

    @Override
    public void sayHi() {
      System.out.println("hi");
    }

    @Override
    public void sayHello() {
      System.out.println("hello");
    }
  }

  static class ProxyInvocationHandler implements InvocationHandler{
    private Subject target;
    public ProxyInvocationHandler(Subject target) {
      this.target=target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      System.out.print("say:");
      return method.invoke(target, args);
    }

  }

  public static void main(String[] args) {
    Subject subject=new SubjectImpl();
    Subject subjectProxy=(Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(), 
    subject.getClass().getInterfaces(), new ProxyInvocationHandler(subject));
    subjectProxy.sayHi();
    subjectProxy.sayHello();

  }
}
로그인 후 복사

만 보면 기본 메소드는 두 번째 메소드만 있습니다. 행은 이전 정적 프록시와 다릅니다. 또한 subjectProxy 프록시 객체를 생성하지만 생성된 코드가 다릅니다. 정적 프록시는 SubjectImplProxy의 인스턴스를 직접 새로 만드는 반면, 동적 프록시는 java.lang.reflect.Proxy.newProxyInstance() 메서드를 호출합니다. 이 메서드의 소스 코드를 살펴보겠습니다.


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

    /*
     * Look up or generate the designated proxy class.
     */
    Class<?> cl = getProxyClass(loader, interfaces);  //获取代理类的Class

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
      Constructor cons = cl.getConstructor(constructorParams);  
      //constructorParams是写死的:{ InvocationHandler.class },上边返回的代理类Class一定是extends Proxy的,而Proxy有一个参数为InvocationHandler的构造函数
      return cons.newInstance(new Object[] { h });  
      //这里通过构造函数将我们自己定义的InvocationHandler的子类传到代理类的实例里,当我们调用代理类的任何方法时,
      实际上都会调用我们定义的InvocationHandler子类重写的invoke()函数
    } catch (NoSuchMethodException e) {
      throw new InternalError(e.toString());
    } catch (IllegalAccessException e) {
      throw new InternalError(e.toString());
    } catch (InstantiationException e) {
      throw new InternalError(e.toString());
    } catch (InvocationTargetException e) {
      throw new InternalError(e.toString());
    }
  }
로그인 후 복사

위 클래스 cl = getProxyClass(loader, 인터페이스); 호출된 getProxyClass 메소드:

public static Class<?> getProxyClass(ClassLoader loader,
                     Class<?>... interfaces)
    throws IllegalArgumentException
  {
    if (interfaces.length > 65535) {  //因为在class文件中,一个类保存的接口数量是用2个字节来表示的,因此java中一个类最多可以实现65535个接口
      throw new IllegalArgumentException("interface limit exceeded");
    }

    Class<?> proxyClass = null;

    /* collect interface names to use as key for proxy class cache */
    String[] interfaceNames = new String[interfaces.length];

    // for detecting duplicates
    Set<Class<?>> interfaceSet = new HashSet<>();
     //验证interfaces里的接口是否能被类加载器加载,是否是接口,是否有重复的 
    for (int i = 0; i < interfaces.length; i++) {
      /*
       * Verify that the class loader resolves the name of this
       * interface to the same Class object.
       */
      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");
      }

      /*
       * 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.contains(interfaceClass)) {
        throw new IllegalArgumentException(
          "repeated interface: " + interfaceClass.getName());
      }
      interfaceSet.add(interfaceClass);

      interfaceNames[i] = interfaceName;
    }

    /*
     * Using string representations of the proxy interfaces as
     * keys in the proxy class cache (instead of their Class
     * objects) is sufficient because we require the proxy
     * interfaces to be resolvable by name through the supplied
     * class loader, and it has the advantage that using a string
     * representation of a class makes for an implicit weak
     * reference to the class.
     */
    List<String> key = Arrays.asList(interfaceNames);  //使用interfaces列表作为key缓存在cache里,也就是实现了相同interfaces的代理类只会创建加载一次

    /*
     * 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);
      }
      /*
       * 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.
       */
    }

    /*
     * Look up the list of interfaces in the proxy class cache using
     * the key. This lookup will result in one of three possible
     * kinds of values:
     *   null, if there is currently no proxy class for the list of
     *     interfaces in the class loader,
     *   the pendingGenerationMarker object, if a proxy class for the
     *     list of interfaces is currently being generated,
     *   or a weak reference to a Class object, if a proxy class for
     *     the list of interfaces has already been generated.
     */
     //看看缓存里有没有,如果有就直接取出来然后return,否则判断根据pendingGenerationMarker判断是否有其它线程正在生成当前的代理类,
     如果有则cache.wait()等待,如果没有则创建。
    synchronized (cache) {
      /*
       * Note that we need not worry about reaping the cache for
       * entries with cleared weak references because if a proxy class
       * has been garbage collected, its class loader will have been
       * garbage collected as well, so the entire cache will be reaped
       * from the loaderToCache map.
       */
      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) {
            /*
             * The class generation that we are waiting for should
             * take a small, bounded time, so we can safely ignore
             * thread interrupts here.
             */
          }
          continue;
        } else {
          /*
           * No proxy class for this list of interfaces has been
           * generated or is being generated, so we will go and
           * generate it now. Mark it as pending generation.
           */
          cache.put(key, pendingGenerationMarker);
          break;
        }
      } while (true);
    }
     //确认要生成的代理类所属的包,如果interfaces里所有接口都是public的,代理类所属包就是默认包;
     如果有interface不是public,那么所有不是public的interface必须在一个包里否则报错。
    try {
      String proxyPkg = null;   // package to define proxy class in

      /*
       * 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 (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(&#39;.&#39;);
          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,
        proxyPkg = "";     // use the unnamed package
      }

      {
        /*
         * Choose a name for the proxy class to generate.
         */
        long num;
        synchronized (nextUniqueNumberLock) {
          num = nextUniqueNumber++;
        }
        String proxyName = proxyPkg + proxyClassNamePrefix + num;  
        //生成代理类的名字,proxyPkg是上面确定下来的代理类所在的包名,proxyClassNamePrefix是写死的字符串“$Proxy”,
        num是一个全局唯一的long型数字,从0开始累积,每次生成新的代理类就+1,从这里也能看出生成的动态代理类的数量不能超过Long.maxValue
        /*
         * Verify that the class loader hasn&#39;t already
         * defined a class with the chosen name.
         */

        /*
         * Generate the specified proxy class.
         */
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
          proxyName, interfaces);  //生成一个以proxyName为类名的,实现了Interfaces里所有接口的类的字节码
        try {
          proxyClass = 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());
        }
      }
      // add to set of all generated proxy classes, for isProxyClass
      proxyClasses.put(proxyClass, null);

    } finally {
      /*
       * We must clean up the "pending generation" state of the proxy
       * class cache entry somehow. If a proxy class was successfully
       * generated, store it in the cache (with a weak reference);
       * otherwise, remove the reserved entry. In all cases, notify
       * all waiters on reserved entries in this cache.
       */
       
       //创建成功,则将cache中该key的pendingGenerationMarker替换为实际的代理类的弱引用,否则也要清除pendingGenerationMarker标记;
       不管是否成功,都要执行cache.notifyAll(),让其它要创建相同代理类并且执行了cache.wait()的线程恢复执行。
      synchronized (cache) {
        if (proxyClass != null) {
          cache.put(key, new WeakReference<Class<?>>(proxyClass));
        } else {
          cache.remove(key);
        }
        cache.notifyAll();
      }
    }
    return proxyClass; //最后返回代理类Class
  }
로그인 후 복사

여기서는 동적 프록시의 Java 소스 코드를 구문 분석했으며 이제 아이디어가 매우 명확해졌습니다.

Proxy.newProxyInstance(ClassLoader loader,Class[] 인터페이스,InvocationHandler h) 메서드는 간단합니다. 실행 다음 작업이 수행됩니다.

1. 매개변수 인터페이스의 모든 인터페이스를 구현하고

프록시를 상속하는 프록시 클래스의 바이트코드를 생성한 다음 매개변수의 classLoader를 사용하여 프록시 클래스를 로드합니다.

2. 프록시 클래스 상위 클래스의 생성자 Proxy(InvocationHandler h)를 사용하여 프록시 클래스의 인스턴스를 생성하고 사용자 정의 InvocationHandler 하위 클래스를 전달합니다.

3. 우리가 구성한 프록시 클래스는 인터페이스(즉, 프로그램에서 전달된 subject.getClass().getInterfaces())의 모든 인터페이스를 구현하므로 이 프록시 클래스 인스턴스를 반환합니다. 프록시 클래스를 주제 유형으로 캐스팅하여 인터페이스에 정의된 메소드를 호출할 수 있습니다. <… 이를 위해서는 프록시 클래스의 소스 코드를 살펴봐야 합니다. 그러나 프록시 클래스는 프로그램에 의해 동적으로 생성된 바이트코드에 의해 로드됩니다. 소스 코드를 보는 방법은 무엇입니까? 문제가 되지 않습니다. System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true")를 기본 메서드에 추가하면 생성된 프록시 클래스 클래스 파일이 로컬에 저장됩니다. 그런 다음 컴파일하여 프록시 클래스의 소스 코드를 가져옵니다.

package common;

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 Test.Subject
{
 private static Method m4;
 private static Method m1;
 private static Method m3;
 private static Method m0;
 private static Method m2;

 static
 {
   try {
     m4 = Class.forName("Test$Subject").getMethod("sayHello", new Class[0]);
     m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
     m3 = Class.forName("Test$Subject").getMethod("sayHi", new Class[0]);
     m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
     m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
 }

 public $Proxy0(InvocationHandler paramInvocationHandler)
 {
  super(paramInvocationHandler);
 }

 public final void sayHello()
 {
  try
  {
   this.h.invoke(this, m4, null);
   return;
  }
  catch (RuntimeException localRuntimeException)
  {
   throw localRuntimeException;
  }
  catch (Throwable localThrowable)
  {
    throw new UndeclaredThrowableException(localThrowable);
  }
 }

 public final boolean equals(Object paramObject)
 {
  try
  {
   return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
  }
  catch (RuntimeException localRuntimeException)
  {
   throw localRuntimeException;
  }
  catch (Throwable localThrowable)
  {
    throw new UndeclaredThrowableException(localThrowable);
  }
 }

 public final void sayHi()
 {
  try
  {
   this.h.invoke(this, m3, null);
   return;
  }
  catch (RuntimeException localRuntimeException)
  {
   throw localRuntimeException;
  }
  catch (Throwable localThrowable)
  {
    throw new UndeclaredThrowableException(localThrowable);
  }
 }

 public final int hashCode()
 {
  try
  {
   return ((Integer)this.h.invoke(this, m0, null)).intValue();
  }
  catch (RuntimeException localRuntimeException)
  {
   throw localRuntimeException;
  }
  catch (Throwable localThrowable)
  {
    throw new UndeclaredThrowableException(localThrowable);
  }
 }

 public final String toString()
 {
  try
  {
   return (String)this.h.invoke(this, m2, null);
  }
  catch (RuntimeException localRuntimeException)
  {
   throw localRuntimeException;
  }
  catch (Throwable localThrowable)
  {
    throw new UndeclaredThrowableException(localThrowable);
  }
 }
}
로그인 후 복사

我们可以看到代理类内部实现比较简单,在调用每个代理类每个方法的时候,都用反射去调h的invoke方法(也就是我们自定义的InvocationHandler的子类中重写的invoke方法),用参数传递了代理类实例、接口方法、调用参数列表,这样我们在重写的invoke方法中就可以实现对所有方法的统一包装了。

总结

动态代理相对于静态代理在使用上的优点主要是能够对一个对象的所有方法进行统一包装,而且后期被代理的类添加方法的时候动态代理类不需要改动。

缺点是要求被代理的类必须实现了接口,因为动态代理类在实现的时候继承了Proxy类,java不支持多继承,因此动态代理类只能根据接口来定义方法。

最后动态代理之所以叫做动态代理是因为java在实现动态代理的时候,动态代理类是在运行时动态生成和加载的,相对的,静态代理类和其他普通类一下,在类加载阶段就加载了。

위 내용은 Java 동적 프록시 샘플 코드에 대한 심층적인 이해의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 이슈
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!