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