diff --git a/docs/.vuepress/config/zh/series.ts b/docs/.vuepress/config/zh/series.ts index 3fd5110..1d63d92 100644 --- a/docs/.vuepress/config/zh/series.ts +++ b/docs/.vuepress/config/zh/series.ts @@ -23,6 +23,7 @@ export const series = { { text: 'Map 转对象', link: '/guide/map-to-class' }, { text: '枚举转换', link: '/guide/enum-convert' }, { text: '一个类与多个类之间转换', link: '/guide/multiple-class-convert' }, + { text: '类循环嵌套', link: '/guide/cycle-avoiding' }, { text: '类转换API', link: '/guide/converter-api' }, { text: '配置项', link: '/guide/configuration' }, { text: '常见问题', link: '/guide/faq' }, diff --git a/docs/README.md b/docs/README.md index 7682197..41b8007 100644 --- a/docs/README.md +++ b/docs/README.md @@ -62,18 +62,26 @@ footer: io.github.linpeilie mapstruct-plus-spring-boot-starter - 1.3.6 + 1.4.0-R1 ``` - gradle ```groovy -implementation group: 'io.github.linpeilie', name: 'mapstruct-plus-spring-boot-starter', version: '1.3.6' +implementation group: 'io.github.linpeilie', name: 'mapstruct-plus-spring-boot-starter', version: '1.4.0-R1' ``` ## 更新日志 +### 1.4.0 + +- **优化复杂对象转换逻辑,占用元空间更小!性能更快!** +- 去除 hutool 等依赖,目前项目中只依赖了 MapStruct +- 适配对象循环嵌套场景 +- [feature#63](https://github.com/linpeilie/mapstruct-plus/pull/63)`AutoMapping`、`ReverseAutoMapping` 支持 `qualifiedByName`、`conditionQualifiedByName` 和 `dependsOn` 属性 +- [issue#I93Z2Z](https://gitee.com/easii/mapstruct-plus/issues/I93Z2Z)`AutoMappings` 支持配置在方法上面 + ### 1.3.6 - 兼容内部类转换 diff --git a/docs/en/README.md b/docs/en/README.md index f2a03a2..403c0b2 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -58,18 +58,26 @@ fotter: io.github.linpeilie mapstruct-plus-spring-boot-starter - 1.3.6 + 1.4.0-R1 ``` - gradle ```groovy -implementation group: 'io.github.linpeilie', name: 'mapstruct-plus-spring-boot-starter', version: '1.3.6' +implementation group: 'io.github.linpeilie', name: 'mapstruct-plus-spring-boot-starter', version: '1.4.0-R1' ``` ## Change Log +### 1.4.0 + +- **Optimize complex object conversion logic, take up less meta-space! and faster!** +- Get rid of dependencies such as hutool, which currently only rely on MapStruct in the project. +- The adaptation object loop nesting scenario +- [feature#63](https://github.com/linpeilie/mapstruct-plus/pull/63) `AutoMapping`、`ReverseAutoMapping` supports `qualifiedByName`,`conditionQualifiedByName`,and `dependsOn` properties. +- [issue#I93Z2Z](https://gitee.com/easii/mapstruct-plus/issues/I93Z2Z) `AutoMappings` supports configuration on methods. + ### 1.3.6 - Compatible with internal class conversion. diff --git a/docs/en/release/log.md b/docs/en/release/log.md index 8b97ba6..16e2c73 100644 --- a/docs/en/release/log.md +++ b/docs/en/release/log.md @@ -6,6 +6,14 @@ category: description: MapStructPlus release log --- +### 1.4.0 + +- **Optimize complex object conversion logic, take up less meta-space! and faster!** +- Get rid of dependencies such as hutool, which currently only rely on MapStruct in the project. +- The adaptation object loop nesting scenario +- [feature#63](https://github.com/linpeilie/mapstruct-plus/pull/63) `AutoMapping`、`ReverseAutoMapping` supports `qualifiedByName`,`conditionQualifiedByName`,and `dependsOn` properties. +- [issue#I93Z2Z](https://gitee.com/easii/mapstruct-plus/issues/I93Z2Z) `AutoMappings` supports configuration on methods. + ### 1.3.6 - Compatible with internal class conversion. diff --git a/docs/guide/cycle-avoiding.md b/docs/guide/cycle-avoiding.md new file mode 100644 index 0000000..23d8c63 --- /dev/null +++ b/docs/guide/cycle-avoiding.md @@ -0,0 +1,82 @@ +--- +title: 类循环嵌套 +order: 7 +category: +- 指南 +description: MapStructPlus MapStructPlus类循环嵌套 CycleAvoiding +--- + +## 背景 + +类循环嵌套是指两个类互相引用,例如,源对象和目标对象结构都包含父对象和子对象之间的双向关联。 +当存在这种情况时,直接进行转换时,会导致栈溢出的问题(stack overflow error)。 + +示例: + +```java +@Data +public class TreeNode { + private TreeNode parent; + private List children; +} + +@Data +public class TreeNodeDto { + private TreeNodeDto parent; + private List children; +} +``` + +`parent` 属性可以是其他类型的,可能跨越一个更长的属性链形成的嵌套循环。 + +为了适配这种情况,MapStructPlus 的 **`AutoMapper`** 注解中增加了 **`cycleAvoiding`** 属性,该属性用于标识,是否需要避免循环嵌套的问题。 +默认为 `false`,如果需要避免循环嵌套,需要将该属性设置为 `true`。 + +当配置为 `true` 时,在整个对象的转换过程链路中,会传递一个 `CycleAvoidingMappingContext` 对象,临时保存转换生成的对象, +在转换链路中,如果发现需要生成的对象已经存在,会直接返回该类型,从而避免栈溢出问题。 +所以,配置该属性为 `true` 时,会有一点的性能消耗,如果没有循环嵌套的情况,使用默认配置即可,避免不必要的性能消耗。 + +## 使用示例 + +以上面的示例为例,在 `AutoMapper` 注解中,配置 `cycleAvoiding` 属性为 `true`,如下所示: + +```java +@Data +@AutoMapper(target = TreeNodeDto.class, cycleAvoiding = true) +public class TreeNode { + private TreeNode parent; + private List children; +} + +@Data +@AutoMapper(target = TreeNode.class, cycleAvoiding = true) +public class TreeNodeDto { + private TreeNodeDto parent; + private List children; +} +``` + +编译生成的转换逻辑如下: + +```java +public TreeNodeDto convert(TreeNode arg0, CycleAvoidingMappingContext arg1) { + TreeNodeDto target = arg1.getMappedInstance(arg0, TreeNodeDto.class); + if (target != null) { + return target; + } + + if (arg0 == null) { + return null; + } + + TreeNodeDto treeNodeDto = new TreeNodeDto(); + + arg1.storeMappedInstance(arg0, treeNodeDto); + + treeNodeDto.setParent(demoConvertMapperAdapterForCycleAvoiding.iglm_TreeNodeToTreeNodeDto(arg0.getParent(), arg1)); + treeNodeDto.setChildren( + demoConvertMapperAdapterForCycleAvoiding.iglm_TreeNodeToTreeNodeDto(arg0.getChildren(), arg1)); + + return treeNodeDto; +} +``` \ No newline at end of file diff --git a/docs/guide/faq.md b/docs/guide/faq.md index 41b257c..f08f39e 100644 --- a/docs/guide/faq.md +++ b/docs/guide/faq.md @@ -1,6 +1,6 @@ --- title: 常见问题 -order: 7 +order: 8 category: - 指南 description: MapStructPlus MapStructPlus常见问题 faq diff --git a/docs/guide/map-to-class.md b/docs/guide/map-to-class.md index 6a80b3a..7bb1dfa 100644 --- a/docs/guide/map-to-class.md +++ b/docs/guide/map-to-class.md @@ -8,8 +8,26 @@ description: MapStructPlus Map转为对象 map convert to class MapStructPlus 提供了更加强大的 `Map` 转对象的功能。 +::: warning +MapStructPlus 1.4.0 及以后版本,不再内置 [Hutool](https://hutool.cn) 框架,如果需要用到该功能时,需要额外引入 `hutool-core` 依赖。 +::: + ## 使用 +### 添加依赖 + +> 1.4.0 及以后的版本需要添加该依赖,1.4.0之前的版本内置 hutool,不需要额外添加。 + +```xml + + cn.hutool + hutool-core + ${hutool.version} + +``` + +### 添加注解 + **当想要自动生成 `Map` 转为目标类的接口及实现类时,只需要在目标类上添加 `@AutoMapMapper` 注解**。 ## 支持的 value 类型 diff --git a/docs/release/log.md b/docs/release/log.md index 175f8fe..cf7dc5d 100644 --- a/docs/release/log.md +++ b/docs/release/log.md @@ -6,6 +6,14 @@ category: description: MapStructPlus release log --- +### 1.4.0 + +- **优化复杂对象转换逻辑,占用元空间更小!性能更快!** +- 去除 hutool 等依赖,目前项目中只依赖了 MapStruct +- 适配对象循环嵌套场景 +- [feature#63](https://github.com/linpeilie/mapstruct-plus/pull/63)`AutoMapping`、`ReverseAutoMapping` 支持 `qualifiedByName`、`conditionQualifiedByName` 和 `dependsOn` 属性 +- [issue#I93Z2Z](https://gitee.com/easii/mapstruct-plus/issues/I93Z2Z)`AutoMappings` 支持配置在方法上面 + ### 1.3.6 - 兼容内部类转换 diff --git a/example/pom.xml b/example/pom.xml index 40528de..a16c8ff 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -18,7 +18,7 @@ UTF-8 1.5.1.Final - 1.4.0 + 1.4.0-R1 1.18.22 5.8.26 diff --git a/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/mapper/Titles.java b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/mapper/Titles.java new file mode 100644 index 0000000..a30b35d --- /dev/null +++ b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/mapper/Titles.java @@ -0,0 +1,27 @@ +package io.github.linpeilie.mapper; + +import org.mapstruct.Named; +import org.springframework.boot.context.properties.bind.Name; +import org.springframework.stereotype.Component; + +@Component +@Named("TitleTranslator") +public class Titles { + + @Named("EnglishToFrench") + public String translateTitleEF(String title) { + if ("One Hundred Years of Solitude".equals(title)) { + return "Cent ans de solitude"; + } + return "Inconnu et inconnu"; + } + + @Named("FrenchToEnglish") + public String translateTitleFE(String title) { + if ("Cent ans de solitude".equals(title)) { + return "One Hundred Years of Solitude"; + } + return "Unknown"; + } + +} diff --git a/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/EnglishRelease.java b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/EnglishRelease.java new file mode 100644 index 0000000..87f5082 --- /dev/null +++ b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/EnglishRelease.java @@ -0,0 +1,15 @@ +package io.github.linpeilie.model; + +import io.github.linpeilie.annotations.AutoMapper; +import io.github.linpeilie.annotations.AutoMapping; +import io.github.linpeilie.mapper.Titles; +import lombok.Data; + +@Data +@AutoMapper(target = FrenchRelease.class, uses = Titles.class) +public class EnglishRelease { + + @AutoMapping(qualifiedByName = "EnglishToFrench") + private String title; + +} diff --git a/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/FrenchRelease.java b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/FrenchRelease.java new file mode 100644 index 0000000..fdbd4e3 --- /dev/null +++ b/example/spring-boot-with-lombok/src/main/java/io/github/linpeilie/model/FrenchRelease.java @@ -0,0 +1,15 @@ +package io.github.linpeilie.model; + +import io.github.linpeilie.annotations.AutoMapper; +import io.github.linpeilie.annotations.AutoMapping; +import io.github.linpeilie.mapper.Titles; +import lombok.Data; + +@Data +@AutoMapper(target = EnglishRelease.class, uses = Titles.class) +public class FrenchRelease { + + @AutoMapping(qualifiedByName = "FrenchToEnglish") + private String title; + +} diff --git a/example/spring-boot-with-lombok/src/test/java/io/github/linpeilie/QuickStartTest.java b/example/spring-boot-with-lombok/src/test/java/io/github/linpeilie/QuickStartTest.java index 7ef1639..ee90bba 100644 --- a/example/spring-boot-with-lombok/src/test/java/io/github/linpeilie/QuickStartTest.java +++ b/example/spring-boot-with-lombok/src/test/java/io/github/linpeilie/QuickStartTest.java @@ -2,7 +2,10 @@ package io.github.linpeilie; import cn.hutool.core.date.DateUtil; import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.StrUtil; import io.github.linpeilie.model.Car; +import io.github.linpeilie.model.EnglishRelease; +import io.github.linpeilie.model.FrenchRelease; import io.github.linpeilie.model.Goods; import io.github.linpeilie.model.GoodsDto; import io.github.linpeilie.model.GoodsStateEnum; @@ -16,6 +19,8 @@ import io.github.linpeilie.model.SVO; import io.github.linpeilie.model.Sku; import io.github.linpeilie.model.SysMenu; import io.github.linpeilie.model.SysMenuVo; +import io.github.linpeilie.model.TreeNode; +import io.github.linpeilie.model.TreeNodeDto; import io.github.linpeilie.model.User; import io.github.linpeilie.model.UserDto; import io.github.linpeilie.model.UserVO; @@ -262,4 +267,48 @@ public class QuickStartTest { System.out.println(sDto1); } + @Test + public void testConditionQualifiedByName() { + TreeNode parent = new TreeNode(); + // children + List children = new ArrayList<>(); + for (int i = 0; i < 1; i++) { + TreeNode child = new TreeNode(); + child.setParent(parent); + children.add(child); + } + parent.setChildren(children); + TreeNodeDto treeNodeDto1 = converter.convert(parent, TreeNodeDto.class); + // 当 children 为 <2 时不进行转换 + Assert.isNull(treeNodeDto1.getChildren()); + for (int i = 0; i < 99; i++) { + TreeNode child = new TreeNode(); + child.setParent(parent); + children.add(child); + } + TreeNodeDto treeNodeDto2 = converter.convert(parent, TreeNodeDto.class); + Assert.equals(treeNodeDto2.getChildren().size(), 100); + + treeNodeDto2.getChildren().forEach(child -> { + Assert.equals(child.getParent(), treeNodeDto2); + }); + } + + @Test + public void testQualifiedByName() { + EnglishRelease englishRelease = new EnglishRelease(); + englishRelease.setTitle("Algorithms, 4th Edition"); + FrenchRelease frenchRelease1 = converter.convert(englishRelease, FrenchRelease.class); + Assert.equals(frenchRelease1.getTitle(), "Inconnu et inconnu"); + englishRelease.setTitle("One Hundred Years of Solitude"); + FrenchRelease frenchRelease2 = converter.convert(englishRelease, FrenchRelease.class); + Assert.equals(frenchRelease2.getTitle(), "Cent ans de solitude"); + + EnglishRelease englishRelease1 = converter.convert(frenchRelease1, EnglishRelease.class); + Assert.equals(englishRelease1.getTitle(), "Unknown"); + frenchRelease2.setTitle("Cent ans de solitude"); + EnglishRelease englishRelease2 = converter.convert(frenchRelease2, EnglishRelease.class); + Assert.equals(englishRelease2.getTitle(), "One Hundred Years of Solitude"); + } + } diff --git a/pom.xml b/pom.xml index ca1de3a..904ab45 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ - 1.4.0 + 1.4.0-R1 8 8 UTF-8