上一篇博文常见Bean拷贝框架应用姿态及性能比照 介绍了几种bean拷贝框架的应用姿态以及性能比照,次要实用的是属性名统一、类型统一的拷贝,在理论的业务开发中,常常会用到驼峰和下划线的互转,本文在之前的根底上进行扩大
- cglib
- hutool
常见Bean拷贝框架下划线驼峰互转扩大反对
<!-- more -->
I. 驼峰下划线拷贝反对
下面的应用都是最根本的应用姿态,属性名 + 类型统一,都有getter/setter办法,咱们理论的业务场景中,有一个比拟重要的中央,就是下划线与驼峰的转换反对,如果要应用下面的框架,能够怎么适配?
1. cglib 下划线转驼峰
spring cglib封装 与 污浊版的cglib 实现逻辑差异不大,次要是spring外面做了一些缓存,所以体现会绝对好一点;为了更加通用,这里以污浊版的cglib进行扩大演示
cglib实现转换的外围逻辑在 net.sf.cglib.beans.BeanCopier.Generator.generateClass
public void generateClass(ClassVisitor v) { // ... 省略无关代码 PropertyDescriptor[] getters = ReflectUtils.getBeanGetters(source); PropertyDescriptor[] setters = ReflectUtils.getBeanSetters(target); // 扫描source的所有getter办法,写入到map, key为属性名; // 为了反对驼峰,下划线,咱们能够扩大一下这个map,如果属性名为下划线的,额定加一个驼峰的kv进去 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]; // 这里依据target的属性名,获取source对应的getter办法,同样适配一下,如果下划线格局的获取不到,则改用驼峰的试一下 PropertyDescriptor getter = (PropertyDescriptor)names.get(setter.getName()); if (getter != null) { // .... } } // ...}
革新逻辑,下面的正文中曾经贴出来了,外围实现就比较简单了
提供一个下划线转驼峰的工具了 StrUtil
public class StrUtil { private static final char UNDER_LINE = '_'; /** * 下划线转驼峰 * * @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; }}
而后自定义一个 PureCglibBeanCopier, 将之前BeanCopier的代码都拷贝进来,而后改一下下面正文的两个中央 (残缺的代码参考我的项目源码)
public void generateClass(ClassVisitor v) { // ... 省略无关代码 PropertyDescriptor[] setters = ReflectUtils.getBeanSetters(target); // 扫描source的所有getter办法,写入到map, key为属性名; // 为了反对驼峰,下划线,咱们能够扩大一下这个map,如果属性名为下划线的,额定加一个驼峰的kv进去 Map<String, PropertyDescriptor> names = buildGetterNameMapper(source) // ... for (int i = 0; i < setters.length; i++) { PropertyDescriptor setter = setters[i]; // 这里依据target的属性名,获取source对应的getter办法,同样适配一下,如果下划线格局的获取不到,则改用驼峰的试一下 PropertyDescriptor getter = loadSourceGetter(names, setter); if (getter != null) { // .... } } // ...}/** * 获取指标的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)) { // 反对下划线转驼峰 names.put(camelName, getters[i]); } } return names;}/** * 依据target的setter办法,找到source的getter办法,反对下划线与驼峰的转换 * * @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)));}
应用姿态和之前没有什么区别,就是BeanCopier的创立这里稍稍批改一下即可(BeanCopier能够加缓存,防止频繁的创立)
public <K, T> T copyAndParse(K source, Class<T> target) throws IllegalAccessException, InstantiationException { // todo copier 能够缓存起来,防止每次从新创立 BeanCopier copier = PureCglibBeanCopier.create(source.getClass(), target, false); T res = target.newInstance(); copier.copy(source, res, null); return res;}
2. hutool 下划线转驼峰
hutool也反对下划线与驼峰的互转,而且不须要批改源码, 只用咱们本人保护一个FieldMapper即可,改变老本较小;而且在map2bean, bean2map时,能够无批改的实现驼峰下划线互转,这一点还是十分很优良的
/** * 驼峰转换 * * @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(); // 下划线转驼峰 BeanUtil.copyProperties(source, res, getCopyOptions(source.getClass())); return res;}// 缓存CopyOptions(留神这个是HuTool的类,不是Cglib的)private Map<Class, CopyOptions> cacheMap = new HashMap<>();private CopyOptions getCopyOptions(Class source) { CopyOptions options = cacheMap.get(source); if (options == null) { // 不加锁,咱们认为反复执行不会比并发加锁带来的开销大 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;}
3. mapstruct
最初再介绍一下MapStruct,尽管咱们须要手动编码来实现转换,然而益处是性能高啊,既然曾经手动编码了,那也就不介意补上下划线和驼峰的转换了
@Mappings({ @Mapping(target = "userName", source = "user_name"), @Mapping(target = "market_price", source = "marketPrice")})Target2 copyAndParse(Source source);
4. 测试
接下来测试一下下面三个是否能失常工作
定义一个Target2,留神它与Source有两个字段不同,别离是 user_name/userName
, marketPrice/market_price
@Datapublic 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);}
输入后果如下
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)
性能测试
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());}
比照后果如下,尽管cglib, hutool 反对了驼峰,下划线的互转,最终的体现和下面的也没什么太大区别
1w -------- cost: StopWatch '': running time = 754589100 ns---------------------------------------------ns % Task name---------------------------------------------572878100 076% apacheCopier yihui017037900 002% springCglibCopier031207500 004% pureCglibCopier105254600 014% hutoolCopier022156300 003% springBeanCopier006054700 001% mapStruct1w -------- cost: StopWatch '': running time = 601845500 ns---------------------------------------------ns % Task name---------------------------------------------494895600 082% apacheCopier009014500 001% springCglibCopier008998600 001% pureCglibCopier067145800 011% hutoolCopier016557700 003% springBeanCopier005233300 001% mapStruct10w -------- cost: StopWatch '': running time = 5543094200 ns---------------------------------------------ns % Task name---------------------------------------------4474871900 081% apacheCopier089066500 002% springCglibCopier090526400 002% pureCglibCopier667986400 012% hutoolCopier166274800 003% springBeanCopier054368200 001% mapStruct50w -------- cost: StopWatch '': running time = 27527708400 ns---------------------------------------------ns % Task name---------------------------------------------22145604900 080% apacheCopier452946700 002% springCglibCopier448455700 002% pureCglibCopier3365908800 012% hutoolCopier843306700 003% springBeanCopier271485600 001% mapStruct
II. 其余
1. 一灰灰Blog: https://liuyueyi.github.io/he...
一灰灰的集体博客,记录所有学习和工作中的博文,欢送大家前去逛逛
- 我的项目源码: https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/004-bean-util
2. 申明
尽信书则不如,以上内容,纯属一家之言,因集体能力无限,不免有疏漏和谬误之处,如发现bug或者有更好的倡议,欢送批评指正,不吝感谢
- 微博地址: 小灰灰Blog
- QQ: 一灰灰/3302797840
3. 扫描关注
一灰灰blog