比 BeanUtils 更快的实体映射工具 MapStruct 原理


💓 BeanUtils类


BeanUtils 是 org.springframework.beans 包下的工具类,将给定源bean的属性值复制到目标bean中,源类和目标类不必匹配,甚至不必派生只要属性匹配就可以进行复制。任何bean属性源bean公开,但目标bean不公开,将被默认忽略。


💓BeanUtils测试


测试代码

public static void main(String[] args) {
        User user = new User();
        UserDto userDto = new UserDto();
        user.setNickName("zcct");
        user.setUsername("测试");
        BeanUtils.copyProperties(user,userDto);
        System.out.println(userDto);
    }

打印结果
在这里插入图片描述


💓BeanUtils原理解析


简单来说 BeanUtils 其实就是利用反射获取bean的所有属性,循环通过setter方法 向target设置属性,他的代码实现也比较简单,如下所示

private static void copyProperties(Object source, Object target, @Nullable Class<?> editable,
			@Nullable String... ignoreProperties) throws BeansException {

		Assert.notNull(source, "Source must not be null");
		Assert.notNull(target, "Target must not be null");

		Class<?> actualEditable = target.getClass();
		if (editable != null) {
			if (!editable.isInstance(target)) {
				throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
						"] not assignable to Editable class [" + editable.getName() + "]");
			}
			actualEditable = editable;
		}
		// 获取 target 属性
		PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
		List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
		
		// 循环设置属性
		for (PropertyDescriptor targetPd : targetPds) {
			// 获取setter方法 
			Method writeMethod = targetPd.getWriteMethod();
			if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
				// 获取 soure 属性
				PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
				if (sourcePd != null) {
				    // 获取get方法
					Method readMethod = sourcePd.getReadMethod();
					if (readMethod != null &&
							ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
						try {
							if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
								readMethod.setAccessible(true);
							}
							Object value = readMethod.invoke(source);
							if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
								writeMethod.setAccessible(true);
							}
							// 反射设置属性
							writeMethod.invoke(target, value);
						}
						catch (Throwable ex) {
							throw new FatalBeanException(
									"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
						}
					}
				}
			}
		}
	}

💓 mapstruct

在这里插入图片描述

官网地址

MapStruct 是一个代码生成器,它基于约定而不是配置方法,极大地简化了 Java Bean 类型之间映射的实现。生成的映射代码使用纯方法调用,因此快速、类型安全且易于理解。

💓Bmapstruct测试

1. 导入 mapstruct 依赖

<!-- https://mvnrepository.com/artifact/org.mapstruct/mapstruct -->
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>1.5.2.Final</version>
        </dependency>

2. 增加 mapper 映射器接口

注意这里的 @Mapper 注解 是 org.mapstruct包下的注解列

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

@Mapper
public interface UserCopeMapper {

    UserCopeMapper INSTANCE = Mappers.getMapper( UserCopeMapper.class ); 

    @Mapping(source = "username", target = "username")
    @Mapping(source = "nickName", target = "nickName")
    UserDto userToUserDto(User user);

}

3. 测试使用

	User user = new User();
        user.setNickName("zcct");
        user.setUsername("测试");
        UserDto userDto = UserCopeMapper.INSTANCE.userToUserDto(user);
        System.out.println(userDto);
    }

打印结果
在这里插入图片描述

💓Mapstruct原理

在debug 中可以看到 这里获取的实例时 UserCpoerMapper 的实现类
在这里插入图片描述
这个实例是通过 Mappers.getMapper( UserCopeMapper.class ); 获取的

1. Mappers.getMapper方法

在这个方法中 获取一个 Mapper实例

首先获取所有类加载器,遍历类加载器通过doGetMapper生成 mapper对象

 private static <T> T getMapper(Class<T> mapperType, Iterable<ClassLoader> classLoaders) throws ClassNotFoundException, NoSuchMethodException {
        Iterator var2 = classLoaders.iterator();

        Object mapper;
        do {
            if (!var2.hasNext()) {
                throw new ClassNotFoundException("Cannot find implementation for " + mapperType.getName());
            }

            ClassLoader classLoader = (ClassLoader)var2.next();
            mapper = doGetMapper(mapperType, classLoader);
        } while(mapper == null);

        return mapper;
    }

doGetMapper

doGetMapper 加载一个 名字为 mapperName + “Impl” 的类 , 从这里并不能看出 impl 字类时怎样生成的,

private static <T> T doGetMapper(Class<T> clazz, ClassLoader classLoader) throws NoSuchMethodException {
        try {
            @SuppressWarnings( "unchecked" )
            Class<T> implementation = (Class<T>) classLoader.loadClass( clazz.getName() + IMPLEMENTATION_SUFFIX );
            Constructor<T> constructor = implementation.getDeclaredConstructor();
            constructor.setAccessible( true );

            return constructor.newInstance();
        }
        catch (ClassNotFoundException e) {
            return getMapperFromServiceLoader( clazz, classLoader );
        }
        catch ( InstantiationException | InvocationTargetException | IllegalAccessException e) {
            throw new RuntimeException( e );
        }
    }

Mapstruct 生成 Impl 过程

我们打开项目的 target 目录 可以看到这样的一个文件
在这里插入图片描述

public class UserCopeMapperImpl implements UserCopeMapper {
    @Override
    public UserDto userToUserDto(User user) {
        if ( user == null ) {
            return null;
        }
        UserDto userDto = new UserDto();
        userDto.setNickName( user.getNickName() );
        userDto.setUsername( user.getUsername() );
        return userDto;
    }

其实在编译阶段就生成了 mapper 子类 ,其原理在于JSR-269规范, lombok 也是基于此实现的

JDK 1.6中实现了JSR-269规范JSR-269:Pluggable Annotations Processing API(插入式注解处理API)。提供了一组插入式注解处理器的标准API在编译期间对注解进行处理。我们可以把它看做是一组编译器的插件,在这些插件里面,可以读取、修改、添加抽象语法树中的任意元素。如果这些插件在处理注解期间对语法树进行了修改,编译器将回到解析及填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止,每一次循环称为一个Round,也就是第一张图中的回环过程。 有了编译器注解处理的标准API后,我们的代码才有可能干涉编译器的行为,由于语法树中的任意元素,甚至包括代码注释都可以在插件之中访问到,所以通过插入式注解处理器实现的插件在功能上有很大的发挥空间。只要有足够的创意,程序员可以使用插入式注解处理器来实现许多原本只能在编码中完成的事情

在 org.mapstruct.ap; 包下有一个 MappingProcessor 类

	@SupportedAnnotationTypes("org.mapstruct.Mapper")
	@SupportedOptions({
	    MappingProcessor.SUPPRESS_GENERATOR_TIMESTAMP,
	    MappingProcessor.SUPPRESS_GENERATOR_VERSION_INFO_COMMENT,
	    MappingProcessor.UNMAPPED_TARGET_POLICY,
	    MappingProcessor.DEFAULT_COMPONENT_MODEL
	})
	public class MappingProcessor extends AbstractProcessor {
	}

AbstractProcessor 相关方法:

  • init() 方法,会被注解处理工具调用
  • process() 方法,这相当于每个处理器的主函数main(),你在这里写你的扫描、评估和处理注解的代码,以及生成Java文件。
  • getSupportedSourceVersion() 方法,指定使用的Java版本,通常这里返回SourceVersion.latestSupported(),默认返回SourceVersion.RELEASE_6。
  • getSupportedAnnotationTypes() 方法,这里必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称,即注解器所支持的注解类型集合,如果没有这样的类型,则返回一个空集合

MappingProcessor类

一个 JSR 269 注释Processor ,它生成映射器接口(使用@Mapper注释的接口)的实现。

实施说明:

生成是通过增量构建每个要生成的映射器(一个Mapper对象)的模型表示来进行的,然后将其写入生成的 Java 源文件。
通过应用一系列ModelElementProcessor ,模型实例化和处理发生在几个阶段/通道中。使用 Java 服务加载器机制检索要应用的处理器,并按照它们的priority顺序进行处理。一般的处理流程是这样的:

  • 检索映射方法
  • 创建Mapper模型
  • 执行丰富和修改(例如为依赖注入添加注释)
  • 如果没有发生错误,则将模型写入 Java 源文件

为了读取注释属性,使用在Hickory 工具的帮助下生成的棱镜。这些棱镜允许轻松访问注释及其属性,而不依赖于它们的类对象。
Java 源文件的创建是使用FreeMarker 模板引擎完成的。映射器模型的每个节点都有一个相应的 FreeMarker 模板文件,该文件提供该元素的 Java 表示,并且可以通过自定义 FreeMarker 指令包含子元素。这样写出模型的根节点( Mapper )将递归地包括所有包含的子元素(例如它的方法,它们的属性映射等)

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

1999

每人一点点,明天会更好

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值