我一直在研究代理类,但我不明白设计它的全部想法。
据我所知,到目前为止,它是一个包装对象,可以控制对原始对象的访问。但如果我们想控制它,为什么我们不能设计具有这些访问机制的原始类呢?
我读到这些代理对象对于跟踪方法调用、将方法调用路由到远程服务器非常有用。
但我搜索了一个可以用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)
有人可以向我指出这个代理设计模式是怎么回事吗?它在这个特定的程序中做了什么及其优点?
当您处理来自其他团队或第三方的代码时,代理类非常有用,并且可用于各种诊断或增强操作。
我将它们与数据库供应商 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)
非常广泛的问题:
围绕这个有几种不同的选择:
例如工作中的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://docs.oracle.com/javase/6/docs/api/java/lang/reflect/Proxy.html