聲明:java
一、DO(業務實體對象),DTO(數據傳輸對象)。spring
在一個成熟的工程中,尤爲是如今的分佈式系統中,應用與應用之間,還有單獨的應用細分模塊以後,DO 通常不會讓外部依賴,這時候須要在提供對外接口的模塊裏放 DTO 用於對象傳輸,也便是 DO 對象對內,DTO對象對外,DTO 能夠根據業務須要變動,並不須要映射 DO 的所有屬性。shell
/** * 獲取營銷信息 * * @param hallId 場館編號 * @param modelCode 車型代碼 * @return */
BrandMarketInfoDTO getBrandMarketInfo(String hallId, String modelCode);
複製代碼
/** * 獲取水牌營銷信息 * * @param hallId * @param modelCode * @return */
HallCarManageDO getBoardMarketInfo(@Param("hallId") String hallId, @Param("modelCode") String modelCode);
複製代碼
可是咱們HallCarManageDO和BrandMarketInfoDTO中的屬性和屬性類型不是相等的。這種 對象與對象之間的互相轉換,就須要有一個專門用來解決轉換問題的工具,畢竟每個字段都 get/set 會很麻煩。緩存
如下列舉被廣大開發工程師經常使用的Bean屬性複製工具安全
如下選取屬性賦值的功能來對比每一個工具類的不一樣app
import org.springframework.beans.BeanUtils;
/** * @author james mu * @date 2019/10/22 */
public class PojoUtils {
/** * * @param source 源對象 * @param clazz 目標對象 * * @return 複製屬性後的對象 */
public static <T> T copyProperties(Object source, Class<T> clazz) {
if (source == null) {
return null;
}
T target;
try {
target = clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException("經過反射建立對象失敗");
}
BeanUtils.copyProperties(source, target);
return target;
}
}
複製代碼
springframework
的BeanUtils也是經過java內省機制
獲取getter/setter,而後經過反射調用從而實現屬性複製。分佈式
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
複製代碼
import net.sf.cglib.beans.BeanCopier;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/** * @author james mu * @date 2019/10/22 */
public class BeanCopierUtils {
/** * BeanCopier拷貝速度快,性能瓶頸出如今建立BeanCopier實例的過程當中。 * 因此,把建立過的BeanCopier實例放到緩存中,下次能夠直接獲取,提高性能 */
public static Map<String, BeanCopier> beanCopierMap = new ConcurrentHashMap<String, BeanCopier>();
/** * cp 對象賦值 * * @param source 源對象 * @param target 目標對象 */
public static void copyProperties(Object source, Object target) {
String beanKey = generateKey(source.getClass(), target.getClass());
BeanCopier copier = null;
if (!beanCopierMap.containsKey(beanKey)) {
copier = BeanCopier.create(source.getClass(), target.getClass(), false);
beanCopierMap.put(beanKey, copier);
} else {
copier = beanCopierMap.get(beanKey);
}
copier.copy(source, target, null);
}
private static String generateKey(Class<?> class1, Class<?> class2) {
return class1.toString() + class2.toString();
}
}
複製代碼
使用動態代理,生成字節碼類,再經過Java反射成Class,調用其copy方法。ide
你們能夠看到這裏用到了ConcurrentHashMap存取copier
,由於BeanCopier.create使用了緩存,該過程也消耗資源,建議全局只初始化一次。工具
支持自定義轉換器。post
MapSturct
是一個生成類型安全, 高性能且無依賴的 JavaBean 映射代碼的註解處理器(annotation processor)。
抓一下重點:
JavaBean
之間那的映射代碼從字面的理解, 咱們能夠知道, 該工具能夠幫咱們實現 JavaBean
之間的轉換, 經過註解的方式。
同時, 做爲一個工具類,相比於手寫, 其應該具備便捷, 不容易出錯的特色。
依賴引用
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.3.0.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.3.0.Final</version>
<scope>provided</scope>
</dependency>
複製代碼
案例演示
UserDO
import lombok.Data;
import lombok.ToString;
import java.util.Date;
/** * @author james mu * @date 2019/10/22 */
@Data
@ToString
public class UserDO {
private String name;
private String password;
private Integer age;
private Date birthday;
private String sex;
}
複製代碼
UserDTO
import lombok.Data;
import lombok.ToString;
/** * @author james mu * @date 2019/10/22 */
@Data
@ToString
public class UserDTO {
private String name;
private String age;
private String birthday;
private String gender;
}
複製代碼
UserConvertUtils
import com.sanshengshui.javabeanconvert.DO.UserDO;
import com.sanshengshui.javabeanconvert.DTO.UserDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
/** * @author james mu * @date 2019/10/22 */
@Mapper
public interface UserConvertUtils {
UserConvertUtils INSTANCE = Mappers.getMapper(UserConvertUtils.class);
/** * 類型轉換 * * @param userDO UserDO數據持久層類 * @return 數據傳輸類 */
@Mappings({
@Mapping(target = "gender", source = "sex")
})
UserDTO doToDTO(UserDO userDO);
}
複製代碼
@Mapping
明確關係來造成映射(如sex對應gender)結果以下:
UserDO(name=snow, password=123, age=20, birthday=Tue Oct 22 17:10:19 CST 2019, sex=男)
+-+-+-+-+-+-+-+-+-+-+-
UserDTO(name=snow, age=20, birthday=19-10-22 下午5:10, gender=男)
複製代碼
上面中, 我寫了3個步驟來實現了從 UserDTO
到 UserDO
的轉換。
那麼, 做爲一個註解處理器, 經過MapStruct
生成的代碼具備怎麼樣的優點呢?
Java反射原理和反射低的緣由:juejin.im/post/5da33b…
這是相對反射來講的, 反射須要去讀取字節碼的內容, 花銷會比較大。 而經過 MapStruct
來生成的代碼, 其相似於人手寫。 速度上能夠獲得保證。
前面例子中生成的代碼能夠在編譯後看到。 在 target/generated-sources/annotations 裏能夠看到。
對應的代碼
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2019-10-22T17:10:17+0800",
comments = "version: 1.3.0.Final, compiler: javac, environment: Java 1.8.0_222 (Azul Systems, Inc.)"
)
public class UserConvertUtilsImpl implements UserConvertUtils {
@Override
public UserDTO doToDTO(UserDO userDO) {
if ( userDO == null ) {
return null;
}
UserDTO userDTO = new UserDTO();
userDTO.setGender( userDO.getSex() );
userDTO.setName( userDO.getName() );
if ( userDO.getAge() != null ) {
userDTO.setAge( String.valueOf( userDO.getAge() ) );
}
if ( userDO.getBirthday() != null ) {
userDTO.setBirthday( new SimpleDateFormat().format( userDO.getBirthday() ) );
}
return userDTO;
}
}
複製代碼
能夠看到其生成了一個實現類, 而代碼也相似於咱們手寫, 通俗易懂。
測試在兩個簡單的Bean之間轉換的耗時,執行次數分別爲十、100、1k、10k、100k,時間單位爲ms。
雖然反射效率低,但這個時間是很小很小的。根據不一樣工具的性能及功能維度,我的建議當對象轉換操做較少或者應用對性能要求較高時,儘可能不採用工具,而是手寫getter/setter;在不考慮性能的狀況下,普通的對象轉換可使用Cglib.BeanCopier,複雜的對象轉換使用MapStruct。