设计代理类的实际目的是什么?

huangapple go评论74阅读模式
英文:

What is the actual purpose of designing a proxy class?

问题

我一直在研究代理类,但对其设计思想还没有完全理解。

从我目前的学习来看,代理类是一个包装对象,可以控制对原始对象的访问。但如果我们想要控制访问,为什么不能直接在原始类中设计这些访问机制呢?

我阅读过代理对象在跟踪方法调用、将方法调用路由到远程服务器方面的应用。但我在Java方面寻找了一个能够解释这一点的问题,但并没有找到。

我将举例说明一个方法跟踪程序的代码,这个例子在我参考的书中有提到。

public class ProxyTest {

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

     var elements = new Object[1000];

     // 使用整数1 . . . 1000的代理填充elements数组
     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;
     }

     // 构造一个随机整数
     Integer key = new Random().nextInt(elements.length) + 1;

     // 在数组中搜索这个随机整数
     int result = Arrays.binarySearch(elements, key);

     // 如果找到,打印匹配项
     if (result >= 0)
        System.out.println(elements[result]);

  }

}

/**
 * 一个调用处理程序,它打印出方法名称和参数,然后调用原始方法
 **/

class TraceHandler implements InvocationHandler{

  private Object target;

  /**
   * 构造一个TraceHandler
   * @param t 方法调用的隐式参数
   **/

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

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

     // 打印隐式参数
     System.out.print(target);

     // 打印方法名称
     System.out.print("." + m.getName() + "(");

     // 打印显式参数
     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(")");

     // 调用实际方法
     return m.invoke(target, args);

  }

}

有人能否指出这个代理设计模式的运作原理,在这个特定程序中它是做什么的,以及它的优势是什么?

英文:

I've been studying the proxy classes and I don't get the whole idea of designing it.

From what I learned so far it is a wrapper object that can control access to the original object. But if we want to control it why can't we design the original class with those access mechanisms.

I read that these proxy objects are useful for tracing method calls, routing method calls to remote servers.

But I searched for a question that would explain this to me in java but I didn't find any.

I'll illustrate the code for a method tracing program which was in the book that I was referring.

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 &lt; 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 &gt;= 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(&quot;.&quot; + m.getName() + &quot;(&quot;);
// print explicit arguments
if (args != null){
for (int i = 0; i &lt; args.length; i++){
System.out.print(args[i]);
if (i &lt; args.length - 1)
System.out.print(&quot;, &quot;);
}
}
System.out.println(&quot;)&quot;);
// invoke actual method
return m.invoke(target, args);
}
}

Can someone please point out to me what is going on with this proxy design pattern, what does it do in this particular program and its advantages?

答案1

得分: 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));
}

然后,您可以使用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);
        }

        // 构造一个随机整数
        Integer key = new Random().nextInt(elements.length) + 1;

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

        // 在没有代理的情况下搜索关键字
        System.out.println("Search for " + key + " without proxy:");
        int result = Arrays.binarySearch(elements, key, comparator);

        // 打印匹配结果
        System.out.println("Found result=" + result);

        // 使用代理搜索关键字并打印每次调用的调试信息
        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
英文:

Proxy classes are extremely useful when you are dealing with code from other teams or third parties and can be used for all sorts of diagnostics or enhanced operations.

I've used them with database vendor JDBC Connection jars and fault tolerant Remote server calls where the proxy could deal with failover for errors or re-connections such that the application logic is cleaner in all client calling code.

Your TraceHandler could be a self contained class by adding the method:

@SuppressWarnings(&quot;unchecked&quot;)
public static &lt;T&gt; T create(final T impl, final Class&lt;?&gt;... interfaces)
{
final Class&lt;?&gt; cls = impl.getClass();
return (T)Proxy.newProxyInstance(cls.getClassLoader(), interfaces, new TraceHandler(impl));
}

Then you could use to TraceHandler to monitor / log ANY interface used by your application:

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

Hopefully it will then be clearer how proxy classes can be helpful, and an example as follows:

public class ProxyTest
{
public static void main(String[] args) {
var elements = new Integer[1000];
for (int i = 0; i &lt; elements.length; i++) {
elements[i] = Integer.valueOf(i);
}
// construct a random integer
Integer key = new Random().nextInt(elements.length) + 1;
Comparator&lt;Integer&gt; comparator = Integer::compare;
Comparator&lt;Integer&gt; comparator2 = TraceHandler.create(comparator, Comparator.class);
// search for the key without proxy
System.out.println(&quot;Search for &quot;+key+&quot; without proxy:&quot;);
int result = Arrays.binarySearch(elements, key, comparator);
// print match
System.out.println(&quot;Found result=&quot;+result);
// search for the key with proxy prints debug info per call
System.out.println(&quot;Search &quot;+key+&quot; with proxy:&quot;);
int result2 = Arrays.binarySearch(elements, key, comparator2);
System.out.println(&quot;Found result2=&quot;+result2);
}
}

Example output from the above run shows that the proxy class can print details of each compare call.

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

得分: 4

非常广泛的问题:

在这方面有几个不同的选择:

  • 代理模式 - 一种通过另一个对象对一个对象进行操作的方式,通过第三个对象
  • 懒加载也可以是讨论的一部分
  • 最后最流行的是 - 动态代理和编译后的代码增强。许多知名框架都是这样工作的(例如 Spring、Hibernate、Selenium)。这可以实现更可读的代码并提高其质量(减少错误)。动态代理带来了动态惰性初始化的可能性,代码增强,更多声明性代码

例如,Spring 中的事务注解在工作中

class UsersDao {
@Transactional
  public void method() {
    // 做一些事情
  }
}

Spring 创建动态代理,它“扩展”了 UsersDao,但实际上会将所有方法调用重定向到一个实现 InvocationHandler 接口的特定对象

InvocationHandler 的示例

public interface InvocationHandler {

    /**
     * 处理代理实例上的方法调用并返回结果。
     * 当在与其关联的代理实例上调用方法时,将在调用处理程序上调用此方法。
     */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

在“invoke”内部,Spring 做了类似以下的操作:

Transaction tx = TransactionManager.createTransaction()
try {
    // 执行方法
    method.invoke();
    tx.commit()

}
catch (Exception e) {
   // 执行回滚操作
    tx.rollback()
}
finally () {
   // 进行清理操作
   // 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

英文:

Very wide question:

there are several different options around this:

  • Proxy pattern - a way of manipulation of one object by another thought third one
  • also lazy loading could also be part of that discussion
  • finally most popular - dynamic proxies and code enhancement after compilation. A lot of top famous frameworks work that way (e.g. spring, hibernate, selenium). That allows achieve more readable code and increase its quality (less bugs). Dynamic proxies bring dynamic lazy init possibility, code enhancements, more declarative code

E.g. spring transactional annotation in work

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

Spring is creating dynamic proxy that "extends" UsersDao but realy does redirect all method invocations to a specific object that implement InvocationHandler interface

example of 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;
}

Inside "invoke" spring is doing something like that:

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

That is achieved by a magic of dynamic proxies

Tools for dynamic proxying:

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

https://www.javassist.org/

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

答案3

得分: 3

让我们摒弃一切名词术语的迷雾,通过一个例子来在现实世界中理解它:

  1. Bob 和他的 10000 位朋友去观看一场足球比赛。
  2. 由于安全原因,入口处的保安必须检查所有人的门票,然后才能让他们进入。
  3. 体育场内还有比萨饼和啤酒摊位,但只有持有效门票的人才能购买。
  4. 现在,由于我们知道保安们做好了他们的工作,没有持有效门票的人能够进入体育场,比萨饼和啤酒供应商就不必亲自检查每个人的门票。

现在让我们看看:

  1. 保安充当着体育场摊位与世界其他部分之间的代理层。
  2. 如果没有保安,比萨饼和啤酒供应商都需要编写检查门票的代码,或者至少自己调用那段代码。这可能会在未来引起问题,因为如果体育场里开了一个新的摊位,他们可能会忘记调用检查人员门票的代码。

优势:

  1. 代理允许您在使用对象的同时对对象进行操作,从而使您能够轻松编写更干净、更符合 SOLID 原则的代码。
  2. 如果这是一个真实的场景,我既不会告诉保安有关体育场摊位的事情,也不会告诉供应商有关体育场整个售票系统的情况。这两个操作可以独立地自行发展。

以下两篇文章对代理提供了很好的介绍:

英文:

Let's remove all the hocus-pocus of nomenclatures and just understand it in the real world using an example-

  1. Bob and his 10,000 friends go to watch a soccer match.
  2. Due to security reasons, it is mandatory for the guards at the entry gates to check tickets of all the people and then let them enter.
  3. There are also pizza and beer stalls inside the stadium but you can only buy them if you have a valid ticket.
  4. Now since we know that the guards did there duty right and no one without a valid ticket could enter the stadium, the pizza and beer vendors don't have to check tickets of every person themselves.

Now let's see-

  1. The guards are acting as a proxy layer between the stadium stalls and the rest of the world.
  2. If the guards were not there, the pizza and beer vendors would all need to write the ticket checking code or at least make a call to that piece of code themselves. This might cause problems in the future because what if a new stall opens in the stadium and they forget to call the piece of code which checks tickets of the people.

Advantages-

  1. Proxies allow you to do something with the object, using the object that you are using while allowing you to write a cleaner, SOLID code with ease.
  2. Had this been a real-world scenario, neither would have I told the guards about the stalls in the stadium nor would have I told the vendors about the whole ticketing system of the stadium. These 2 operations could grow independently on their own.

These 2 articles give a good intro about proxies-

答案4

得分: 0

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

它们与包装器类有些相似,但它们不包含与之接口的对象,并且在必须为无法更改其代码的类添加附加行为时会用到。

英文:

Proxy classes act as an interface to access an object, usually adding additional functionality.

They are somewhat similar to Wrapper classes but they don't contain the object that they interface with and they are needed when you have to add additional behaviour to a class which you can't change the code of.

huangapple
  • 本文由 发表于 2020年5月5日 22:07:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/61615058.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定