英文:
Why can't I reference a @Context parameter in the @Mapping attributes?
问题
使用mapstruct,我需要一个映射方法,其中包含多个源,并且这些多个源被传递到其他映射方法中,以便我可以在需要这些附加源的所有映射方法中都拥有所有这些多个源。
目前有两个功能可能可以一起使用:
- 只有@Context可以传递到其他映射方法,但不能用作源。
- 可以使用第二个参数(非@Context)作为源,但不会传递到其他映射方法。
因此,要求的功能要么是允许将次要源参数传递到其他映射方法,要么是使@Context参数能够被@Mapping(target="something", source="ctx.somethingElse")
或@Mapping(target="ctx.something", source="somethingElse")
引用。
示例:
// 源类:Instant timestamp 是我单独获取的字段
Instant timestamp;
class WrapperSource
List<NestedSource> nested;
class NestedSource
String name;
// 目标类:我希望映射嵌套和名称字段,还要将时间戳插入到WrapperTarget和嵌套列表中的每个NestedTarget中
class WrapperTarget
Instant timestamp;
List<NestedTarget> nested;
class NestedTarget
String name;
Instant timestamp;
理想情况下,映射应该是这样的:
// 目前这不起作用,因为我们无法在源属性中引用@Context
@Mapping(target = "nested", source="source.nested")
@Mapping(target = "timestamp", source="timestamp")
WrapperTarget map(WrapperSource source, @Context Instant timestamp);
@Mapping(target = "name", source="source.name")
@Mapping(target = "timestamp", source="timestamp")
NestedTarget map(NestedSource source, @Context Instant timestamp);
或者:
// 目前这不起作用,因为第一个生成的方法不调用具有2个源的第二个方法
@Mapping(target = "nested", source="source.nested")
@Mapping(target = "timestamp", source="timestamp")
WrapperTarget map(WrapperSource source, Instant timestamp);
@Mapping(target = "name", source="source.name")
@Mapping(target = "timestamp", source="timestamp")
NestedTarget map(NestedSource source, Instant timestamp);
我唯一的(冗长的)解决方法是:
// @Context 被传递,并且我可以在@AfterMapping中手动将其用作源,但需要额外的代码
WrapperTarget map(WrapperSource source, @Context Instant timestamp);
@AfterMapping
void map(WrapperSource source, @MappingTarget WrapperTarget target, @Context Instant timestamp) {
target.setTimestamp(timestamp);
}
NestedTarget map(NestedSource source, @Context Instant timestamp);
@AfterMapping
void map(NestedSource source, @MappingTarget NestedTarget target, @Context Instant timestamp) {
target.setTimestamp(timestamp);
}
这个方法可以正常工作,但需要额外的手动代码,因此一个更好的解决方法是能够在@Mapping
的属性中引用@Context
。这样,我可以使用第一个“理想”映射示例。
对于这个问题,是否有更好的解决方法?
英文:
Using mapstruct, what I need is a mapping method with several sources and that these several sources are passed around to other mapping methods so I can have all my several sources for all mapping methods where I need these additional sources.
Currently there are two features that could be made to work together maybe :
- only the @Context can be passed around to other mappings methods but it can't be used as a source.
- a secondary parameter (non @Context) can be used as a source but it is not passed around to other mapping methods
So the feature need would be either to allow secondary source parameters to be passed around to other mapping methods or to make the @Context parameter able to be referenced by a @Mapping(target="something", source="ctx.somethingElse")
or @Mapping(target="ctx.something", source="somethingElse)
Example :
// source classes : `Instant timestamp` is a field I obtain separately
Instant timestamp;
class WrapperSource
List<NestedSource> nested;
class NestedSource
String name;
// target classes : I want to map the nested and name field but also to insert the timestamp in both the WrapperTarget and every NestedTarget in the nested list
class WrapperTarget
Instant timestamp;
List<NestedTarget> nested;
class NestedTarget
String name;
Instant timestamp;
Ideally, the mapping would be something like :
// Currently this doesn't work because we can't reference the @Context in the source attribute
@Mapping(target = "nested", source="source.nested")
@Mapping(target = "timestamp", source="timestamp")
WrapperTarget map(WrapperSource source, @Context Instant timestamp);
@Mapping(target = "name", source="source.name")
@Mapping(target = "timestamp", source="timestamp")
NestedTarget map(NestedSource source, @Context Instant timestamp);
Or :
// Currently this doesn't work because the second method with 2 sources in not called by the first generated method
@Mapping(target = "nested", source="source.nested")
@Mapping(target = "timestamp", source="timestamp")
WrapperTarget map(WrapperSource source, Instant timestamp);
@Mapping(target = "name", source="source.name")
@Mapping(target = "timestamp", source="timestamp")
NestedTarget map(NestedSource source, Instant timestamp);
The only (verbose) workaround that works for me is :
// @Context is passed around and I can manually use it as a source in an @AfterMapping but it requires additional code
WrapperTarget map(WrapperSource source, @Context Instant timestamp);
@AfterMapping
void map(WrapperSource source, @MappingTarget WrapperTarget target, @Context Instant timestamp) {
target.setTimestamp(timestamp);
}
NestedTarget map(NestedSource source, @Context Instant timestamp);
@AfterMapping
void map(NestedSource source, @MappingTarget NestedTarget target, @Context Instant timestamp) {
target.setTimestamp(timestamp);
}
This works alright but it required additional manual code, so a better alternative would be to be able to reference a @Context
in a @Mapping's attributes
. This way I could use the first "ideal" mapping example.
Is there a better workaround for this issue ?
答案1
得分: 3
@Context
应该是参数所暗示的内容。它提供了映射的上下文信息,因此不参与映射本身。映射原则上是从源到目标。
记住:MapStruct可以解决许多问题,但不可能解决所有问题。
然而,你可以尝试这样做:
class WrapperTarget implements TimeStamped
Instant timestamp;
List<NestedTarget> nested;
class NestedTarget implements TimeStamped
String name;
Instance timestamp;
interface TimeStamped{
void setTimestamp(Instance timeStamp);
}
定义你自己的上下文... MapStruct会自动在你定义的上下文中调用"after mapping"。你甚至可以在上下文中加入更多的内容,比如在映射之前从仓库中解析一些内容,id之类的。
class MyContext {
Instance timestamp;
@AfterMapping
map(@MappingTarget TimeStamped timeStamped)
}
在这种情况下,你的映射代码保持干净,不会包含上下文。当然,在调用方法之前你需要初始化上下文。也许你可以在上下文的构造中构造时间戳(如果要求你在所有地方使用相同的时间戳)。
@Mapping(target = "nested", source="source.nested")
WrapperTarget map(WrapperSource source, @Context MyContext ctx);
@Mapping(target = "name", source="source.name")
NestedTarget map(NestedSource source, @Context MyContext ctx);
关于如何使用上下文,你可以查看这个示例。
英文:
@Context
should be what the parameter suggests what it is. Context information to a mapping and hence not partake in the mapping itself. Mapping is in principle from source to target.
Remember: MapStruct can solve a lot of problems, but it will never be able to solve them all.
However: you can try this:
class WrapperTarget implements TimeStamped
Instant timestamp;
List<NestedTarget> nested;
class NestedTarget implements TimeStamped
String name;
Instance timestamp;
interface TimeStamped{
void setTimestamp(Instance timeStamp);
}
Define your own context... MapStruct calls the after mapping on the context you defined automatically. You can put even more things in the context like this, e.g. resolving stuff from repositories in before mapping, id's... etc.
class MyContext {
Instance timestamp;
@AfterMapping
map(@MappingTarget TimeStamped timeStamped)
}
You mapping remains clean from the context in that case. You need to initialise the context of course before you call the method. Perhaps you can construct the time instant in the construction of the context (if the requirement is that you include the same time instant everywhere)..
@Mapping(target = "nested", source="source.nested")
WrapperTarget map(WrapperSource source, @Context MyContext ctx);
@Mapping(target = "name", source="source.name")
NestedTarget map(NestedSource source, @Context MyContext ctx);
For use of context, you can checkout this example.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论