diff --git a/README.md b/README.md index 91cf813be86a9d12b84763b57bccb1fbee9b9f55..110479ab3ea7171e4f40900c603cfb73c08df748 100644 --- a/README.md +++ b/README.md @@ -188,7 +188,7 @@ public class User { ```xml - 1.4.2 + 1.4.3 diff --git a/docs/README.md b/docs/README.md index aa779a7cf84ea9b8da3255ea4ca379fdb4fa5f1f..8ebe737b0fc2a4c0613a7cc7b0c22d6d17d0ff2e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -62,18 +62,24 @@ footer: io.github.linpeilie mapstruct-plus-spring-boot-starter - 1.4.2 + 1.4.3 ``` - gradle ```groovy -implementation group: 'io.github.linpeilie', name: 'mapstruct-plus-spring-boot-starter', version: '1.4.2' +implementation group: 'io.github.linpeilie', name: 'mapstruct-plus-spring-boot-starter', version: '1.4.3' ``` ## 更新日志 +### 1.4.3 + +- feat: `ComponentModel` 增加 `spring-lazy` 可选项,懒加载 Spring Bean,解决互相依赖的问题,并将默认配置改为该选项; +- fix: 解决 `unmappedTargetPolicy` 默认配置不生效的问题; +- enhance: 优化 IDEA 本地开发构建效率,一定程度上缩短构建时间、减小元空间占用;[Issue #89](https://github.com/linpeilie/mapstruct-plus/issues/89) + ### 1.4.2 - feat: `AutoMapper` 注解增加 `mapperNameSuffix` 属性,支持配置生成的转换接口名称增加后缀,默认规则下生成的反向转换接口同时生效; @@ -101,13 +107,6 @@ implementation group: 'io.github.linpeilie', name: 'mapstruct-plus-spring-boot-s > 当然,这个问题在之前也会有,几率可能低一些,所以多模块下,务必配置 `adapterPackage` 来避免该问题。 > - Map 与对象的转换,还是依赖 hutool 中的类转换实现,如果需要该功能,需要额外引入 `hutool-core` 依赖包。 -### 1.3.6 - -- 兼容内部类转换 -- feature : AutoMapping 注解中的 targetClass 支持配置父类 -- [issue#I8QPRO](https://gitee.com/easii/mapstruct-plus/issues/I8QPRO) : 框架自动生成的 AutoMapperConfig 和 AutoMapMapper 包和类名支持配置 -- [issue#I8T7EF](https://gitee.com/easii/mapstruct-plus/issues/I8T7EF) : 支持在父类中配置的 AutoMapping 注解 - …… ## 代码仓库 diff --git a/docs/en/README.md b/docs/en/README.md index 14cc8feef1908dec00d8c5633a2121094a3843bb..5bfff1a5efa3cfcb61821e708b99257ae7d635b3 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -58,25 +58,25 @@ fotter: io.github.linpeilie mapstruct-plus-spring-boot-starter - 1.4.0 + 1.4.3 ``` - gradle ```groovy -implementation group: 'io.github.linpeilie', name: 'mapstruct-plus-spring-boot-starter', version: '1.4.0' +implementation group: 'io.github.linpeilie', name: 'mapstruct-plus-spring-boot-starter', version: '1.4.3' ``` ## Change Log -### 1.4.2 - -Sure, here is the translated update document: +### 1.4.3 ---- +- **feat**: Added `spring-lazy` option to `ComponentModel` for lazy loading Spring Beans, resolving mutual dependency issues, and set this option as the default configuration. +- **fix**: Fixed the issue where the default configuration for `unmappedTargetPolicy` was not effective. +- **enhance**: Optimized IDEA local development build efficiency, reducing build time and metaspace usage to some extent.[Issue #89](https://github.com/linpeilie/mapstruct-plus/issues/89) -### Updates +### 1.4.2 - **feat**: Added the `mapperNameSuffix` attribute to the `AutoMapper` annotation. This supports adding a suffix to the generated conversion interface name, and the reverse conversion interface will be effective under the default rules. - **feat**: Adapted the `Mapper` annotation to support the following attributes: `unmappedSourcePolicy`, `unmappedTargetPolicy`, `typeConversionPolicy`, `collectionMappingStrategy`, `nullValueMappingStrategy`, `nullValueIterableMappingStrategy`, `nullValuePropertyMappingStrategy`, `nullValueCheckStrategy`, and `mappingControl`. diff --git a/docs/en/release/log.md b/docs/en/release/log.md index 4e1682054173373231d055357b11865649070b53..e5294371291b5d8d68eaf65b6e4d2d89925c8db0 100644 --- a/docs/en/release/log.md +++ b/docs/en/release/log.md @@ -6,6 +6,12 @@ category: description: MapStructPlus release log --- +### 1.4.3 + +- **feat**: Added `spring-lazy` option to `ComponentModel` for lazy loading Spring Beans, resolving mutual dependency issues, and set this option as the default configuration. +- **fix**: Fixed the issue where the default configuration for `unmappedTargetPolicy` was not effective. +- **enhance**: Optimized IDEA local development build efficiency, reducing build time and metaspace usage to some extent.[Issue #89](https://github.com/linpeilie/mapstruct-plus/issues/89) + ### 1.4.2 - **feat**: Added the `mapperNameSuffix` attribute to the `AutoMapper` annotation. This supports adding a suffix to the generated conversion interface name, and the reverse conversion interface will be effective under the default rules. diff --git a/docs/release/log.md b/docs/release/log.md index f69f9f019c16c120ebac9c445915a685127aff9a..2d5c40ff9bd4d5f4cc14a0b263f93a9333c577b0 100644 --- a/docs/release/log.md +++ b/docs/release/log.md @@ -6,6 +6,12 @@ category: description: MapStructPlus release log --- +### 1.4.3 + +- feat: `ComponentModel` 增加 `spring-lazy` 可选项,懒加载 Spring Bean,解决互相依赖的问题,并将默认配置改为该选项; +- fix: 解决 `unmappedTargetPolicy` 默认配置不生效的问题; +- enhance: 优化 IDEA 本地开发构建效率,一定程度上缩短构建时间、减小元空间占用;[Issue #89](https://github.com/linpeilie/mapstruct-plus/issues/89) + ### 1.4.2 - feat: `AutoMapper` 注解增加 `mapperNameSuffix` 属性,支持配置生成的转换接口名称增加后缀,默认规则下生成的反向转换接口同时生效; diff --git a/example/pom.xml b/example/pom.xml index cb33ca75625f4802a157770ae189652b77d62049..d5d2a39ddb9ed74dc9b9dee85b0128836d616209 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -18,7 +18,7 @@ UTF-8 1.5.1.Final - 1.4.2 + 1.4.3 1.18.22 5.8.26 32.1.3-jre diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/AbstractAdapterMapperGenerator.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/AbstractAdapterMapperGenerator.java index c3e9e4abe320b0e95db8a86ff9b9d477d8e730c5..426b5c3b987415d841ee850c1e830df78f2e4bda 100644 --- a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/AbstractAdapterMapperGenerator.java +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/AbstractAdapterMapperGenerator.java @@ -12,6 +12,7 @@ import io.github.linpeilie.processor.metadata.AbstractAdapterMethodMetadata; import io.github.linpeilie.processor.metadata.AdapterMapMethodMetadata; import io.github.linpeilie.processor.metadata.AdapterMethodMetadata; import io.github.linpeilie.utils.ClassUtil; +import io.github.linpeilie.utils.CollectionUtils; import java.io.IOException; import java.io.Writer; import java.util.ArrayList; @@ -61,9 +62,6 @@ public abstract class AbstractAdapterMapperGenerator { .map(method -> buildProxyMethod(method, cycleAvoiding)) .flatMap(Collection::stream) .collect(Collectors.toList()); - if (methods.isEmpty()) { - return null; - } return createTypeSpec(methods, adapterClassName, cycleAvoiding ? ClassName.get(adapterPackage(), AutoMapperProperties.getAdapterClassName()) : null); } @@ -77,7 +75,9 @@ public abstract class AbstractAdapterMapperGenerator { } // adapter methods - adapterBuilder.addMethods(methods); + if (CollectionUtils.isNotEmpty(methods)) { + adapterBuilder.addMethods(methods); + } return adapterBuilder.build(); } diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/AdapterMapperGeneratorFactory.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/AdapterMapperGeneratorFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..30c4196b69281053f6bea90df633758c69fd8397 --- /dev/null +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/AdapterMapperGeneratorFactory.java @@ -0,0 +1,23 @@ +package io.github.linpeilie.processor; + +import io.github.linpeilie.ComponentModelConstant; +import io.github.linpeilie.processor.generator.DefaultAdapterMapperGenerator; +import io.github.linpeilie.processor.generator.SolonAdapterMapperGenerator; +import io.github.linpeilie.processor.generator.SpringAdapterMapperGenerator; +import org.mapstruct.MappingConstants; + +public class AdapterMapperGeneratorFactory { + + public static AbstractAdapterMapperGenerator instance(String componentModel) { + switch (AutoMapperProperties.getComponentModel()) { + case MappingConstants.ComponentModel.SPRING: + case ContextConstants.ComponentModelConfig.springLazy: + return new SpringAdapterMapperGenerator(); + case ComponentModelConstant.SOLON: + return new SolonAdapterMapperGenerator(); + default: + return new DefaultAdapterMapperGenerator(); + } + } + +} diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/AutoMapperProcessor.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/AutoMapperProcessor.java index 9d7150baf973134c3169dc687f9c951f83cdd5fe..a5d4c158bf136265dcfc804ee603d1c50a0db8e0 100644 --- a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/AutoMapperProcessor.java +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/AutoMapperProcessor.java @@ -1,6 +1,8 @@ package io.github.linpeilie.processor; +import com.squareup.javapoet.ArrayTypeName; import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import io.github.linpeilie.ComponentModelConstant; import io.github.linpeilie.annotations.AutoEnumMapper; @@ -117,6 +119,10 @@ public class AutoMapperProcessor extends AbstractProcessor { private final Set mapperSet = new HashSet<>(); + private static final Map AUTO_MAPPER_INDEX = new HashMap<>(); + + private final Map> typeRelationMappers = new HashMap<>(); + private Messager messager; public AutoMapperProcessor() { @@ -178,16 +184,7 @@ public class AutoMapperProcessor extends AbstractProcessor { refreshProperties(annotations, roundEnv); // 根据配置生成适配类生成器 - switch (AutoMapperProperties.getComponentModel()) { - case MappingConstants.ComponentModel.SPRING: - this.adapterMapperGenerator = new SpringAdapterMapperGenerator(); - break; - case ComponentModelConstant.SOLON: - this.adapterMapperGenerator = new SolonAdapterMapperGenerator(); - break; - default: - this.adapterMapperGenerator = new DefaultAdapterMapperGenerator(); - } + this.adapterMapperGenerator = AdapterMapperGeneratorFactory.instance(AutoMapperProperties.getComponentModel()); // AutoMapMapper final TypeElement autoMapMapperAnnotation = @@ -381,7 +378,7 @@ public class AutoMapperProcessor extends AbstractProcessor { } AutoMapperProperties.setUnmappedSourcePolicy(mapperConfigGem.unmappedSourcePolicy().getValue()); // 重定义 MapStruct 中 unmappedTargetPolicy 的默认值 WARN ---> IGNORE - AutoMapperProperties.setUnmappedTargetPolicy(mapperConfigGem.unmappedTargetPolicy().getValue()); + AutoMapperProperties.setUnmappedTargetPolicy(mapperConfigGem.unmappedTargetPolicy().get()); AutoMapperProperties.setTypeConversionPolicy(mapperConfigGem.typeConversionPolicy().getValue()); AutoMapperProperties.setCollectionMappingStrategy(mapperConfigGem.collectionMappingStrategy().getValue()); AutoMapperProperties.setNullValueMappingStrategy(mapperConfigGem.nullValueMappingStrategy().getValue()); @@ -507,7 +504,7 @@ public class AutoMapperProcessor extends AbstractProcessor { mapperList.addAll(autoMapperMetadata); } - private void generateMapper() { + private List generateReverseConverters() { List reverseMapperMetadataList = new ArrayList<>(); mapperList.forEach(autoMapperMetadata -> { @@ -524,19 +521,44 @@ public class AutoMapperProcessor extends AbstractProcessor { reverseMapperMetadataList.add(reverseMapperMetadata); }); - mapperList.addAll(reverseMapperMetadataList); + return reverseMapperMetadataList; + } + + private void typeRelationMapper(AutoMapperMetadata metadata) { + String source = metadata.getSourceClassName().reflectionName(); + if (!typeRelationMappers.containsKey(source)) { + typeRelationMappers.put(source, new ArrayList<>()); + } + typeRelationMappers.get(source).add(metadata.mapperClass()); + + String target = metadata.getTargetClassName().reflectionName(); + if (!typeRelationMappers.containsKey(target)) { + typeRelationMappers.put(target, new ArrayList<>()); + } + typeRelationMappers.get(target).add(metadata.mapperClass()); + } + + private void generateMapper() { + mapperList.addAll(generateReverseConverters()); + + mapperList.removeIf(metadata -> !metadata.isConvertGenerate()); mapperList.forEach(metadata -> { - if (!metadata.isConvertGenerate()) { - return; + // 兼容同模块下,同名不同包的场景 + this.mapperNameAddSuffix(metadata); + + if (metadata.isCycleAvoiding()) { + addAdapterMethod(metadata); + } else { + typeRelationMapper(metadata); } - this.writeAutoMapperClassFile(metadata); - addAdapterMethod(metadata); }); - if (methodMap.isEmpty()) { - return; - } + mapperList.forEach(metadata -> { + this.relationDependencies(metadata); + this.usesRemoveItself(metadata); + this.writeAutoMapperClassFile(metadata); + }); adapterMapperGenerator.write(processingEnv, methodMap.values(), @@ -563,6 +585,88 @@ public class AutoMapperProcessor extends AbstractProcessor { } } + private void mapperNameAddSuffix(AutoMapperMetadata metadata) { + String mapperName = metadata.mapperName(); + // 同名类时,增加后缀 + Integer index = AUTO_MAPPER_INDEX.getOrDefault(mapperName, 0); + if (index > 0) { + mapperName = mapperName + "__" + index; + } + AUTO_MAPPER_INDEX.put(metadata.mapperName(), ++index); + metadata.setMapperName(mapperName); + } + + private void relationDependencies(AutoMapperMetadata metadata) { + Set dependencies = metadata.getDependencies(); + if (CollectionUtils.isNotEmpty(dependencies)) { + List dependencyMappers = dependencies.stream().map(dependency -> + typeRelationMappers.get(dependency.toString()) + ).filter(Objects::nonNull).flatMap(Collection::stream).collect(Collectors.toList()); + + if (CollectionUtils.isNotEmpty(dependencyMappers)) { + metadata.addUseList(dependencyMappers); + } + } + // source + List sourceDependencies = + typeRelationMappers.get(metadata.getSourceClassName().reflectionName()); + + if (CollectionUtils.isNotEmpty(sourceDependencies)) { + metadata.addUseList(sourceDependencies); + } + } + + private void usesRemoveItself(AutoMapperMetadata metadata) { + // remove itself + if (CollectionUtils.isNotEmpty(metadata.getUsesClassNameList())) { + metadata.getUsesClassNameList() + .removeIf(use -> use.reflectionName().equals(metadata.mapperClass().reflectionName())); + } + } + + private Set listDependencies(TypeElement autoMapperEle) { + Set set = new HashSet<>(); + + if (!autoMapperEle.getKind().isClass() && !autoMapperEle.getKind().isInterface()) { + return set; + } + + for (Element ele : autoMapperEle.getEnclosedElements()) { + if (ele.getKind() != ElementKind.FIELD) { + continue; + } + TypeName typeName = ClassName.get(ele.asType()); + if (typeName instanceof ArrayTypeName) { + ArrayTypeName arrayTypeName = (ArrayTypeName) typeName; + typeName = arrayTypeName.componentType; + } else if (typeName instanceof ParameterizedTypeName) { + ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) typeName; + List typeArguments = parameterizedTypeName.typeArguments; + set.addAll(typeArguments); + continue; + } + set.add(typeName); + } + + // add super class dependencies + getSuperClass(autoMapperEle).ifPresent(superClass -> set.addAll(listDependencies(superClass))); + + set.removeIf(ele -> { + if (ele == null) { + return true; + } + try { + if (ele.box().isBoxedPrimitive()) { + return true; + } + } catch (Exception e) { + // ignore + } + return false; + }); + return set; + } + private AutoMapperMetadata reverseMapper(AutoMapperMetadata autoMapperMetadata) { AutoMapperMetadata reverseMapperMetadata = initAutoMapperMetadata(autoMapperMetadata.getTargetClassName(), @@ -761,6 +865,9 @@ public class AutoMapperProcessor extends AbstractProcessor { MapperUtils.getEnumMapperClassName(enumClass.simpleName()))) .collect(Collectors.toList()); + // dependencies + Set dependencies = listDependencies((TypeElement) ele); + usesClassNameList.addAll(useEnumClassNameList); metadata.setUsesClassNameList(usesClassNameList); @@ -790,6 +897,7 @@ public class AutoMapperProcessor extends AbstractProcessor { metadata.setMapperName(autoMapperGem.mapperName().getValue()); } metadata.setMapperNameSuffix(autoMapperGem.mapperNameSuffix().getValue()); + metadata.setDependencies(dependencies); addMapper(metadata, true); diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/AutoMapperProperties.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/AutoMapperProperties.java index ddb68fea9fd44b2e955e35415d884847d29af761..65e08046bc720c3caa0c15561e463f0ce9317aa7 100644 --- a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/AutoMapperProperties.java +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/AutoMapperProperties.java @@ -15,7 +15,7 @@ public class AutoMapperProperties { private static String unmappedSourcePolicy; - private static String unmappedTargetPolicy; + private static String unmappedTargetPolicy = "IGNORE"; private static String typeConversionPolicy; diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/ContextConstants.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/ContextConstants.java index 7344696cb4873b586c0d69d398ff7f02e0fba36e..14013b492cdd585881a3ece4be2660a64ee36490 100644 --- a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/ContextConstants.java +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/ContextConstants.java @@ -65,7 +65,8 @@ public interface ContextConstants { interface ComponentModelConfig { String qualifiedClassName = "io.github.linpeilie.annotations.ComponentModelConfig"; - String defaultComponentModel = MappingConstants.ComponentModel.SPRING; + String springLazy = "spring-lazy"; + String defaultComponentModel = springLazy; } interface ConvertAdapter { diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/enhance/model/SpringDelayInjectMapperReference.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/enhance/model/SpringDelayInjectMapperReference.java new file mode 100644 index 0000000000000000000000000000000000000000..a2b5d2dd9f54d237349b28f1d727d74d546dd2e9 --- /dev/null +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/enhance/model/SpringDelayInjectMapperReference.java @@ -0,0 +1,25 @@ +package io.github.linpeilie.processor.enhance.model; + +import io.github.linpeilie.processor.enhance.processor.SpringComponentProcessor; +import java.util.HashSet; +import java.util.Set; +import org.mapstruct.ap.internal.model.MapperReference; +import org.mapstruct.ap.internal.model.common.Type; + +public class SpringDelayInjectMapperReference extends MapperReference { + + private final Type springContextUtil; + + public SpringDelayInjectMapperReference(Type type, String variableName, boolean isUsed, Type springContextUtil) { + super(type, variableName, isUsed); + this.springContextUtil = springContextUtil; + } + + @Override + public Set getImportTypes() { + Set importTypes = new HashSet<>(); + importTypes.add(getType()); + importTypes.add(springContextUtil); + return importTypes; + } +} diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/solon/SolonComponentProcessor.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/enhance/processor/SolonComponentProcessor.java similarity index 95% rename from mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/solon/SolonComponentProcessor.java rename to mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/enhance/processor/SolonComponentProcessor.java index 4c6aa638c2b26b33ab21d21ba999125da6549255..1c4fd8ce421f2923edbdf75583342ddee720c333 100644 --- a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/solon/SolonComponentProcessor.java +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/enhance/processor/SolonComponentProcessor.java @@ -1,4 +1,4 @@ -package io.github.linpeilie.processor.solon; +package io.github.linpeilie.processor.enhance.processor; import io.github.linpeilie.ComponentModelConstant; import io.github.linpeilie.utils.CollectionUtils; diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/enhance/processor/SpringComponentProcessor.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/enhance/processor/SpringComponentProcessor.java new file mode 100644 index 0000000000000000000000000000000000000000..8d74245953b18f11303039b526b9b95ddb5db5f3 --- /dev/null +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/enhance/processor/SpringComponentProcessor.java @@ -0,0 +1,48 @@ +package io.github.linpeilie.processor.enhance.processor; + +import io.github.linpeilie.processor.ContextConstants; +import io.github.linpeilie.processor.enhance.model.SpringDelayInjectMapperReference; +import io.github.linpeilie.utils.CollectionUtils; +import java.util.Collections; +import java.util.List; +import org.mapstruct.ap.internal.gem.InjectionStrategyGem; +import org.mapstruct.ap.internal.model.Annotation; +import org.mapstruct.ap.internal.model.Field; +import org.mapstruct.ap.internal.model.Mapper; +import org.mapstruct.ap.internal.processor.AnnotationBasedComponentModelProcessor; + +public class SpringComponentProcessor extends AnnotationBasedComponentModelProcessor { + + private Annotation component() { + return new Annotation(getTypeFactory().getType("org.springframework.stereotype.Component")); + } + + @Override + protected String getComponentModelIdentifier() { + return ContextConstants.ComponentModelConfig.springLazy; + } + + @Override + protected List getTypeAnnotations(Mapper mapper) { + return CollectionUtils.newArrayList(component()); + } + + @Override + protected List getMapperReferenceAnnotations() { + return Collections.emptyList(); + } + + @Override + protected boolean requiresGenerationOfDecoratorClass() { + return true; + } + + @Override + protected Field replacementMapperReference(Field originalReference, + List annotations, + InjectionStrategyGem injectionStrategy) { + return new SpringDelayInjectMapperReference(originalReference.getType(), originalReference.getVariableName(), + originalReference.isUsed(), + getTypeFactory().getType("io.github.linpeilie.mapstruct.SpringContextUtils")); + } +} diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/AutoMapperGenerator.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/AutoMapperGenerator.java index 19deb351b0aa4a44b5235945771195f08c90a454..f53bb4841c4a4ed09dc6a4b59d75759f5bd80312 100644 --- a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/AutoMapperGenerator.java +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/AutoMapperGenerator.java @@ -7,12 +7,12 @@ import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import io.github.linpeilie.processor.ContextConstants; import io.github.linpeilie.processor.metadata.AutoMapperMetadata; import io.github.linpeilie.processor.metadata.AutoMappingMetadata; import io.github.linpeilie.utils.CollectionUtils; -import java.io.IOException; import java.io.Writer; import java.util.ArrayList; import java.util.Collections; @@ -32,22 +32,9 @@ public class AutoMapperGenerator { public static final String CONVERT_METHOD_NAME = "convert"; - private static final Map AUTO_MAPPER_INDEX = new HashMap<>(); - public void write(AutoMapperMetadata metadata, ProcessingEnvironment processingEnv) { String mapperPackage = metadata.mapperPackage(); - - /* - 当前处理方式,本地使用 IDEA 开发时,当修改 Source/Target 类时,可能还会出现类名冲突的问题, - 当出现该问题时,需要执行 clean 把之前构建的类清掉。 - */ String mapperName = metadata.mapperName(); - // 同名类时,增加后缀 - Integer index = AUTO_MAPPER_INDEX.getOrDefault(mapperName, 0); - if (index > 0) { - mapperName = mapperName + "__" + index; - } - AUTO_MAPPER_INDEX.put(metadata.mapperName(), ++index); try (final Writer writer = processingEnv.getFiler() .createSourceFile(mapperPackage + "." + mapperName) @@ -55,7 +42,7 @@ public class AutoMapperGenerator { JavaFile.builder(metadata.mapperPackage(), createTypeSpec(processingEnv, metadata, mapperName)) .build() .writeTo(writer); - } catch (IOException e) { + } catch (Exception e) { processingEnv.getMessager() .printMessage(ERROR, "Error while opening " + metadata.mapperName() + " output file: " + e.getMessage()); @@ -79,19 +66,69 @@ public class AutoMapperGenerator { ParameterSpec target = ParameterSpec.builder(targetClassName, "target") .addAnnotation(AnnotationSpec.builder(ClassName.get("org.mapstruct", "MappingTarget")).build()) .build(); + ParameterSpec sourceList = ParameterSpec.builder( + ParameterizedTypeName.get( + ClassName.get("java.util", "List"), + metadata.getSourceClassName() + ), "sourceList").build(); ParameterSpec context = ParameterSpec.builder(ClassName.get("io.github.linpeilie", "CycleAvoidingMappingContext"), "context") .addAnnotation(ClassName.get("org.mapstruct", "Context")) .build(); - if (metadata.getFieldMappingList() != null && !metadata.getFieldMappingList().isEmpty()) { + ParameterizedTypeName targetList = ParameterizedTypeName.get( + ClassName.get("java.util", "List"), + targetClassName + ); + + // 如果需要避免循环依赖,则把 BaseMapper 中的实现,全部添加 DoIgnore 防止使用该方法进行转换 + if (metadata.isCycleAvoiding()) { + // convert(source) + builder.addMethod( + addCallSuperConvertMethodSpec( + metadata.getSuperClass(), + CollectionUtils.newArrayList(source), + targetClassName, + CONVERT_METHOD_NAME) + ); + // convert(source, target) + builder.addMethod( + addCallSuperConvertMethodSpec( + metadata.getSuperClass(), CollectionUtils.newArrayList(source, target), + targetClassName, + CONVERT_METHOD_NAME + ) + ); + // convert(sourceList) + builder.addMethod( + addCallSuperConvertMethodSpec( + metadata.getSuperClass(), CollectionUtils.newArrayList(sourceList), + targetList, + CONVERT_METHOD_NAME + ) + ); + // convert(sourceList, context) + builder.addMethod( + addCallSuperConvertMethodSpec( + metadata.getSuperClass(), CollectionUtils.newArrayList(sourceList, context), + targetList, + CONVERT_METHOD_NAME + ) + ); + } + + // convert(source) | convert(source, context) + if (CollectionUtils.isNotEmpty(metadata.getFieldMappingList()) || metadata.isCycleAvoiding()) { builder.addMethod(addConvertMethodSpec( - metadata.isCycleAvoiding() ? CollectionUtils.newArrayList(source, context) : Collections.singletonList( - source), + metadata.isCycleAvoiding() + ? CollectionUtils.newArrayList(source, context) + : Collections.singletonList(source), metadata.getFieldMappingList(), targetClassName, - CONVERT_METHOD_NAME)); + CONVERT_METHOD_NAME, + metadata.isCycleAvoiding())); } + // convert(source, target) boolean targetIsImmutable = classIsImmutable(processingEnv, targetClassName); if (targetIsImmutable) { builder.addMethod( @@ -100,13 +137,15 @@ public class AutoMapperGenerator { context) : CollectionUtils.newArrayList(source, target), targetClassName, CONVERT_METHOD_NAME)); - } else if (metadata.getFieldMappingList() != null && !metadata.getFieldMappingList().isEmpty()) { + } else if (CollectionUtils.isNotEmpty(metadata.getFieldMappingList()) || metadata.isCycleAvoiding()) { builder.addMethod(addConvertMethodSpec( - metadata.isCycleAvoiding() ? CollectionUtils.newArrayList(source, target, - context) : CollectionUtils.newArrayList(source, target), + metadata.isCycleAvoiding() + ? CollectionUtils.newArrayList(source, target, context) + : CollectionUtils.newArrayList(source, target), metadata.getFieldMappingList(), targetClassName, - CONVERT_METHOD_NAME)); + CONVERT_METHOD_NAME, + metadata.isCycleAvoiding())); } return builder.build(); @@ -137,15 +176,50 @@ public class AutoMapperGenerator { private MethodSpec addConvertMethodSpec(List parameterSpecs, List autoMappingMetadataList, - ClassName target, String methodName) { + ClassName target, + String methodName, + boolean cycleAvoiding) { final MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder(methodName) .addParameters(parameterSpecs) .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) - .addAnnotation(ClassName.get(ContextConstants.DoIgnore.packageName, ContextConstants.DoIgnore.className)) .returns(target); if (CollectionUtils.isNotEmpty(autoMappingMetadataList)) { methodSpecBuilder.addAnnotations(buildMappingAnnotations(autoMappingMetadataList)); } + if (cycleAvoiding) { + methodSpecBuilder.addAnnotation( + ClassName.get(ContextConstants.DoIgnore.packageName, ContextConstants.DoIgnore.className)); + } + return methodSpecBuilder.build(); + } + + private ClassName doIgnore() { + return ClassName.get(ContextConstants.DoIgnore.packageName, ContextConstants.DoIgnore.className); + } + + private MethodSpec addCallSuperConvertMethodSpec(ClassName superClass, + List parameterSpecs, + TypeName target, + String methodName) { + MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder(methodName) + .addParameters(parameterSpecs) + .addModifiers(Modifier.DEFAULT, Modifier.PUBLIC) + .addAnnotation(doIgnore()) + .returns(target); + + // return super.convert( *** ); + CodeBlock.Builder codeBlock = CodeBlock.builder(); + codeBlock.add("return $T.super.$L(", superClass, methodName); + for (int i = 0; i < parameterSpecs.size(); i++) { + codeBlock.add("$N", parameterSpecs.get(i)); + if (i != parameterSpecs.size() -1) { + codeBlock.add(","); + } + } + codeBlock.add(");\n"); + + methodSpecBuilder.addCode(codeBlock.build()); + return methodSpecBuilder.build(); } diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/IocAdapterMapperGenerator.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/IocAdapterMapperGenerator.java index e6d49303a7a28fdd793d81f5552aa258b45f0a1b..540edbdff2efa24025122a8a7e1cc3ef49b0f5f3 100644 --- a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/IocAdapterMapperGenerator.java +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/generator/IocAdapterMapperGenerator.java @@ -8,6 +8,7 @@ import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeSpec; import io.github.linpeilie.processor.AbstractAdapterMapperGenerator; import io.github.linpeilie.processor.metadata.AbstractAdapterMethodMetadata; +import io.github.linpeilie.utils.CollectionUtils; import java.util.List; import javax.lang.model.element.Modifier; @@ -27,7 +28,9 @@ public abstract class IocAdapterMapperGenerator extends AbstractAdapterMapperGen adapterBuilder.addField(buildConverterField()); - adapterBuilder.addMethods(methods); + if (CollectionUtils.isNotEmpty(methods)) { + adapterBuilder.addMethods(methods); + } if (superClass != null) { adapterBuilder.superclass(superClass); diff --git a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/metadata/AutoMapperMetadata.java b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/metadata/AutoMapperMetadata.java index 34c65325bf9714e2fc3f3b7cae46992ce1cba6a5..e8b248eec98ddb29d48b6bed83d8ae0151d5cab7 100644 --- a/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/metadata/AutoMapperMetadata.java +++ b/mapstruct-plus-processor/src/main/java/io/github/linpeilie/processor/metadata/AutoMapperMetadata.java @@ -1,9 +1,12 @@ package io.github.linpeilie.processor.metadata; import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.TypeName; import io.github.linpeilie.processor.utils.MapperUtils; import io.github.linpeilie.utils.StrUtil; +import java.util.ArrayList; import java.util.List; +import java.util.Set; import org.mapstruct.NullValueMappingStrategy; import org.mapstruct.SubclassExhaustiveStrategy; @@ -65,10 +68,19 @@ public class AutoMapperMetadata extends AbstractMapperMetadata { private ClassName mappingControl; + private Set dependencies; + public String qualifiedMapperName() { return mapperPackage() + "." + mapperName(); } + public boolean addUseList(List uses) { + if (this.usesClassNameList == null) { + this.usesClassNameList = new ArrayList<>(); + } + return usesClassNameList.addAll(uses); + } + /*************** getter/setter ***************/ public String mapperName() { @@ -79,10 +91,6 @@ public class AutoMapperMetadata extends AbstractMapperMetadata { this.mapperName = mapperName; } - public String getMapperName() { - return mapperName; - } - public String getMapperNameSuffix() { return mapperNameSuffix; } @@ -277,4 +285,12 @@ public class AutoMapperMetadata extends AbstractMapperMetadata { public void setMappingControl(ClassName mappingControl) { this.mappingControl = mappingControl; } + + public Set getDependencies() { + return dependencies; + } + + public void setDependencies(Set dependencies) { + this.dependencies = dependencies; + } } diff --git a/mapstruct-plus-processor/src/main/resources/META-INF/services/org.mapstruct.ap.internal.processor.ModelElementProcessor b/mapstruct-plus-processor/src/main/resources/META-INF/services/org.mapstruct.ap.internal.processor.ModelElementProcessor index a1449122d9e2a15da94620a0d548753d1714e5e6..42fded8ff2610f654e118729f79eddbfde81f8dc 100644 --- a/mapstruct-plus-processor/src/main/resources/META-INF/services/org.mapstruct.ap.internal.processor.ModelElementProcessor +++ b/mapstruct-plus-processor/src/main/resources/META-INF/services/org.mapstruct.ap.internal.processor.ModelElementProcessor @@ -11,4 +11,5 @@ org.mapstruct.ap.internal.processor.MapperRenderingProcessor org.mapstruct.ap.internal.processor.MethodRetrievalProcessor org.mapstruct.ap.internal.processor.SpringComponentProcessor org.mapstruct.ap.internal.processor.MapperServiceProcessor -io.github.linpeilie.processor.solon.SolonComponentProcessor +io.github.linpeilie.processor.enhance.processor.SolonComponentProcessor +io.github.linpeilie.processor.enhance.processor.SpringComponentProcessor diff --git a/mapstruct-plus-processor/src/main/resources/io/github/linpeilie/processor/enhance/model/SpringDelayInjectMapperReference.ftl b/mapstruct-plus-processor/src/main/resources/io/github/linpeilie/processor/enhance/model/SpringDelayInjectMapperReference.ftl new file mode 100644 index 0000000000000000000000000000000000000000..f84aa7d2ad91099d56f00778e58daf4730942ead --- /dev/null +++ b/mapstruct-plus-processor/src/main/resources/io/github/linpeilie/processor/enhance/model/SpringDelayInjectMapperReference.ftl @@ -0,0 +1,2 @@ +<#-- @ftlvariable name="" type="io.github.linpeilie.processor.enhance.model.SpringDelayInjectMapperReference" --> +private <@includeModel object=type/> ${variableName} = SpringContextUtils.getBean("${variableName}", <@includeModel object=type/>.class); \ No newline at end of file diff --git a/mapstruct-plus-spring-boot-starter/src/main/java/io/github/linpeilie/mapstruct/MapstructAutoConfiguration.java b/mapstruct-plus-spring-boot-starter/src/main/java/io/github/linpeilie/mapstruct/MapstructAutoConfiguration.java index 23743f506990930621e8c65b5afd9964dbed1d49..28c7a06cb5ac248ac50546dfba2c0dae77962831 100644 --- a/mapstruct-plus-spring-boot-starter/src/main/java/io/github/linpeilie/mapstruct/MapstructAutoConfiguration.java +++ b/mapstruct-plus-spring-boot-starter/src/main/java/io/github/linpeilie/mapstruct/MapstructAutoConfiguration.java @@ -24,4 +24,9 @@ public class MapstructAutoConfiguration { return new Converter(converterFactory); } + @Bean + public SpringContextUtils springContextUtils() { + return new SpringContextUtils(); + } + } diff --git a/mapstruct-plus-spring-boot-starter/src/main/java/io/github/linpeilie/mapstruct/SpringContextUtils.java b/mapstruct-plus-spring-boot-starter/src/main/java/io/github/linpeilie/mapstruct/SpringContextUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..bdb201aeaa34766a0da493687575cd60026c0b6b --- /dev/null +++ b/mapstruct-plus-spring-boot-starter/src/main/java/io/github/linpeilie/mapstruct/SpringContextUtils.java @@ -0,0 +1,68 @@ +package io.github.linpeilie.mapstruct; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.NoUniqueBeanDefinitionException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ConfigurableApplicationContext; + +public class SpringContextUtils implements BeanFactoryPostProcessor, ApplicationContextAware { + + private static ConfigurableListableBeanFactory beanFactory; + private static ApplicationContext applicationContext; + + public SpringContextUtils() { + } + + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + SpringContextUtils.beanFactory = beanFactory; + } + + public void setApplicationContext(ApplicationContext applicationContext) { + SpringContextUtils.applicationContext = applicationContext; + } + + public static ApplicationContext getApplicationContext() { + return applicationContext; + } + + public static ListableBeanFactory getBeanFactory() { + ListableBeanFactory factory = null == beanFactory ? applicationContext : beanFactory; + if (null == factory) { + throw new RuntimeException("No ConfigurableListableBeanFactory or ApplicationContext injected, maybe not in the Spring environment?"); + } else { + return (ListableBeanFactory)factory; + } + } + + public static ConfigurableListableBeanFactory getConfigurableBeanFactory() { + ConfigurableListableBeanFactory factory; + if (null != beanFactory) { + factory = beanFactory; + } else { + if (!(applicationContext instanceof ConfigurableApplicationContext)) { + throw new RuntimeException("No ConfigurableListableBeanFactory from context!"); + } + + factory = ((ConfigurableApplicationContext)applicationContext).getBeanFactory(); + } + + return factory; + } + + public static T getBean(Class clazz) { + return getBeanFactory().getBean(clazz); + } + + public static T getBean(String name, Class clazz) { + try { + return getBean(clazz); + } catch (NoUniqueBeanDefinitionException e) { + return getBeanFactory().getBean(name, clazz); + } + } + +} diff --git a/mapstruct-plus/src/main/java/io/github/linpeilie/BaseCycleAvoidingMapper.java b/mapstruct-plus/src/main/java/io/github/linpeilie/BaseCycleAvoidingMapper.java index 75ba1f62f14766d05ae0e1ac7546fbeed46f1966..48c7773a946001b55fcc91e0bddbdac195bf034e 100644 --- a/mapstruct-plus/src/main/java/io/github/linpeilie/BaseCycleAvoidingMapper.java +++ b/mapstruct-plus/src/main/java/io/github/linpeilie/BaseCycleAvoidingMapper.java @@ -1,6 +1,5 @@ package io.github.linpeilie; -import io.github.linpeilie.annotations.DoIgnore; import java.util.List; import java.util.stream.Collectors; import org.mapstruct.Context; @@ -8,13 +7,10 @@ import org.mapstruct.MappingTarget; public interface BaseCycleAvoidingMapper extends BaseMapper { - @DoIgnore T convert(S source, @Context CycleAvoidingMappingContext context); - @DoIgnore T convert(S source, @MappingTarget T target, @Context CycleAvoidingMappingContext context); - @DoIgnore default List convert(List sourceList, @Context CycleAvoidingMappingContext context) { return sourceList.stream() .map(item -> convert(item, context)) @@ -22,20 +18,17 @@ public interface BaseCycleAvoidingMapper extends BaseMapper { } @Override - @DoIgnore default T convert(S source) { return convert(source, new CycleAvoidingMappingContext()); } @Override - @DoIgnore default T convert(S source, @MappingTarget T target) { return convert(source, new CycleAvoidingMappingContext()); } @Override - @DoIgnore default List convert(List sourceList) { return convert(sourceList, new CycleAvoidingMappingContext()); } diff --git a/mapstruct-plus/src/main/java/io/github/linpeilie/BaseMapper.java b/mapstruct-plus/src/main/java/io/github/linpeilie/BaseMapper.java index a89fa3e36f6082ecc9144e54022a6c0cc9a20c7c..eec5536c2b627fa7d60ef2d13485c21abd8271a8 100644 --- a/mapstruct-plus/src/main/java/io/github/linpeilie/BaseMapper.java +++ b/mapstruct-plus/src/main/java/io/github/linpeilie/BaseMapper.java @@ -1,6 +1,5 @@ package io.github.linpeilie; -import io.github.linpeilie.annotations.DoIgnore; import io.github.linpeilie.utils.CollectionUtils; import java.util.ArrayList; import java.util.List; @@ -9,13 +8,10 @@ import org.mapstruct.MappingTarget; public interface BaseMapper { - @DoIgnore T convert(S source); - @DoIgnore T convert(S source, @MappingTarget T target); - @DoIgnore default List convert(List sourceList) { if (CollectionUtils.isEmpty(sourceList)) { return new ArrayList<>(); diff --git a/mapstruct-plus/src/main/java/io/github/linpeilie/annotations/ComponentModelConfig.java b/mapstruct-plus/src/main/java/io/github/linpeilie/annotations/ComponentModelConfig.java index d63c97c8a0bc99db9c9232d76a9b1388bc4a1dc0..4e64a3e728e6885646231473c5d4adcd3665857c 100644 --- a/mapstruct-plus/src/main/java/io/github/linpeilie/annotations/ComponentModelConfig.java +++ b/mapstruct-plus/src/main/java/io/github/linpeilie/annotations/ComponentModelConfig.java @@ -9,6 +9,6 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.SOURCE) public @interface ComponentModelConfig { - String componentModel() default "spring"; + String componentModel() default "spring-lazy"; } diff --git a/pom.xml b/pom.xml index 37086e05a3f07998de26935e4d317e72ddb65799..f155c129bb9adcdf994166721473ef165a492d66 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ - 1.4.2 + 1.4.3 8 8 UTF-8