【问题标题】:What is the actual purpose of designing a proxy class?设计代理类的实际目的是什么?
【发布时间】:2020-05-05 14:07:20
【问题描述】:

我一直在研究代理类,但我并没有完全理解如何设计它。

从我目前学到的知识来看,它是一个包装对象,可以控制对原始对象的访问。但是如果我们想控制它,为什么我们不能设计具有这些访问机制的原始类。

我读到这些代理对象对于跟踪方法调用、将方法调用路由到远程服务器很有用。

但我搜索了一个可以在 java 中向我解释这个问题的问题,但我没有找到。

我将说明我所指的书中的方法跟踪程序的代码。

public class ProxyTest {

  public static void main(String[] args) throws ClassNotFoundException {

     var elements = new Object[1000];

     // fill elements with proxies for the integers 1 . . . 1000
     for (int i = 0; i < elements.length; i++) {
       Integer value = i + 1;
       var handler = new TraceHandler(value);
       Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Comparable.class}, handler);
       elements[i] = proxy;
     }

     // construct a random integer
     Integer key = new Random().nextInt(elements.length) + 1;

     // search for the key
     int result = Arrays.binarySearch(elements, key);

     // print match if found
     if (result >= 0)
        System.out.println(elements[result]);

  }

}

/**
 * An invocation handler that prints out the method name and parameters, then
 * invokes the original method
 **/

class TraceHandler implements InvocationHandler{

  private Object target;

  /**
   * Constructs a TraceHandler
   * @param t the implicit parameter of the method call
   **/

  public TraceHandler(Object t){
    target = t;
  }

  public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {

     // print implicit argument
     System.out.print(target);

     // print method name
     System.out.print("." + m.getName() + "(");

     // print explicit arguments
     if (args != null){
       for (int i = 0; i < args.length; i++){
         System.out.print(args[i]);
         if (i < args.length - 1)
           System.out.print(", ");
       }
     }

     System.out.println(")");

     // invoke actual method
     return m.invoke(target, args);

  }

}

谁能告诉我这个代理设计模式是怎么回事,它在这个特定的程序中做了什么以及它的优势?

【问题讨论】:

  • 为什么我们不能用这些访问机制设计原始类”代理可以与任何类一起工作,包括那些不是你写的
  • @Michael 它可以生成一个全新的类,比如自己生成代码吗?
  • 它不是代码生成工具。它允许您在单个方法中处理接口上的所有方法调用。如果不编写在每个方法中具有相同代码的这些接口的实现,您自己就无法做到这一点,这可能会非常重复。
  • @Clarke, Cay S. Horstman Core Java 我猜 :)
  • @jwpol 你说得对,我崇拜那本书!

标签: java proxy


【解决方案1】:

代理类在处理来自其他团队或第三方的代码时非常有用,可用于各种诊断或增强操作。

我已经将它们与数据库供应商 JDBC 连接 jar 和容错远程服务器调用一起使用,其中代理可以处理错误或重新连接的故障转移,从而使所有客户端调用代码中的应用程序逻辑更加清晰。

通过添加方法,您的 TraceHandler 可以是一个自包含的类:

@SuppressWarnings("unchecked")
public static <T> T create(final T impl, final Class<?>... interfaces)
{
    final Class<?> cls = impl.getClass();

    return (T)Proxy.newProxyInstance(cls.getClassLoader(), interfaces, new TraceHandler(impl));
}

然后您可以使用 TraceHandler 来监控/记录您的应用程序使用的任何接口:

SomeObject x = TraceHandler.create(x, SomeObject.class);
FileVisitor myvisitor = TraceHandler.create(visitor, FileVisitor.class)

希望代理类的帮助会更清楚,示例如下:

public class ProxyTest
{
    public static void main(String[] args) {

        var elements = new Integer[1000];

        for (int i = 0; i < elements.length; i++) {
            elements[i] = Integer.valueOf(i);
        }

        // construct a random integer
        Integer key = new Random().nextInt(elements.length) + 1;

        Comparator<Integer> comparator = Integer::compare;
        Comparator<Integer> comparator2 = TraceHandler.create(comparator, Comparator.class);

        // search for the key without proxy
        System.out.println("Search for "+key+" without proxy:");
        int result = Arrays.binarySearch(elements, key, comparator);

        // print match
        System.out.println("Found result="+result);

        // search for the key with proxy prints debug info per call
        System.out.println("Search "+key+" with proxy:");
        int result2 = Arrays.binarySearch(elements, key, comparator2);
        System.out.println("Found result2="+result2);
     }
}

上述运行的示例输出表明代理类可以打印每个比较调用的详细信息。

Search for 486 without proxy:
Found result at 486
Search 486 with proxy:
stackoverflow.proxy.ProxyTest$$Lambda$1/0x0000000800c00a00@15aeb7ab.compare(499, 486)
stackoverflow.proxy.ProxyTest$$Lambda$1/0x0000000800c00a00@15aeb7ab.compare(249, 486)
stackoverflow.proxy.ProxyTest$$Lambda$1/0x0000000800c00a00@15aeb7ab.compare(374, 486)
stackoverflow.proxy.ProxyTest$$Lambda$1/0x0000000800c00a00@15aeb7ab.compare(436, 486)
stackoverflow.proxy.ProxyTest$$Lambda$1/0x0000000800c00a00@15aeb7ab.compare(467, 486)
stackoverflow.proxy.ProxyTest$$Lambda$1/0x0000000800c00a00@15aeb7ab.compare(483, 486)
stackoverflow.proxy.ProxyTest$$Lambda$1/0x0000000800c00a00@15aeb7ab.compare(491, 486)
stackoverflow.proxy.ProxyTest$$Lambda$1/0x0000000800c00a00@15aeb7ab.compare(487, 486)
stackoverflow.proxy.ProxyTest$$Lambda$1/0x0000000800c00a00@15aeb7ab.compare(485, 486)
stackoverflow.proxy.ProxyTest$$Lambda$1/0x0000000800c00a00@15aeb7ab.compare(486, 486)
Found result at 486

【讨论】:

    【解决方案2】:

    非常广泛的问题:

    有几个不同的选项:

    • 代理模式 - 一种由另一个想法第三个对象操纵一个对象的方式
    • 延迟加载也可能是讨论的一部分
    • 最后是最流行的——动态代理和编译后的代码增强。许多著名的框架都以这种方式工作(例如 spring、hibernate、selenium)。这允许实现更具可读性的代码并提高其质量(更少的错误)。动态代理带来了动态惰性初始化的可能性、代码增强、更具声明性的代码

    例如工作中的spring事务注释

    class UsersDao {
    @Transactional
      public void method() {
        // DO SOME STUFF
      }
    }
    

    Spring 正在创建“扩展”UsersDao 的动态代理,但确实会将所有方法调用重定向到实现 InvocationHandler 接口的特定对象

    InvocationHandler 示例

    public interface InvocationHandler {
    
        /**
         * Processes a method invocation on a proxy instance and returns
         * the result.  This method will be invoked on an invocation handler
         * when a method is invoked on a proxy instance that it is
         * associated with.
         */
        public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
    }
    

    在“invoke”内部,spring 正在做类似的事情:

    Transaction tx = TransactionManager.createTransaction()
    try {
        // execute method
        method.invoke();
        tx.commit()
    
    }
    catch (Exception e) {
       // execute callback 
        tx.rollback()
    }
    finally () {
       // do clean up
       // tx.flush()
    }
    

    这是通过动态代理的魔力实现的

    动态代理工具:

    https://github.com/cglib/cglib/wiki

    https://www.javassist.org/

    https://docs.oracle.com/javase/6/docs/api/java/lang/reflect/Proxy.html

    【讨论】:

      【解决方案3】:

      让我们删除所有命名法的花招,并通过一个示例在现实世界中理解它-

      1. Bob 和他的 10,000 位朋友去看足球比赛。
      2. 出于安全原因,入口处的警卫必须检查所有人员的门票,然后让他们进入。
      3. 体育场内也有比萨饼和啤酒摊,但您必须持有有效门票才能购买。
      4. 现在,既然我们知道警卫在那里的职责是正确的,没有有效门票的人不能进入体育场,那么披萨和啤酒供应商就不必亲自检查每个人的门票了。

      现在让我们看看-

      1. 警卫充当体育场摊位与世界其他地方之间的代理层。
      2. 如果警卫不在,披萨和啤酒供应商都需要编写检票代码或至少自己调用该代码。这可能会在未来引起问题,因为如果体育场里开了一个新摊位,而他们忘记调用检查人们门票的代码怎么办。

      优点-

      1. 代理允许您对对象执行某些操作,使用您正在使用的对象,同时允许您轻松编写更简洁、可靠的代码。
      2. 如果这是真实场景,我既不会告诉警卫体育场内的摊位,也不会告诉供应商体育场的整个售票系统。这两个操作可以自己独立增长。

      这两篇文章很好地介绍了代理-

      【讨论】:

        【解决方案4】:

        代理类充当访问对象的接口,通常会添加额外的功能。

        它们有点类似于 Wrapper 类,但它们不包含与之交互的对象,并且当您必须向无法更改代码的类添加其他行为时需要它们。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-02-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多