如何使用 MethodDelegation 或 Forwarding 创建 Byte Buddy 代理?
How to create a Byte Buddy proxy with MethodDelegation or Forwarding?
当我尝试执行以下操作时,加载调用出现异常:
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)
我显然不明白 Forwarding
应该如何工作,我做错了什么?
我的目的是用一个代理替换现有的 KeyMapper
,我 覆盖 一个方法,并将其余的委托给原始方法。
编辑:我现在也尝试使用 MethodDelegation
,它抛出相同的异常:
.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 无法为您提供正确的错误消息。子类化时,覆盖 protected
方法是合法的。然而,由于可见性限制,在另一种类型上调用这些方法并不总是合法的。因此,当您转接呼叫时,您只能覆盖 public
方法。你要做的是匹配:
.method(ElementMatchers.isPublic())
错误不再发生的地方。我已经在 Byte Buddy 的下一个版本 (1.5.8) 中添加了一个修复程序来为您提供此错误消息。
并不是我所问问题的真正答案,而是我的问题的另外两个替代解决方案:
我说服自己可以在尚未使用的时间点替换所需的对象引用。所以我决定不 delegate/proxy 任何东西,并丢弃原始对象实例,将其完全替换为 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;
}
}
退一步,然后我发现,由于 class 和有问题的方法是 public,我可以正常地 subclass 它:
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;
}
}
}
当我尝试执行以下操作时,加载调用出现异常:
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)
我显然不明白 Forwarding
应该如何工作,我做错了什么?
我的目的是用一个代理替换现有的 KeyMapper
,我 覆盖 一个方法,并将其余的委托给原始方法。
编辑:我现在也尝试使用 MethodDelegation
,它抛出相同的异常:
.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 无法为您提供正确的错误消息。子类化时,覆盖 protected
方法是合法的。然而,由于可见性限制,在另一种类型上调用这些方法并不总是合法的。因此,当您转接呼叫时,您只能覆盖 public
方法。你要做的是匹配:
.method(ElementMatchers.isPublic())
错误不再发生的地方。我已经在 Byte Buddy 的下一个版本 (1.5.8) 中添加了一个修复程序来为您提供此错误消息。
并不是我所问问题的真正答案,而是我的问题的另外两个替代解决方案:
我说服自己可以在尚未使用的时间点替换所需的对象引用。所以我决定不 delegate/proxy 任何东西,并丢弃原始对象实例,将其完全替换为 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;
}
}
退一步,然后我发现,由于 class 和有问题的方法是 public,我可以正常地 subclass 它:
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;
}
}
}