[Java/Spring] 深入理解 : SpringBoot PropertyMapper
1 概述: SpringBoot PropertyMapper
简介:PropertyMapper ∈ 对象拷贝与转换工具
PropertyMapper
是Spring提供的一个工具类,主要用于对对象的重新赋值,拷贝、转换等操作。
- 位于:
org.springframework.boot.context.properties.PropertyMapper
辨析: Spring BeanUtils 与 SpringBoot PropertyMapper
- 共同点:
- 对象及属性拷贝工具:BeanUtils 和 PropertyMapper 都是 Spring 框架中用于处理 Java Bean 之间对象属性拷贝的工具。
- 不同点:
- 通用 vs 定制 :
BeanUtils
提供了拷贝属性的通用方法,而PropertyMapper
提供了一种更灵活、可扩展的方式来定制属性的映射逻辑。
- 对于
BeanUtils.copyProperties
来说,你必须保证属性名和类型是相同的,因为它是根据get和set方法来赋值的。
- 浅拷贝 vs 深拷贝:
- BeanUtils :浅拷贝
org.springframework.beans.BeanUtils 工具类中的 copyProperties() 无法实现深拷贝,只能实现浅拷贝
详情参见:
- PropertyMapper : 支持深拷贝,完全取决于应用程序的开发者用户的诉求
- 模块/包
- BeanUtils :
spring-beans
模块
org.springframework.beans.BeanUtils
- PropertyMapper :
spring-boot
模块
org.springframework.boot.context.properties.PropertyMapper
2 应用场景
场景 :2个异构数据对象的拷贝与转换
- 在实际工作中,经常会遇到将数据库的实体类 Entity 转成 DTO 类的操作。通常的方法:
- 手工方法:我们有可以将属性一个个
get
出来,再set
进去。但经常涉及到判空、数据类型的转换等简单的逻辑处理,容易留下一大堆 IF ELSE 的臃肿代码。
- 第三方工具:用
BeanUtils
工具类 将对应类型的属性一个个copy进去。
- 现在还可以尝试使用 SpringBoot 的 PropertyMapper 来做数据对象的转换
案例1:SpringBoot 的 RabbitTemplateConfiguration
- SpringBoot 官方模块
spring-boot-starter-amqp
中RabbitTemplate
的配置实现
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration.RabbitTemplateConfiguration
@Bean
@ConditionalOnSingleCandidate(ConnectionFactory.class)
@ConditionalOnMissingBean(RabbitOperations.class)
public RabbitTemplate rabbitTemplate(RabbitProperties properties,
ObjectProvider<MessageConverter> messageConverter,
ObjectProvider<RabbitRetryTemplateCustomizer> retryTemplateCustomizers,
ConnectionFactory connectionFactory) {
PropertyMapper map = PropertyMapper.get();
RabbitTemplate template = new RabbitTemplate(connectionFactory);
messageConverter.ifUnique(template::setMessageConverter);
template.setMandatory(determineMandatoryFlag(properties));
RabbitProperties.Template templateProperties = properties.getTemplate();
if (templateProperties.getRetry().isEnabled()) {
template.setRetryTemplate(
new RetryTemplateFactory(retryTemplateCustomizers.orderedStream().collect(Collectors.toList()))
.createRetryTemplate(templateProperties.getRetry(),
RabbitRetryTemplateCustomizer.Target.SENDER));
}
map.from(templateProperties::getReceiveTimeout).whenNonNull().as(Duration::toMillis)
.to(template::setReceiveTimeout);
map.from(templateProperties::getReplyTimeout).whenNonNull().as(Duration::toMillis)
.to(template::setReplyTimeout);
map.from(templateProperties::getExchange).to(template::setExchange);
map.from(templateProperties::getRoutingKey).to(template::setRoutingKey);
map.from(templateProperties::getDefaultReceiveQueue).whenNonNull().to(template::setDefaultReceiveQueue);
return template;
}
案例2:基于 Http11NioProtocol、WebServerFactoryCustomizer 、自定义配置实体,实现 自定义 SpringBoot 的 Tomcat Server 配置
TomcatEmbedServerProperties : 应用程序的自定义配置实体
//import org.springframework.boot.context.properties.ConfigurationProperties;
//import org.springframework.context.annotation.ComponentScan;
//import org.springframework.context.annotation.Configuration;
/**
* @create-time 2023/4/11
* @description ...
*/
//@ComponentScan
//@Configuration
//@ConfigurationProperties(
// prefix="service-config.tomcat-server"
// , ignoreUnknownFields = true
//)
public class TomcatEmbedServerProperties { //应用程序的自定义配置实体
private Integer port;
private Integer minSpareThreads;
private Integer maxThreads;
private Integer acceptCount;
private Integer maxConnections;
private Integer maxKeepAliveRequests;
private Integer keepAliveTimeout;
private Integer connectionTimeout;
public TomcatEmbedServerProperties(Integer port, Integer minSpareThreads, Integer maxThreads, Integer acceptCount, Integer maxConnections, Integer maxKeepAliveRequests, Integer keepAliveTimeout, Integer connectionTimeout) {
this.port = port;
this.minSpareThreads = minSpareThreads;
this.maxThreads = maxThreads;
this.acceptCount = acceptCount;
this.maxConnections = maxConnections;
this.maxKeepAliveRequests = maxKeepAliveRequests;
this.keepAliveTimeout = keepAliveTimeout;
this.connectionTimeout = connectionTimeout;
}
public TomcatEmbedServerProperties() {
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public Integer getMinSpareThreads() {
return minSpareThreads;
}
public void setMinSpareThreads(Integer minSpareThreads) {
this.minSpareThreads = minSpareThreads;
}
public Integer getMaxThreads() {
return maxThreads;
}
public void setMaxThreads(Integer maxThreads) {
this.maxThreads = maxThreads;
}
public Integer getAcceptCount() {
return acceptCount;
}
public void setAcceptCount(Integer acceptCount) {
this.acceptCount = acceptCount;
}
public Integer getMaxConnections() {
return maxConnections;
}
public void setMaxConnections(Integer maxConnections) {
this.maxConnections = maxConnections;
}
public Integer getMaxKeepAliveRequests() {
return maxKeepAliveRequests;
}
public void setMaxKeepAliveRequests(Integer maxKeepAliveRequests) {
this.maxKeepAliveRequests = maxKeepAliveRequests;
}
public Integer getKeepAliveTimeout() {
return keepAliveTimeout;
}
public void setKeepAliveTimeout(Integer keepAliveTimeout) {
this.keepAliveTimeout = keepAliveTimeout;
}
public Integer getConnectionTimeout() {
return connectionTimeout;
}
public void setConnectionTimeout(Integer connectionTimeout) {
this.connectionTimeout = connectionTimeout;
}
private static boolean isPositive(int value) {
return value > 0;
}
@Override
public String toString() {
return "TomcatEmbedServerProperties{" +
"port=" + port +
", minSpareThreads=" + minSpareThreads +
", maxThreads=" + maxThreads +
", acceptCount=" + acceptCount +
", maxConnections=" + maxConnections +
", maxKeepAliveRequests=" + maxKeepAliveRequests +
", keepAliveTimeout=" + keepAliveTimeout +
", connectionTimeout=" + connectionTimeout +
'}';
}
}
WebServerConfiguration : 应用程序的自定义 Tomcat Server 配置 Bean
案例3: Order 转 OrderDTO
Order
@Data
public class Order {
private Long id;
private BigDecimal totalAmout;
private Integer status;
private Long userId;
private LocalDateTime createTime;
}
OrderDTO
@Data
public class OrderDTO {
private Long id;
private BigDecimal totalAmout;
private Integer status;
private Long userId;
private String createTime;
}
使用 PropertyMapper 转换
Order order = new Order();
order.setId(1L);
order.setStatus(1);
order.setTotalAmout(BigDecimal.ONE);
order.setUserId(100L);
order.setCreateTime(LocalDateTime.now());
PropertyMapper propertyMapper = PropertyMapper.get();
OrderDTO orderDTO = new OrderDTO();
propertyMapper.from(order::getId).to(orderDTO::setId);
// 如果from获取到的元素不是null,则执行to里面的动作
propertyMapper.from(order::getStatus).whenNonNull().to(orderDTO::setStatus);
propertyMapper.from(order::getUserId).to(orderDTO::setUserId);
propertyMapper.from(order::getTotalAmout).to(orderDTO::setTotalAmout);
// 因为Order里面的createTime是LocalDateTime类型,OrderDTO里面则是String类型,需要转换一下
propertyMapper.from(order::getCreateTime).as(createTime -> {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
return createTime.format(formatter);
}).to(orderDTO::setCreateTime);
这样一来就可以通过 PropertyMapper 将 Order 对象的值 set 到 OrderDTO 对象中。
3 PropertyMapper API
<T> Source<T> from(Supplier<T> supplier)
:提供值的来源,入参为Supplier
<T> Source<T> from(T value)
:一种重载形式,入参可以为一个对象void to(Consumer<T> consumer)
:通过将任何未过滤的值传递给指定的使用者来完成映射<R> R toInstance(Function<T, R> factory)
:通过从未过滤的值创建新实例来完成映射void toCall(Runnable runnable)
:当值还没有时,通过调用指定的方法来完成映射<R> Source<R> as(Function<T, R> adapter)
:将T
类型的入参转成R
类型的出参,类似于Stream
中的map
Source<T> when...
:这一系列方法,都是过滤用的。在from
后面调用,如果满足条件,就直接to
方法static PropertyMapper get()
:提供PropertyMapper
实例
PropertyMapper 类内部维护一个静态实例,我们一开始只能通过获取它得到 PropertyMapper 实例
get()
方法始终返回的是其内部的 PropertyMapper final static 实例, PropertyMapper 对象本身的内存开销不会太大
PropertyMapper alwaysApplying(SourceOperator operator)
:自定义过滤规则,参考代码
alwaysApplying 用于向 PropertyMapper 添加一个 when 条件判定,这个判定在每次 from 方法中都被调用。
alwaysApplyingWhenNonNull
则是对这个方法的包装。
PropertyMapper alwaysApplyingWhenNonNull()
:提供实例时,当前实例就过滤掉from
之后是null
的元素。PropertyMapper.get().alwaysApplyingWhenNonNull();
//使当前 PropertyMapper 只会映射 LocalDateTime 类型的字段
PropertyMapper propertyMapper = PropertyMapper.get()
.alwaysApplying( new PropertyMapper.SourceOperator() {
@Override
public <T> PropertyMapper.Source<T> apply(PropertyMapper.Source<T> source) {
return source.when(t -> t instanceof LocalDateTime);
}
} );
//注意:如果from方法后面有when条件,则 alwaysApplying 中设置的初始化提交将会失效
Y 推荐文献
- [Java] Java 对象拷贝与转换 - 博客园/千千寰宇
X 参考文献
- [小工具]PropertyMapper使用 - 稀土掘金
- 简化 Java 代码 ——(一)使用 PropertyMapper - 博客园 【不推荐】

本文作者:
千千寰宇
本文链接: https://www.cnblogs.com/johnnyzen
关于博文:评论和私信会在第一时间回复,或直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
日常交流:大数据与软件开发-QQ交流群: 774386015 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!
本文链接: https://www.cnblogs.com/johnnyzen
关于博文:评论和私信会在第一时间回复,或直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
日常交流:大数据与软件开发-QQ交流群: 774386015 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!