• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

关于java:常见Bean拷贝框架下划线驼峰互转扩展支持

java 搞代码 3年前 (2022-02-14) 16次浏览 已收录 0个评论
文章目录[隐藏]

上一篇博文常见Bean拷贝框架应用姿态及性能比照 介绍了几种bean拷贝框架的应用姿态以及性能比照,次要实用的是属性名统一、类型统一的拷贝,在理论的业务开发中,常常会用到驼峰和下划线的互转,本文在之前的根底上进行扩大

  • cglib
  • hutool

常见Bean拷贝框架下划线驼峰互转扩大反对

<!– more –>

I. 驼峰下划线拷贝反对

下面的应用都是最根本的应用姿态,属性名 + 类型统一,都有getter/setter办法,咱们理论的业务场景中,有一个比拟重要的中央,就是下划线与驼峰的转换反对,如果要应用下面的框架,能够怎么适配?

1. cglib 下划线转驼峰

spring cglib封装 与 污浊版的cglib 实现逻辑差异不大,次要是spring外面做了一些缓存,所以体现会绝对好一点;为了更加通用,这里以污浊版的cglib进行扩大演示

cglib实现转换的外围逻辑在 net.sf.cglib.beans.BeanCopier.Generator.generateClass

<code class="java">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

<code class="java">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的代码都拷贝进来,而后改一下下面正文的两个中央 (残缺的代码参考我的项目源码)

<code class="java">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能够加缓存,防止频繁的创立)

<code class="java">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时,能够无批改的实现驼峰下划线互转,这一点还是十分很优良的

<code class="java">/**
 * 驼峰转换
 *
 * @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,尽管咱们须要手动编码来实现转换,然而益处是性能高啊,既然曾经手动编码了,那也就不介意补上下划线和驼峰的转换了

<code class="java">@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

<code class="java">@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);
    <strong style="color:transparent">来源gaodai#ma#com搞@代~码网</strong>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);
}

输入后果如下

<code class="bash">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)

性能测试

<code class="java">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 反对了驼峰,下划线的互转,最终的体现和下面的也没什么太大区别

<code class="bash">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

II. 其余

1. 一灰灰Blog: https://liuyueyi.github.io/he&#8230;

一灰灰的集体博客,记录所有学习和工作中的博文,欢送大家前去逛逛

2. 申明

尽信书则不如,以上内容,纯属一家之言,因集体能力无限,不免有疏漏和谬误之处,如发现bug或者有更好的倡议,欢送批评指正,不吝感谢

  • 微博地址: 小灰灰Blog
  • QQ: 一灰灰/3302797840

3. 扫描关注

一灰灰blog


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:关于java:常见Bean拷贝框架下划线驼峰互转扩展支持

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址