這篇文章主要講解了“如何實(shí)現(xiàn)Bean拷貝框架下劃線駝峰互轉(zhuǎn)擴(kuò)展支持”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“如何實(shí)現(xiàn)Bean拷貝框架下劃線駝峰互轉(zhuǎn)擴(kuò)展支持”吧!
創(chuàng)新互聯(lián)公司云計(jì)算的互聯(lián)網(wǎng)服務(wù)提供商,擁有超過(guò)13年的服務(wù)器租用、綿陽(yáng)服務(wù)器托管、云服務(wù)器、虛擬主機(jī)、網(wǎng)站系統(tǒng)開(kāi)發(fā)經(jīng)驗(yàn),已先后獲得國(guó)家工業(yè)和信息化部頒發(fā)的互聯(lián)網(wǎng)數(shù)據(jù)中心業(yè)務(wù)許可證。專業(yè)提供云主機(jī)、虛擬主機(jī)、主機(jī)域名、VPS主機(jī)、云服務(wù)器、香港云服務(wù)器、免備案服務(wù)器等。
上面的使用都是最基本的使用姿勢(shì),屬性名 + 類型一致,都有g(shù)etter/setter方法,我們實(shí)際的業(yè)務(wù)場(chǎng)景中,有一個(gè)比較重要的地方,就是下劃線與駝峰的轉(zhuǎn)換支持,如果要使用上面的框架,可以怎樣適配?
spring cglib封裝 與 純凈版的cglib 實(shí)現(xiàn)邏輯差別不大,主要是spring里面做了一些緩存,所以表現(xiàn)會(huì)相對(duì)好一點(diǎn);為了更加通用,這里以純凈版的cglib進(jìn)行擴(kuò)展演示
cglib實(shí)現(xiàn)轉(zhuǎn)換的核心邏輯在 net.sf.cglib.beans.BeanCopier.Generator.generateClass
public void generateClass(ClassVisitor v) { // ... 省略無(wú)關(guān)代碼 PropertyDescriptor[] getters = ReflectUtils.getBeanGetters(source); PropertyDescriptor[] setters = ReflectUtils.getBeanSetters(target); // 掃描source的所有g(shù)etter方法,寫入到map, key為屬性名; // 為了支持駝峰,下劃線,我們可以擴(kuò)展一下這個(gè)map,如果屬性名為下劃線的,額外加一個(gè)駝峰的kv進(jìn)去 Map names = new HashMap(); for (int i = 0; i < getters.length; i++) { names.put(getters[i].getName(), getters[i]); } // ... for (int i = 0; i < setters.length; i++) { PropertyDescriptor setter = setters[i]; // 這里根據(jù)target的屬性名,獲取source對(duì)應(yīng)的getter方法,同樣適配一下,如果下劃線格式的獲取不到,則改用駝峰的試一下 PropertyDescriptor getter = (PropertyDescriptor)names.get(setter.getName()); if (getter != null) { // .... } } // ... }
改造邏輯,上面的注釋中已經(jīng)貼出來(lái)了,核心實(shí)現(xiàn)就比較簡(jiǎn)單了
提供一個(gè)下劃線轉(zhuǎn)駝峰的工具了 StrUtil
public class StrUtil { private static final char UNDER_LINE = '_'; /** * 下劃線轉(zhuǎn)駝峰 * * @param name * @return */ public static String toCamelCase(String name) { if (null == name || name.length() == 0) { return null; } if (!contains(name, UNDER_LINE)) { return name; } int length = name.length(); StringBuilder sb = new StringBuilder(length); boolean underLineNextChar = false; for (int i = 0; i < length; ++i) { char c = name.charAt(i); if (c == UNDER_LINE) { underLineNextChar = true; } else if (underLineNextChar) { sb.append(Character.toUpperCase(c)); underLineNextChar = false; } else { sb.append(c); } } return sb.toString(); } public static boolean contains(String str, char searchChar) { return str.indexOf(searchChar) >= 0; } }
然后自定義一個(gè) PureCglibBeanCopier, 將之前BeanCopier的代碼都拷貝進(jìn)來(lái),然后改一下上面注釋的兩個(gè)地方 (完整的代碼參考項(xiàng)目源碼)
public void generateClass(ClassVisitor v) { // ... 省略無(wú)關(guān)代碼 PropertyDescriptor[] setters = ReflectUtils.getBeanSetters(target); // 掃描source的所有g(shù)etter方法,寫入到map, key為屬性名; // 為了支持駝峰,下劃線,我們可以擴(kuò)展一下這個(gè)map,如果屬性名為下劃線的,額外加一個(gè)駝峰的kv進(jìn)去 Map<String, PropertyDescriptor> names = buildGetterNameMapper(source) // ... for (int i = 0; i < setters.length; i++) { PropertyDescriptor setter = setters[i]; // 這里根據(jù)target的屬性名,獲取source對(duì)應(yīng)的getter方法,同樣適配一下,如果下劃線格式的獲取不到,則改用駝峰的試一下 PropertyDescriptor getter = loadSourceGetter(names, setter); if (getter != null) { // .... } } // ... } /** * 獲取目標(biāo)的getter方法,支持下劃線與駝峰 * * @param source * @return */ public Map<String, PropertyDescriptor> buildGetterNameMapper(Class source) { PropertyDescriptor[] getters = org.springframework.cglib.core.ReflectUtils.getBeanGetters(source); Map<String, PropertyDescriptor> names = new HashMap<>(getters.length); for (int i = 0; i < getters.length; ++i) { String name = getters[i].getName(); String camelName = StrUtil.toCamelCase(name); names.put(name, getters[i]); if (!name.equalsIgnoreCase(camelName)) { // 支持下劃線轉(zhuǎn)駝峰 names.put(camelName, getters[i]); } } return names; } /** * 根據(jù)target的setter方法,找到source的getter方法,支持下劃線與駝峰的轉(zhuǎn)換 * * @param names * @param setter * @return */ public PropertyDescriptor loadSourceGetter(Map<String, PropertyDescriptor> names, PropertyDescriptor setter) { String setterName = setter.getName(); return names.getOrDefault(setterName, names.get(StrUtil.toCamelCase(setterName))); }
使用姿勢(shì)和之前沒(méi)有什么區(qū)別,就是BeanCopier的創(chuàng)建這里稍稍修改一下即可(BeanCopier可以加緩存,避免頻繁的創(chuàng)建)
public <K, T> T copyAndParse(K source, Class<T> target) throws IllegalAccessException, InstantiationException { // todo copier 可以緩存起來(lái),避免每次重新創(chuàng)建 BeanCopier copier = PureCglibBeanCopier.create(source.getClass(), target, false); T res = target.newInstance(); copier.copy(source, res, null); return res; }
hutool也支持下劃線與駝峰的互轉(zhuǎn),而且不需要修改源碼, 只用我們自己維護(hù)一個(gè)FieldMapper即可,改動(dòng)成本較??;而且在map2bean, bean2map時(shí),可以無(wú)修改的實(shí)現(xiàn)駝峰下劃線互轉(zhuǎn),這一點(diǎn)還是非常很優(yōu)秀的
/** * 駝峰轉(zhuǎn)換 * * @param source * @param target * @param <K> * @param <T> * @return */ public <K, T> T copyAndParse(K source, Class<T> target) throws Exception { T res = target.newInstance(); // 下劃線轉(zhuǎn)駝峰 BeanUtil.copyProperties(source, res, getCopyOptions(source.getClass())); return res; } // 緩存CopyOptions(注意這個(gè)是HuTool的類,不是Cglib的) private Map<Class, CopyOptions> cacheMap = new HashMap<>(); private CopyOptions getCopyOptions(Class source) { CopyOptions options = cacheMap.get(source); if (options == null) { // 不加鎖,我們認(rèn)為重復(fù)執(zhí)行不會(huì)比并發(fā)加鎖帶來(lái)的開(kāi)銷大 options = CopyOptions.create().setFieldMapping(buildFieldMapper(source)); cacheMap.put(source, options); } return options; } /** * @param source * @return */ private Map<String, String> buildFieldMapper(Class source) { PropertyDescriptor[] properties = ReflectUtils.getBeanProperties(source); Map<String, String> map = new HashMap<>(); for (PropertyDescriptor target : properties) { String name = target.getName(); String camel = StrUtil.toCamelCase(name); if (!name.equalsIgnoreCase(camel)) { map.put(name, camel); } String under = StrUtil.toUnderlineCase(name); if (!name.equalsIgnoreCase(under)) { map.put(name, under); } } return map; }
最后再介紹一下MapStruct,雖然我們需要手動(dòng)編碼來(lái)實(shí)現(xiàn)轉(zhuǎn)換,但是好處是性能高啊,既然已經(jīng)手動(dòng)編碼了,那也就不介意補(bǔ)上下劃線和駝峰的轉(zhuǎn)換了
@Mappings({ @Mapping(target = "userName", source = "user_name"), @Mapping(target = "market_price", source = "marketPrice") }) Target2 copyAndParse(Source source);
接下來(lái)測(cè)試一下上面三個(gè)是否能正常工作
定義一個(gè)Target2,注意它與Source有兩個(gè)字段不同,分別是 user_name/userName
, marketPrice/market_price
@Data public class Target2 { private Integer id; private String userName; private Double price; private List<Long> ids; private BigDecimal market_price; } private void camelParse() throws Exception { Source s = genSource(); Target2 cglib = springCglibCopier.copyAndParse(s, Target2.class); Target2 cglib2 = pureCglibCopier.copyAndParse(s, Target2.class); Target2 hutool = hutoolCopier.copyAndParse(s, Target2.class); Target2 map = mapsCopier.copy(s, Target2.class); System.out.println("source:" + s + "\nsCglib:" + cglib + "\npCglib:" + cglib2 + "\nhuTool:" + hutool + "\nMapStruct:" + map); }
輸出結(jié)果如下
source:Source(id=527180337, user_name=一灰灰Blog, price=7.9, ids=[-2509965589596742300, 5995028777901062972, -1914496225005416077], marketPrice=0.35188996791839599609375) sCglib:Target2(id=527180337, userName=一灰灰Blog, price=7.9, ids=[-2509965589596742300, 5995028777901062972, -1914496225005416077], market_price=0.35188996791839599609375) pCglib:Target2(id=527180337, userName=一灰灰Blog, price=7.9, ids=[-2509965589596742300, 5995028777901062972, -1914496225005416077], market_price=0.35188996791839599609375) huTool:Target2(id=527180337, userName=一灰灰Blog, price=7.9, ids=[-2509965589596742300, 5995028777901062972, -1914496225005416077], market_price=0.35188996791839599609375) MapStruct:Target2(id=527180337, userName=一灰灰Blog, price=7.9, ids=[-2509965589596742300, 5995028777901062972, -1914496225005416077], market_price=0.35188996791839599609375)
性能測(cè)試
private <T> void autoCheck2(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.copyAndParse(s, target)); runCopier(stopWatch, "pureCglibCopier", size, (s) -> pureCglibCopier.copyAndParse(s, target)); runCopier(stopWatch, "hutoolCopier", size, (s) -> hutoolCopier.copyAndParse(s, target)); runCopier(stopWatch, "springBeanCopier", size, (s) -> springBeanCopier.copy(s, target)); runCopier(stopWatch, "mapStruct", size, (s) -> mapsCopier.copyAndParse(s, target)); System.out.println((size / 10000) + "w -------- cost: " + stopWatch.prettyPrint()); }
對(duì)比結(jié)果如下,雖然cglib, hutool 支持了駝峰,下劃線的互轉(zhuǎn),最終的表現(xiàn)和上面的也沒(méi)什么太大區(qū)別
1w -------- cost: StopWatch '': running time = 754589100 ns --------------------------------------------- ns % Task name --------------------------------------------- 572878100 076% apacheCopier yihui 017037900 002% springCglibCopier 031207500 004% pureCglibCopier 105254600 014% hutoolCopier 022156300 003% springBeanCopier 006054700 001% mapStruct 1w -------- cost: StopWatch '': running time = 601845500 ns --------------------------------------------- ns % Task name --------------------------------------------- 494895600 082% apacheCopier 009014500 001% springCglibCopier 008998600 001% pureCglibCopier 067145800 011% hutoolCopier 016557700 003% springBeanCopier 005233300 001% mapStruct 10w -------- cost: StopWatch '': running time = 5543094200 ns --------------------------------------------- ns % Task name --------------------------------------------- 4474871900 081% apacheCopier 089066500 002% springCglibCopier 090526400 002% pureCglibCopier 667986400 012% hutoolCopier 166274800 003% springBeanCopier 054368200 001% mapStruct 50w -------- cost: StopWatch '': running time = 27527708400 ns --------------------------------------------- ns % Task name --------------------------------------------- 22145604900 080% apacheCopier 452946700 002% springCglibCopier 448455700 002% pureCglibCopier 3365908800 012% hutoolCopier 843306700 003% springBeanCopier 271485600 001% mapStruct
感謝各位的閱讀,以上就是“如何實(shí)現(xiàn)Bean拷貝框架下劃線駝峰互轉(zhuǎn)擴(kuò)展支持”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)如何實(shí)現(xiàn)Bean拷貝框架下劃線駝峰互轉(zhuǎn)擴(kuò)展支持這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!
文章標(biāo)題:如何實(shí)現(xiàn)Bean拷貝框架下劃線駝峰互轉(zhuǎn)擴(kuò)展支持
網(wǎng)站網(wǎng)址:http://bm7419.com/article32/jdjopc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供自適應(yīng)網(wǎng)站、搜索引擎優(yōu)化、網(wǎng)站導(dǎo)航、域名注冊(cè)、營(yíng)銷型網(wǎng)站建設(shè)、網(wǎng)站維護(hù)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)