Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/333.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 如何使用MethodDelegation或Forwarding创建字节伙伴代理?_Java_Byte Buddy - Fatal编程技术网

Java 如何使用MethodDelegation或Forwarding创建字节伙伴代理?

Java 如何使用MethodDelegation或Forwarding创建字节伙伴代理?,java,byte-buddy,Java,Byte Buddy,尝试执行以下操作时,load调用中出现异常: Field datasourceExtensionField = Grid.class.getDeclaredField("datasourceExtension"); datasourceExtensionField.setAccessible(true); RpcDataProviderExtension rpcDataProviderExtension = (RpcDataProviderExtension) datasourceE

尝试执行以下操作时,load调用中出现异常:

  Field datasourceExtensionField = Grid.class.getDeclaredField("datasourceExtension");
  datasourceExtensionField.setAccessible(true);
  RpcDataProviderExtension rpcDataProviderExtension = (RpcDataProviderExtension) datasourceExtensionField.get(grid);

  Field activeItemHandlerField = RpcDataProviderExtension.class.getDeclaredField("activeItemHandler");
  activeItemHandlerField.setAccessible(true);
  Object activeItemHandler = activeItemHandlerField.get(rpcDataProviderExtension);

  Field keyMapperField = activeItemHandler.getClass().getDeclaredField("keyMapper");
  keyMapperField.setAccessible(true);
  KeyMapper original = (KeyMapper) keyMapperField.get(activeItemHandler);

  KeyMapper wrapper = new ByteBuddy() //
      .subclass(KeyMapper.class) //
      .defineField("original", KeyMapper.class, Visibility.PUBLIC) //
      .method(ElementMatchers.any()) //
      .intercept(Forwarding.toField("original")) //
      .method(ElementMatchers.named("get")) //
      .intercept(MethodDelegation.to(new KeyMapperWrapper(grid, original))) //
      .make() //
      .load(KeyMapperWrapper.class.getClassLoader()) //
      .getLoaded() //
      .newInstance();

  // give wrapper the reference to the original
  wrapper.getClass().getDeclaredField("original").set(wrapper, original);

  // replace original with wrapper
  keyMapperField.set(activeItemHandler, wrapper);
例外情况:

java.lang.VerifyError: Bad access to protected data in invokevirtual
Exception Details:
  Location:
    com/vaadin/server/KeyMapper$ByteBuddy$WlWljaQa.clone()Ljava/lang/Object; @4: invokevirtual
  Reason:
    Type 'com/vaadin/server/KeyMapper' (current frame, stack[0]) is not assignable to 'com/vaadin/server/KeyMapper$ByteBuddy$WlWljaQa'
  Current Frame:
    bci: @4
    flags: { }
    locals: { 'com/vaadin/server/KeyMapper$ByteBuddy$WlWljaQa' }
    stack: { 'com/vaadin/server/KeyMapper' }
  Bytecode:
    0x0000000: 2ab4 000c b600 1cb0                    

    at java.lang.Class.getDeclaredFields0(Native Method)
    at java.lang.Class.privateGetDeclaredFields(Class.java:2583)
    at java.lang.Class.getDeclaredField(Class.java:2068)
    at net.bytebuddy.implementation.LoadedTypeInitializer$ForStaticField.onLoad(LoadedTypeInitializer.java:101)
    at net.bytebuddy.implementation.LoadedTypeInitializer$Compound.onLoad(LoadedTypeInitializer.java:180)
    at net.bytebuddy.dynamic.TypeResolutionStrategy$Passive.initialize(TypeResolutionStrategy.java:75)
    at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:4525)
    at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:4514)
    at test.KeyMapperWrapper.patch(KeyMapperWrapper.java:62)
 .method(ElementMatchers.any()) //
 .intercept(MethodDelegation.to(original)) //
 .method(ElementMatchers.named("get")) //
 .intercept(MethodDelegation.to(new KeyMapperWrapper(grid, original))) //
我显然不明白转发应该如何工作,我做错了什么

我的意图是用一个代理替换现有的
KeyMapper
,在这个代理中我覆盖一个方法,并将其余方法委托给原始方法

编辑:我现在还尝试了
MethodDelegation
,它引发了相同的异常:

java.lang.VerifyError: Bad access to protected data in invokevirtual
Exception Details:
  Location:
    com/vaadin/server/KeyMapper$ByteBuddy$WlWljaQa.clone()Ljava/lang/Object; @4: invokevirtual
  Reason:
    Type 'com/vaadin/server/KeyMapper' (current frame, stack[0]) is not assignable to 'com/vaadin/server/KeyMapper$ByteBuddy$WlWljaQa'
  Current Frame:
    bci: @4
    flags: { }
    locals: { 'com/vaadin/server/KeyMapper$ByteBuddy$WlWljaQa' }
    stack: { 'com/vaadin/server/KeyMapper' }
  Bytecode:
    0x0000000: 2ab4 000c b600 1cb0                    

    at java.lang.Class.getDeclaredFields0(Native Method)
    at java.lang.Class.privateGetDeclaredFields(Class.java:2583)
    at java.lang.Class.getDeclaredField(Class.java:2068)
    at net.bytebuddy.implementation.LoadedTypeInitializer$ForStaticField.onLoad(LoadedTypeInitializer.java:101)
    at net.bytebuddy.implementation.LoadedTypeInitializer$Compound.onLoad(LoadedTypeInitializer.java:180)
    at net.bytebuddy.dynamic.TypeResolutionStrategy$Passive.initialize(TypeResolutionStrategy.java:75)
    at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:4525)
    at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:4514)
    at test.KeyMapperWrapper.patch(KeyMapperWrapper.java:62)
 .method(ElementMatchers.any()) //
 .intercept(MethodDelegation.to(original)) //
 .method(ElementMatchers.named("get")) //
 .intercept(MethodDelegation.to(new KeyMapperWrapper(grid, original))) //

找到了具有InvocationHandlerAdapter的解决方案。我仍然不明白为什么我最初的尝试没有成功

public class KeyMapperProxyHandler implements InvocationHandler
{
  @SuppressWarnings("unused")
  private static final Logger LOGGER              = Logger.getLogger(KeyMapperProxyHandler.class.getName());

  private Grid                m_grid              = null;
  private KeyMapper           m_originalKeyMapper = null;

  public KeyMapperProxyHandler(Grid grid, KeyMapper originalKeyMapper)
  {
    m_grid = grid;
    m_originalKeyMapper = originalKeyMapper;
  }

  /**
   * call after container data source has been set
   */
  public static void patch(Grid grid)
  {
    try
    {
      Field datasourceExtensionField = Grid.class.getDeclaredField("datasourceExtension");
      datasourceExtensionField.setAccessible(true);
      RpcDataProviderExtension rpcDataProviderExtension = (RpcDataProviderExtension) datasourceExtensionField.get(grid);

      Field activeItemHandlerField = RpcDataProviderExtension.class.getDeclaredField("activeItemHandler");
      activeItemHandlerField.setAccessible(true);
      Object activeItemHandler = activeItemHandlerField.get(rpcDataProviderExtension);

      Field keyMapperField = activeItemHandler.getClass().getDeclaredField("keyMapper");
      keyMapperField.setAccessible(true);
      KeyMapper original = (KeyMapper) keyMapperField.get(activeItemHandler);

      KeyMapperProxyHandler proxyHandler = new KeyMapperProxyHandler(grid, original);

      KeyMapper proxy = new ByteBuddy() //
          .subclass(KeyMapper.class) //
          .method(ElementMatchers.any()) //
          .intercept(InvocationHandlerAdapter.of(proxyHandler)) //
          .method(ElementMatchers.named("get")) //
          .intercept(MethodDelegation.to(proxyHandler)) //
          .make() //
          .load(KeyMapperProxyHandler.class.getClassLoader()) //
          .getLoaded() //
          .newInstance();

      keyMapperField.set(activeItemHandler, proxy);
    }
    catch(Throwable t)
    {
      throw new RuntimeException(t);
    }
  }

  /**
   * override for get method
   */
  public Object get(String key)
  {
    Object staleItemId = m_originalKeyMapper.get(key);
    Optional freshItemId = m_grid.getContainerDataSource().getItemIds().stream().filter(i -> i.equals(staleItemId)).findAny();
    //    LOGGER.log(Level.INFO, "intercept: stale=" + staleItemId + ", fresh=" + freshItemId);
    return freshItemId.isPresent() ? freshItemId.get() : null;
  }

  /**
   * proxy all other methods
   */
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
  {
    return method.invoke(m_originalKeyMapper, args);
  }
}

这是Byte Buddy中的一个bug。但是,您尝试创建的类型不合法,Byte Buddy无法向您提供正确的错误消息。当子类化时,重写受保护的方法是合法的。然而,由于可见性约束,在另一种类型上调用这些方法并不总是合法的。因此,在转发呼叫时,只能覆盖
public
方法。您要做的是匹配:

.method(ElementMatchers.isPublic())

错误不再发生的地方。在Byte Buddy的下一版本(1.5.8)中,我已经添加了一个修复程序来向您提供此错误消息。

这并不是我所问问题的答案,而是我的问题的另外两个解决方案:

我确信我可以在尚未使用的时间点替换所需的对象引用。因此,我决定不委托/代理任何内容,并将原始对象实例扔掉,完全替换为byte buddy动态类型:

public class GridKeyMapperPatch
{
  @SuppressWarnings("unused")
  private static final Logger LOGGER = Logger.getLogger(GridKeyMapperPatch.class.getName());

  private Grid                m_grid = null;

  public GridKeyMapperPatch(Grid grid)
  {
    m_grid = grid;
  }

  /**
   * call immediately after setting container data source
   */
  public static void patch(Grid grid)
  {
    try
    {
      GridKeyMapperPatch gridKeyMapperPatch = new GridKeyMapperPatch(grid);

      KeyMapper patchedKeyMapper = new ByteBuddy() //
          .subclass(KeyMapper.class) //
          .method(ElementMatchers.named("get")) //
          .intercept(MethodDelegation.to(gridKeyMapperPatch)) //
          .make() //
          .load(GridKeyMapperPatch.class.getClassLoader()) //
          .getLoaded() //
          .newInstance();

      Field datasourceExtensionField = Grid.class.getDeclaredField("datasourceExtension");
      datasourceExtensionField.setAccessible(true);
      RpcDataProviderExtension rpcDataProviderExtension = (RpcDataProviderExtension) datasourceExtensionField.get(grid);

      Field activeItemHandlerField = RpcDataProviderExtension.class.getDeclaredField("activeItemHandler");
      activeItemHandlerField.setAccessible(true);
      Object activeItemHandler = activeItemHandlerField.get(rpcDataProviderExtension);

      Field keyMapperField = activeItemHandler.getClass().getDeclaredField("keyMapper");
      keyMapperField.setAccessible(true);
      keyMapperField.set(activeItemHandler, patchedKeyMapper);
    }
    catch(Throwable t)
    {
      throw new RuntimeException(t);
    }
  }

  public Object get(@SuperCall Callable superCall, String key) throws Exception
  {
    Object staleItemId = superCall.call();
    Optional freshItemId = m_grid.getContainerDataSource().getItemIds().stream().filter(i -> i.equals(staleItemId)).findAny();
    LOGGER.log(Level.INFO, "intercept: stale=" + staleItemId + ", fresh=" + freshItemId);
    return freshItemId.isPresent() ? freshItemId.get() : null;
  }
}
退一步,我发现,由于所讨论的类和方法是公共的,我通常可以将其子类化:

public class GridTools
{
  @SuppressWarnings("unused")
  private static final Logger LOGGER = Logger.getLogger(GridTools.class.getName());

  /**
   * call immediately after setting container data source
   */
  public static void replaceKeyMapper(Grid grid) throws Exception
  {
    Field datasourceExtensionField = Grid.class.getDeclaredField("datasourceExtension");
    datasourceExtensionField.setAccessible(true);
    RpcDataProviderExtension rpcDataProviderExtension = (RpcDataProviderExtension) datasourceExtensionField.get(grid);

    Field activeItemHandlerField = RpcDataProviderExtension.class.getDeclaredField("activeItemHandler");
    activeItemHandlerField.setAccessible(true);
    Object activeItemHandler = activeItemHandlerField.get(rpcDataProviderExtension);

    Field keyMapperField = activeItemHandler.getClass().getDeclaredField("keyMapper");
    keyMapperField.setAccessible(true);
    keyMapperField.set(activeItemHandler, new NonCachingKeyMapper(grid));
  }

  private static class NonCachingKeyMapper extends KeyMapper
  {
    private Grid m_grid = null;

    public NonCachingKeyMapper(Grid grid)
    {
      m_grid = grid;
    }

    @Override
    public Object get(String key)
    {
      Object staleItemId = super.get(key);
      Optional freshItemId = m_grid.getContainerDataSource().getItemIds().stream().filter(i -> i.equals(staleItemId)).findAny();
      LOGGER.log(Level.INFO, "intercept: stale=" + staleItemId + ", fresh=" + freshItemId);
      return freshItemId.isPresent() ? freshItemId.get() : null;
    }
  }
}

非常感谢您调查此事!我试图子类化的类型只有公共方法,所以它不应该是合法的吗?但是我需要的下一个代理可能没有所有的公共方法,所以也许使用InvocationHandlerAdapter更好?(并在调用之前使用method.setAccessible(true))它从
对象继承方法。有问题的方法是
Object::clone
,您可以从验证器错误中看到。我明白了,谢谢!我想我会坚持使用调用HandlerAdapter方法。我刚刚发布了新版本。如果您以性能为目标,请注意,转发比调用处理程序更高效。另外,请注意,如果调用
clone
,调用处理程序适配器将导致运行时异常。我的覆盖可能是这里最大的性能杀手。。。即使我调用了method.setAccessible(true),它也会为克隆抛出异常吗?当像这样更改库内部时,我更愿意委托非公共方法,希望保持内部状态正确。