如何使用 ByteBuddy 向现有实例添加字段?
How to add a field to an existing instance with ByteBuddy?
我需要将文档从 Spring 应用程序发送到 MongoDB 实例,我可以在其中利用其数据包中的 MongoTemplate。
但是 Spring 将那些实例 id
字段作为 MongoDB 文档 ID,导致数据库中的重复 ID,从而防止重复实例。
这是在一个项目中:
- 我需要将数据扔到 MongoDB 实例以便以后从数据库本身进行分析,而不是从应用程序(所以我不在乎我是否不能查询它们);
- 可能存在重复文档,并且是要求的一部分;
- 文档是 Web 服务请求的反序列化,我可以在 class 定义中添加一个
_id
字段,但是来自 WSDL 的未来源代码生成将丢弃该字段;
- 在此初始阶段,出于测试目的以这种方式处理文档,在收集和分析一些数据后,它们不会被发送到数据库
阅读 this question 我看到 id
字段对于 Spring 是强制性的,我需要以某种方式添加 _id
字段。
这就是我将文档插入集合的方式:
public void save(List<MyDocument> docs) {
mongoTemplate.insert(MyDocument.class).inCollection("docscoll").all(docs);
}
我是 ByteBuddy 的新手(我认为这可能是适合这项工作的库,但请随意推荐另一个库),通过在此处搜索所以我将这段代码放在一起:
new ByteBuddy()
.redefine(MyDocument.class)
.defineField("_id", int.class, Visibility.PUBLIC)
.make()
.load(MyDocument.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION);
但它失败了:
Cannot inject already loaded type: class com.MyDocument
这行不通。大多数 JVM 不允许向已加载的 class 添加字段。因此,尽管 Byte Buddy 可以调整字节码,但即使在 class 加载后正确完成,这也不会起作用。正确的方法还需要一个 Java 代理,它可以使用 Byte Buddy Agent 项目附加,例如:
new ByteBuddy()
.redefine(MyDocument.class)
.defineField("_id", int.class, Visibility.PUBLIC)
.make()
.load(MyDocument.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
然而,由于 VM 的限制,这将不起作用,您也可以更改方法内容。
您可以附加一个 Java 代理以在应用程序启动之前添加字段,其中 class 尚未加载。这可以使用 Java 代理来完成,Byte Buddy 可以轻松实现这样的代理:
new AgentBuilder.Default()
.type(named("<package>.MyDocument"))
.transform((builder, typeDescription, classLoader, module) -> builder
.defineField("_id", int.class, Visibility.PUBLIC))
.installOn(<instrumentation>);
这样,如果您在代理的 premain
方法中添加此代码,则该字段会在首次加载 class 之前添加。
不过我想知道这是否是解决您的问题的正确方法。我通常宁愿使用弱映射而不是字节码检测。
我需要将文档从 Spring 应用程序发送到 MongoDB 实例,我可以在其中利用其数据包中的 MongoTemplate。
但是 Spring 将那些实例 id
字段作为 MongoDB 文档 ID,导致数据库中的重复 ID,从而防止重复实例。
这是在一个项目中:
- 我需要将数据扔到 MongoDB 实例以便以后从数据库本身进行分析,而不是从应用程序(所以我不在乎我是否不能查询它们);
- 可能存在重复文档,并且是要求的一部分;
- 文档是 Web 服务请求的反序列化,我可以在 class 定义中添加一个
_id
字段,但是来自 WSDL 的未来源代码生成将丢弃该字段; - 在此初始阶段,出于测试目的以这种方式处理文档,在收集和分析一些数据后,它们不会被发送到数据库
阅读 this question 我看到 id
字段对于 Spring 是强制性的,我需要以某种方式添加 _id
字段。
这就是我将文档插入集合的方式:
public void save(List<MyDocument> docs) {
mongoTemplate.insert(MyDocument.class).inCollection("docscoll").all(docs);
}
我是 ByteBuddy 的新手(我认为这可能是适合这项工作的库,但请随意推荐另一个库),通过在此处搜索所以我将这段代码放在一起:
new ByteBuddy()
.redefine(MyDocument.class)
.defineField("_id", int.class, Visibility.PUBLIC)
.make()
.load(MyDocument.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION);
但它失败了:
Cannot inject already loaded type: class com.MyDocument
这行不通。大多数 JVM 不允许向已加载的 class 添加字段。因此,尽管 Byte Buddy 可以调整字节码,但即使在 class 加载后正确完成,这也不会起作用。正确的方法还需要一个 Java 代理,它可以使用 Byte Buddy Agent 项目附加,例如:
new ByteBuddy()
.redefine(MyDocument.class)
.defineField("_id", int.class, Visibility.PUBLIC)
.make()
.load(MyDocument.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
然而,由于 VM 的限制,这将不起作用,您也可以更改方法内容。
您可以附加一个 Java 代理以在应用程序启动之前添加字段,其中 class 尚未加载。这可以使用 Java 代理来完成,Byte Buddy 可以轻松实现这样的代理:
new AgentBuilder.Default()
.type(named("<package>.MyDocument"))
.transform((builder, typeDescription, classLoader, module) -> builder
.defineField("_id", int.class, Visibility.PUBLIC))
.installOn(<instrumentation>);
这样,如果您在代理的 premain
方法中添加此代码,则该字段会在首次加载 class 之前添加。
不过我想知道这是否是解决您的问题的正确方法。我通常宁愿使用弱映射而不是字节码检测。