常見Bean拷貝框架使用姿式及性能對比
Bean屬性拷貝,主要針對幾個經常使用的拷貝框架進行性能對比,以及功能擴展支持java
選用的框架git
<!-- more -->github
當業務量不大時,無論選擇哪一個框架都沒什麼問題,只要功能支持就ok了;可是當數據量大的時候,可能就須要考慮性能問題了;再實際的項目中,正好遇到了這個問題,不只慢,還發現會有鎖競爭,這特麼就尼普了spring
項目中使用的是Spring的 BeanUtils, 版本 3.2.4.RELEASE
, 版本相對較老,主要問題在於org.springframework.beans.CachedIntrospectionResults.forClass
apache
/** * Create CachedIntrospectionResults for the given bean class. * <P>We don't want to use synchronization here. Object references are atomic, * so we can live with doing the occasional unnecessary lookup at startup only. * @param beanClass the bean class to analyze * @return the corresponding CachedIntrospectionResults * @throws BeansException in case of introspection failure */ static CachedIntrospectionResults forClass(Class beanClass) throws BeansException { CachedIntrospectionResults results; Object value; synchronized (classCache) { value = classCache.get(beanClass); } if (value instanceof Reference) { Reference ref = (Reference) value; results = (CachedIntrospectionResults) ref.get(); } else { results = (CachedIntrospectionResults) value; } if (results == null) { if (ClassUtils.isCacheSafe(beanClass, CachedIntrospectionResults.class.getClassLoader()) || isClassLoaderAccepted(beanClass.getClassLoader())) { results = new CachedIntrospectionResults(beanClass); synchronized (classCache) { classCache.put(beanClass, results); } } else { if (logger.isDebugEnabled()) { logger.debug("Not strongly caching class [" + beanClass.getName() + "] because it is not cache-safe"); } results = new CachedIntrospectionResults(beanClass); synchronized (classCache) { classCache.put(beanClass, new WeakReference<CachedIntrospectionResults>(results)); } } } return results; }
看上面的實現,每次獲取value都加了一個同步鎖,並且仍是鎖的全局的classCache
,這就有些過度了啊,微妙的是這段代碼註釋,谷歌翻譯以後爲bash
咱們不想在這裏使用同步。 對象引用是原子的,所以咱們能夠只在啓動時進行偶爾的沒必要要查找。
這意思大概是說我就在啓動的時候用一下,並不會頻繁的使用,因此使用了同步代碼塊也問題不大...微信
可是在BeanUtils#copyProperties
中就蛋疼了,每次都會執行這個方法,扎心了併發
固然咱們如今通常用的Spring5+了,這段代碼也早就作了改造了,新版的以下,再也不存在上面的這個併發問題了app
/** * Create CachedIntrospectionResults for the given bean class. * @param beanClass the bean class to analyze * @return the corresponding CachedIntrospectionResults * @throws BeansException in case of introspection failure */ @SuppressWarnings("unchecked") static CachedIntrospectionResults forClass(Class<?> beanClass) throws BeansException { CachedIntrospectionResults results = strongClassCache.get(beanClass); if (results != null) { return results; } results = softClassCache.get(beanClass); if (results != null) { return results; } results = new CachedIntrospectionResults(beanClass); ConcurrentMap<Class<?>, CachedIntrospectionResults> classCacheToUse; if (ClassUtils.isCacheSafe(beanClass, CachedIntrospectionResults.class.getClassLoader()) || isClassLoaderAccepted(beanClass.getClassLoader())) { classCacheToUse = strongClassCache; } else { if (logger.isDebugEnabled()) { logger.debug("Not strongly caching class [" + beanClass.getName() + "] because it is not cache-safe"); } classCacheToUse = softClassCache; } CachedIntrospectionResults existing = classCacheToUse.putIfAbsent(beanClass, results); return (existing != null ? existing : results); }
接下來咱們看一下幾種常見的bean拷貝框架的使用姿式,以及對比測試框架
阿里規範中,明確說明了,不要使用它,idea安裝阿里的代碼規範插件以後,會有提示
使用姿式比較簡單,引入依賴
<!-- https://mvnrepository.com/artifact/commons-beanutils/commons-beanutils --> <dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.9.4</version> </dependency>
屬性拷貝
@Component public class ApacheCopier { public <K, T> T copy(K source, Class<T> target) throws IllegalAccessException, InstantiationException, InvocationTargetException { T res = target.newInstance(); // 注意,第一個參數爲target,第二個參數爲source // 與其餘的正好相反 BeanUtils.copyProperties(res, source); return res; } }
cglib是經過動態代理的方式來實現屬性拷貝的,與上面基於反射實現方式存在本質上的區別,這也是它性能更優秀的主因
在Spring環境下,通常不須要額外的引入依賴;或者直接引入spring-core
<!-- cglib --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.2.8.RELEASE</version> <scope>compile</scope> </dependency>
屬性拷貝
@Component public class SpringCglibCopier { /** * cglib 對象轉換 * * @param source * @param target * @param <K> * @param <T> * @return * @throws IllegalAccessException * @throws InstantiationException */ public <K, T> T copy(K source, Class<T> target) throws IllegalAccessException, InstantiationException { BeanCopier copier = BeanCopier.create(source.getClass(), target, false); T res = target.newInstance(); copier.copy(source, res, null); return res; } }
固然也能夠直接使用純淨版的cglib,引入依賴
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>
使用姿式和上面如出一轍
@Component public class PureCglibCopier { /** * cglib 對象轉換 * * @param source * @param target * @param <K> * @param <T> * @return * @throws IllegalAccessException * @throws InstantiationException */ public <K, T> T copy(K source, Class<T> target) throws IllegalAccessException, InstantiationException { BeanCopier copier = BeanCopier.create(source.getClass(), target, false); T res = target.newInstance(); copier.copy(source, res, null); return res; } }
這裏使用的是spring 5.2.1.RELEASE
, 就不要拿3.2來使用了,否則併發下的性能實在是感人
基於內省+反射,藉助getter/setter方法實現屬性拷貝,性能比apache高
核心依賴
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>5.2.1.RELEASE</version> <scope>compile</scope> </dependency>
屬性拷貝
@Component public class SpringBeanCopier { /** * 對象轉換 * * @param source * @param target * @param <K> * @param <T> * @return * @throws IllegalAccessException * @throws InstantiationException */ public <K, T> T copy(K source, Class<T> target) throws IllegalAccessException, InstantiationException { T res = target.newInstance(); BeanUtils.copyProperties(source, res); return res; } }
hutool 提供了不少的java工具類,從測試效果來看它的性能比apache會高一點,當低於spring
引入依賴
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-core</artifactId> <version>5.6.0</version> </dependency>
使用姿式
@Component public class HutoolCopier { /** * bean 對象轉換 * * @param source * @param target * @param <K> * @param <T> * @return */ public <K, T> T copy(K source, Class<T> target) throws Exception { return BeanUtil.toBean(source, target); } }
MapStruct 性能更強悍了,缺點也比較明顯,須要聲明bean的轉換接口,自動代碼生成的方式來實現拷貝,性能媲美直接的get/set
引入依賴
<dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.4.2.Final</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.4.2.Final</version> </dependency>
使用姿式
@Mapper public interface MapStructCopier { Target copy(Source source); } @Component public class MapsCopier { private MapStructCopier mapStructCopier = Mappers.getMapper(MapStructCopier.class); public Target copy(Source source, Class<Target> target) { return mapStructCopier.copy(source); } }
缺點也比較明顯,須要顯示的接口轉換聲明
定義兩個Bean,用於轉換測試,兩個bean的成員屬性名,類型徹底一致
@Data public class Source { private Integer id; private String user_name; private Double price; private List<Long> ids; private BigDecimal marketPrice; } @Data public class Target { private Integer id; private String user_name; private Double price; private List<Long> ids; private BigDecimal marketPrice; }
private Random random = new Random(); public Source genSource() { Source source = new Source(); source.setId(random.nextInt()); source.setIds(Arrays.asList(random.nextLong(), random.nextLong(), random.nextLong())); source.setMarketPrice(new BigDecimal(random.nextFloat())); source.setPrice(random.nextInt(120) / 10.0d); source.setUser_name("一灰灰Blog"); return source; } private void copyTest() throws Exception { Source s = genSource(); Target ta = apacheCopier.copy(s, Target.class); Target ts = springBeanCopier.copy(s, Target.class); Target tc = springCglibCopier.copy(s, Target.class); Target tp = pureCglibCopier.copy(s, Target.class); Target th = hutoolCopier.copy(s, Target.class); Target tm = mapsCopier.copy(s, Target.class); System.out.println("source:\t" + s + "\napache:\t" + ta + "\nspring:\t" + ts + "\nsCglib:\t" + tc + "\npCglib:\t" + tp + "\nhuTool:\t" + th + "\nmapStruct:\t" + tm); }
輸出結果以下,知足預期
source: Source(id=1337715455, user_name=一灰灰Blog, price=7.1, ids=[7283949433132389385, 3441022909341384204, 8273318310870260875], marketPrice=0.04279220104217529296875) apache: Target(id=1337715455, user_name=一灰灰Blog, price=7.1, ids=[7283949433132389385, 3441022909341384204, 8273318310870260875], marketPrice=0.04279220104217529296875) spring: Target(id=1337715455, user_name=一灰灰Blog, price=7.1, ids=[7283949433132389385, 3441022909341384204, 8273318310870260875], marketPrice=0.04279220104217529296875) sCglib: Target(id=1337715455, user_name=一灰灰Blog, price=7.1, ids=[7283949433132389385, 3441022909341384204, 8273318310870260875], marketPrice=0.04279220104217529296875) pCglib: Target(id=1337715455, user_name=一灰灰Blog, price=7.1, ids=[7283949433132389385, 3441022909341384204, 8273318310870260875], marketPrice=0.04279220104217529296875) huTool: Target(id=1337715455, user_name=一灰灰Blog, price=7.1, ids=[7283949433132389385, 3441022909341384204, 8273318310870260875], marketPrice=0.04279220104217529296875) mapStruct: Target(id=1337715455, user_name=一灰灰Blog, price=7.1, ids=[7283949433132389385, 3441022909341384204, 8273318310870260875], marketPrice=0.04279220104217529296875)
接下來咱們關注一下不一樣的工具包,實現屬性拷貝的性能對比狀況如何
public void test() throws Exception { // 第一次用於預熱 autoCheck(Target2.class, 10000); autoCheck(Target2.class, 10000); autoCheck(Target2.class, 10000_0); autoCheck(Target2.class, 50000_0); autoCheck(Target2.class, 10000_00); } private <T> void autoCheck(Class<T> target, int size) throws Exception { StopWatch stopWatch = new StopWatch(); runCopier(stopWatch, "apacheCopier", size, (s) -> apacheCopier.copy(s, target)); runCopier(stopWatch, "springCglibCopier", size, (s) -> springCglibCopier.copy(s, target)); runCopier(stopWatch, "pureCglibCopier", size, (s) -> pureCglibCopier.copy(s, target)); runCopier(stopWatch, "hutoolCopier", size, (s) -> hutoolCopier.copy(s, target)); runCopier(stopWatch, "springBeanCopier", size, (s) -> springBeanCopier.copy(s, target)); runCopier(stopWatch, "mapStruct", size, (s) -> mapsCopier.copy(s, target)); System.out.println((size / 10000) + "w -------- cost: " + stopWatch.prettyPrint()); } private <T> void runCopier(StopWatch stopWatch, String key, int size, CopierFunc func) throws Exception { stopWatch.start(key); for (int i = 0; i < size; i++) { Source s = genSource(); func.apply(s); } stopWatch.stop(); } @FunctionalInterface public interface CopierFunc<T> { T apply(Source s) throws Exception; }
輸出結果以下
1w -------- cost: StopWatch '': running time = 583135900 ns --------------------------------------------- ns % Task name --------------------------------------------- 488136600 084% apacheCopier 009363500 002% springCglibCopier 009385500 002% pureCglibCopier 053982900 009% hutoolCopier 016976500 003% springBeanCopier 005290900 001% mapStruct 10w -------- cost: StopWatch '': running time = 5607831900 ns --------------------------------------------- ns % Task name --------------------------------------------- 4646282100 083% apacheCopier 096047200 002% springCglibCopier 093815600 002% pureCglibCopier 548897800 010% hutoolCopier 169937400 003% springBeanCopier 052851800 001% mapStruct 50w -------- cost: StopWatch '': running time = 27946743000 ns --------------------------------------------- ns % Task name --------------------------------------------- 23115325200 083% apacheCopier 481878600 002% springCglibCopier 475181600 002% pureCglibCopier 2750257900 010% hutoolCopier 855448400 003% springBeanCopier 268651300 001% mapStruct 100w -------- cost: StopWatch '': running time = 57141483600 ns --------------------------------------------- ns % Task name --------------------------------------------- 46865332600 082% apacheCopier 1019163600 002% springCglibCopier 1033701100 002% pureCglibCopier 5897726100 010% hutoolCopier 1706155900 003% springBeanCopier 619404300 001% mapStruct
- | 1w | 10w | 50w | 100w |
---|---|---|---|---|
apache | 0.488136600s / 084% | 4.646282100s / 083% | 23.115325200s / 083% | 46.865332600s / 083% |
spring cglib | 0.009363500s / 002% | 0.096047200s / 002% | 0.481878600s / 002% | 1.019163600s / 002% |
pure cglibg | 0.009385500s / 002% | 0.093815600s / 002% | 0.475181600s / 002% | 1.033701100s / 002% |
hutool | 0.053982900s / 009% | 0.548897800s / 010% | 2.750257900s / 010% | 5.897726100s / 010% |
spring | 0.016976500s / 003% | 0.169937400s / 003% | 0.855448400s / 003% | 1.706155900s / 003% |
mapstruct | 0.005290900s / 001% | 0.052851800s / 001% | 0.268651300s / 001% | 0.619404300s / 001% |
total | 0.583135900s | 5.607831900s | 27.946743000s | 57.141483600s |
上面的測試中,存在一個不一樣的變量,即不是用相同的source對象來測試不一樣的工具轉換狀況,可是這個不一樣並不會太影響不一樣框架的性能對比,基本上從上面的運行結果來看
基本趨勢至關於:
apache -> 10 hutool -> 28 spring -> 45 cglib -> 83 mapstruct
若是咱們須要實現簡單的bean拷貝,選擇cglib或者spring的是個不錯選擇
一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛
盡信書則不如,以上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激
一灰灰blog