MapStruct⽣成实现类对象的Spring容器对象属性注⼊问题源码分析
本⽂解析MapStruct⽣成继承类的Spring容器对象属性注⼊为空问题,并分析了相关源码。给出了⼀个Spring容器对象属性正确注⼊例⼦。
在领域模型中经常会遇到对象属性的拷贝,对属性的⼿动赋值会增加不必要的⼯作量,⽽使⽤pyProperties等⼯具存在其他问题。除了领域模型,⼀般MVC项⽬也会涉及对象属性的复制。org.mapstruct包能完美解决对象的复制,使⽤上简洁且功能强⼤,在项⽬中使⽤越来越频繁。
org.mapstruct在⽣成继承类时,含Spring容器对象的属性
本⽂例⼦采⽤了web项⽬,在l中添加依赖如下:
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>1.3.1.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.3.1.Final</version>
</dependency>
直接看代码和运⾏结果
⼀、代码
1、被转换对象
package p.mapstruct;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 测试dto
*/
@Data
@NoArgsConstructor
public class TestConvertorDTO {
private String date;
private Integer number;
private Integer version;
public TestConvertorDTO(String date, Integer number, Integer version) {
this.date = date;
this.number = number;
this.version = version;
}
}
2、转换后对象
package p.mapstruct;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import t.annotation.Scope;
import org.springframework.stereotype.Component;
/
**
* 领域
* ⾮单例的prototype
*
* @author Doflamingo
*/
@Scope("prototype")
@Data
@Component
public class TestConvertorE {
private String date;
private Integer number;
private Integer version;
// Spring bean
/**
* 本⽂主要测试该属性是否会被注⼊Spring容器对象
*/
@Autowired
private TestMapStructService structService;
// 领域⽅法
/**
* 校验 structService 对象是否注⼊
*/
public void testNull() {
if (null == structService) {
System.out.println("本例中 Test1Convertor 会打印本⾏...");
} else {
structService.print();
}
}
}
3、mapstruct转换接⼝Test1Convertor
@Mapper注解的属性componentModel有⼏种取值,本⽂取值为“spring”表⽰转换⽣成的对象会被Spring容器所管理。这⾥会采⽤默认⽅式转换⽣成的对象,如new 0bject()。
package p.mapstruct;
import org.mapstruct.Mapper;
/**
* mapstruct 未指定对象⽣成的Factory
*
* @author Doflamingo
*/
@Mapper(componentModel = "spring")
public interface Test1Convertor {
/**
* 转领域对象,不会注⼊相关Spring容器对象
*
* @param orderDO
* @return
*/
TestConvertorE doToEntity(TestConvertorDTO orderDO);
}
4、mapstruct转换接⼝Test2Convertor
这⾥的@Mapper注解属性⽐Test2Convertor多了uses的取值,uses主要⽤来指定⽬标对象的⽣成⼯⼚,如转换后的⽬标对象将被EntityObjFactory⽣成。
package p.mapstruct;
import org.mapstruct.Mapper;
/**
* mapstruct 带Factory
* EntityObjFactory类⽤于⽣成⽬标对象
*
* @author Doflamingo
*/
@Mapper(componentModel = "spring", uses = EntityObjFactory.class)
public interface Test2Convertor {
/**
* 转领域对象,会注⼊相关Spring容器对象
*
* @param orderDO
* @return
*/
TestConvertorE doToEntity(TestConvertorDTO orderDO);
}
5、TestMapStructService测试属性是否会被注⼊Spring容器对象
package p.mapstruct;
slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* mapstruct测试属性是否会被注⼊Spring容器对象
*
* @author Doflamingo
*/
@Slf4j
@Service
public class TestMapStructService {
@Autowired
private Test1Convertor test1Convertor;
@Autowired
private Test2Convertor test2Convertor;
/**
* 测试⽅法
*/
public void print() {
System.out.println("本例中 Test2Convertor 对象会打印本⾏...");
}
/**
* 测试⽅法
*/
public void test() {
TestConvertorDTO dto = new TestConvertorDTO("20200818", 1, 22);
// 不会向属性注⼊Spring容器对象
TestConvertorE e1 = test1Convertor.doToEntity(dto);
// 会向属性注⼊Spring容器对象
TestConvertorE e2 = test2Convertor.doToEntity(dto);
}
}
6、EntityObjFactory,mapstruct使⽤该factory⽣成⽬标bean
EntityObjFactory实现了ApplicationContextAware接⼝,⽤于获取Spring容器对象,这样mapstruct就可以借助Spring容器对象⽣成⽬标bean,最终再填⼊属性。含容器注⼊的属性将被正确注⼊。
package p.mapstruct;
import org.mapstruct.TargetType;
import t.ApplicationContext;
import t.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* mapstruct 使⽤该factory
* ⽤于获取Spring容器管理的Bean
*
* @author Doflamingo
*/
@Component
public class EntityObjFactory implements ApplicationContextAware {
private ApplicationContext applicationContext = null;
@Override
public void setApplicationContext(ApplicationContext context) {
if (null == this.applicationContext) {
this.applicationContext = context;
}
}
/**
* 获取Spring容器管理的Bean
*
* @param clazz
* @param <T>
* @return
*/
public <T> T createEntity(@TargetType Class<T> clazz) {
Bean(clazz);
}
}
7、测试类MapStructTest
package p.mapstruct;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.st.context.SpringBootTest;
import st.context.junit4.SpringRunner;
@SpringBootTest
@RunWith(SpringRunner.class)
public class MapStructTest {
@Autowired
private TestMapStructService structService;
@Test
public void test() {
}
}
测试类MapStructTest执⾏结果
可以看出@Mapper注解未指定uses属性时,⽣成对象的属性不会被注⼊Spring容器对象。指定为EntityObjFactory即会正常注⼊。
⼆、mapstruct相关源码分析
mapstruct原理是在编译时通过注解处理器解析,扫描@Mapper注解和被注解的接⼝,按照指定属性参数完成被注解的接⼝的实现类⽣成。实现类的⽅法将完成对象属性间的拷贝。
1、先看编译时⽣成的实现类
Test1Convertor实现类:
Test2Convertor实现类:
2、mapstruct注解助理类org.mapstruct.ap.MappingProcessor
MappingProcessor继承了javax.annotation.processing.AbstractProcessor类,AbstractProcessor的⽅法,这⾥不做说明,有兴趣可去查看相关⽂档。这⾥我只关注public boolean process(final Set annotations, final RoundEnvironment roundEnvironment)。
进⼊MappingProcessor.process(...)⽅法
获取被@Mapper注解的接⼝信息具体是在Mappers(...)⽅法中这⾏代码体现
Set<? extends Element> annotatedMappers = ElementsAnnotatedWith( annotation );
进⼊MappingProcessor.processMapperElements(...)⽅法,主要逻辑都在该⽅法中进⾏
中途会进⼊org.mapstruct.ap.internal.ieveMethods(...)⽅法,该⽅法会决定⽣成实现类的含有的属性或⽅法
先看Test1Convertor接⼝实现类的处理
springframework和springboot
先看Test2Convertor接⼝实现类的处理
uses处理逻辑
// org.mapstruct.ap.internal.ieveMethods(...)
// 这短短代码对@Mapper注解的uses属性进⾏了处理
if ( usedMapper.equals( mapperToImplement ) ) {
// 本例中uses只设置了⼀个值 mapperConfig.uses()的值即是p.mapstruct.EntityObjFactory的mapper描述对象
for ( DeclaredType mapper : mapperConfig.uses() ) {
// 这⾥递归处理了p.mapstruct.EntityObjFactory的mapper描述对象
// 会把EntityObjFactory类⾥的声明的⽅法加⼊到methods⾥:setApplicationContext、createEntity⽅法
methods.addAll( retrieveMethods(
asTypeElement( mapper ),
mapperToImplement,
mapperConfig,
prototypeMethods ) );
}
}
3、Test2Convertor接⼝实现类⽣成
前⽂说⽣成实现类核⼼逻辑是在MappingProcessor.processMapperElements(...)⽅法中处理,现在看下其中调⽤的⽅法MappingProcessor.processMapperTypeElement(...)
会循环按顺序处理getProcessors()返回的List,org.mapstruct.ap.internal.ModelElementProcessor的实现类如下
第⼀个遍历值MethodRetrievalProcessor上⾯我们已经说过,处理Test2Convertor时⽣成了三个⽅法描述(model = process( context, processor, mapperTypeElement, model );),model值将作为下⼀个遍历值得输⼊参数。由于@Mapper注解的属性componentModel = "spring",所以Cdi(第三)和Jsr(第四)开头的Processor将会被跳过。
3.1、第⼆个遍历值org.mapstruct.ap.internal.processor.MapperCreationProcessor
这也是为什么⽣成的Test2Convertor实现类会多⼀个EntityObjFactory成员变量。这⾥不包含实现类上的Spring相关注解。
3.2、第五个遍历值org.mapstruct.ap.internal.processor.SpringComponentProcessor
org.mapstruct.ap.internal.processor.AnnotationBasedComponentModelProcessor.process(ProcessorContext context, TypeElement mapperTypeElement, Mapper mapper)源码如下
@Override
public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, Mapper mapper) {
MapperConfiguration mapperConfiguration = InstanceOn( mapperTypeElement );
String componentModel = mapperConfigurationponentModel( Options() );
InjectionStrategyPrism injectionStrategy = InjectionStrategy();
// componentModel不等于"spring"时会直接返回
if ( !getComponentModelIdentifier().equalsIgnoreCase( componentModel ) ) {
return mapper;
}
// ⽤于实现类上的Spring注解,这⾥是定值@Component
for ( Annotation typeAnnotation : getTypeAnnotations( mapper ) ) {
mapper.addAnnotation( typeAnnotation );
}
if ( !requiresGenerationOfDecoratorClass() ) {
}
else if ( Decorator() != null ) {
adjustDecorator( mapper, injectionStrategy );
}
// ⽤于成员属性的Spring注解,这⾥是@Autowired
List<Annotation> annotations = getMapperReferenceAnnotations();
ListIterator<Field> iterator = Fields().listIterator();
// ⽤新⽣成且含Spring注解的Field原来的替换Field
while ( iterator.hasNext() ) {
Field reference = ();
if ( reference instanceof  MapperReference ) {
iterator.add( replacementMapperReference( reference, annotations, injectionStrategy ) );
}
}

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。