【问题标题】:How to make the JVM proxy the only one interface method?如何让 JVM 代理成为唯一的接口方法?
【发布时间】:2022-01-03 07:05:12
【问题描述】:
public interface Action {
    void doSomething();
    void dontProxy();
}

例如有了这个接口,我只想让JVM代理doSomething方法。

class DynamicProxy implements InvocationHandler{
    private Action work;
    public DynamicProxy(Action action){
        this.work = action;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(method.getName()+" start");
        var tmp = method.invoke(work,args);
        System.out.println(method.getName()+" end");
        return tmp;
    }

Action action = (Action) Proxy.newProxyInstance(handler.getClass().getClassLoader(),work.getClass().getInterfaces(),handler);

作品是Action接口实现类的一个实例。 如果我使用Proxy.newProxyInstance的话,好像是让所有的接口方法都被handler处理了。

【问题讨论】:

  • 如果代码调用代理上的非代理方法,你想发生什么?
  • 实现接口时,必须实现所有方法。但看来,你问错了问题。当您希望仅针对一种方法打印消息时,只需检查 method 参数是否描述了您要装饰的方法,例如if(method.getName().equals("doSomething")) System.out.println("doSomething start"); var tmp = method.invoke(work,args); if(method.getName().equals("doSomething")) System.out.println("doSomething end"); return tmp;

标签: java design-patterns jvm dynamic-proxy proxy-pattern


【解决方案1】:

确实,在代理调用中,所有方法都由指定的InvocationHandler 处理。正如人们在 cmets 中已经指出的那样,否则您最终会得到未实现的方法,并且 InvocationHandler 的典型实现通常涉及某种逻辑来在不同方法的不同实现之间切换。对于应该保留原始行为的方法,您通常会有某种传递处理程序,它只调用原始方法(但这些方法仍会被代理)。

或者,有几个可用的库可以使这种检测更容易。例如,对于 Projo 库,您可以使用稍微不同的方法(免责声明:我是该库的作者):

import pro.projo.Projo;
import pro.projo.annotations.Delegate;
import pro.projo.singles.Factory;

public interface ProxiedAction extends Action {

    Factory<ProxiedAction, Action> factory =
        Projo.creates(ProxiedAction.class).with(ProxiedAction::original);

    @Delegate
    Action original();

    @Override
    default void doSomething() {
        System.out.println("doSomething() start");
        original().doSomething();
        System.out.println("doSomething() end");
    }
}

如果您有一个包含几十个方法的大型接口,但您只想检测几个方法,这将特别有用。 要对此进行测试,您可以编写如下代码:

Action work = new Action() {

    @Override
    public void doSomething() {
        System.out.println("work.doSomething()");
    }

    @Override
    public void dontProxy() {
        System.out.println("work.dontProxy()");
    }
};
Action myAction = ProxiedAction.factory.create(work);
System.out.println("Calling myAction.doSomething()...");
myAction.doSomething();
System.out.println("Calling myAction.dontProxy()...");
myAction.dontProxy();
System.out.println("Done.");

结果输出应该是这样的:

Calling myAction.doSomething()...
doSomething() start
work.doSomething()
doSomething() end
Calling myAction.dontProxy()...
work.dontProxy()
Done.

如您所见,只有 doSomething() 使用开始/结束消息进行检测,而调用 dontProxy() 只会打印原始消息。

为此,您只需要添加这两个额外的依赖项:

   <dependency>
    <groupId>pro.projo</groupId>
    <artifactId>projo</artifactId>
    <version>1.3.1</version>
   </dependency>
   <dependency>
    <groupId>pro.projo</groupId>
    <artifactId>projo-runtime-code-generation</artifactId>
    <version>1.3.1</version>
    <scope>runtime</scope>
   </dependency>

这种方法的另一个优点是它实际上不使用代理(这是非常低效和缓慢的),而是在运行时生成一些包装器代码。如果您忽略第二个依赖项 (projo-runtime-code-generation),代码将恢复为使用代理,这将非常接近您的原始代码正在执行的操作(但效率也不是很高;您可能还会看到一些关于通过反射进行非法访问的警告)。

在底层,Projo 使用 Byte Buddy 生成运行时代码,您也可以直接使用 Byte Buddy 为您的案例手动推出一个简单的解决方案。

Spring 和其他一些依赖注入框架也支持使用基于注释的方法进行高效的代码检测。

【讨论】:

    猜你喜欢
    • 2021-10-04
    • 2011-07-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-24
    • 2019-02-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多