设计代理类的真正目的是什么?

Cla*_*rke 9 java proxy

我一直在研究代理类,但我不明白设计它的全部想法。

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

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

但我搜索了一个可以用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);

  }

}
Run Code Online (Sandbox Code Playgroud)

有人可以向我指出这个代理设计模式是怎么回事吗?它在这个特定的程序中做了什么及其优点?

Dun*_*ncG 8

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

我将它们与数据库供应商 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));
}
Run Code Online (Sandbox Code Playgroud)

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

SomeObject x = TraceHandler.create(x, SomeObject.class);
FileVisitor myvisitor = TraceHandler.create(visitor, FileVisitor.class)
Run Code Online (Sandbox Code Playgroud)

希望代理类如何有用会更清楚,示例如下:

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);
     }
}
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)


Ole*_*muk 4

非常广泛的问题:

围绕这个有几种不同的选择:

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

例如工作中的spring事务注释

class UsersDao {
@Transactional
  public void method() {
    // DO SOME STUFF
  }
}
Run Code Online (Sandbox Code Playgroud)

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

调用处理程序的示例

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;
}
Run Code Online (Sandbox Code Playgroud)

在“调用”弹簧内部正在做类似的事情:

Transaction tx = TransactionManager.createTransaction()
try {
    // execute method
    method.invoke();
    tx.commit()

}
catch (Exception e) {
   // execute callback 
    tx.rollback()
}
finally () {
   // do clean up
   // tx.flush()
}
Run Code Online (Sandbox Code Playgroud)

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

动态代理工具:

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

https://www.javassist.org/

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