A blog post on common Bean copy framework USES the pose and performance comparison, this paper introduces several kinds of Bean copy framework using posture and performance comparison, the main applicable is a copy of the consistent attribute name, type, in the actual business development, often used hump and underline transfers, this article on the basis of before

  • cglib
  • hutool

Common Bean copy framework underline camel hump cross extension support

I. Hump underline copy support

The above uses are the most basic use posture, attribute name + type consistent, getter/setter methods, our actual business scenarios, there is a more important place, is the underline and hump conversion support, if you want to use the above framework, how can adapt?

1. Underline cglib and turn the hump

Spring cglib package and the pure version of cglib implementation logic is not very different, mainly spring to do some caching, so the performance will be relatively better; To be more general, here is an extended demonstration of cglib in its pure form

Additional implementation core logic of transformation in the net. Sf. Additional. Beans. BeanCopier. The Generator. The generateClass

public void generateClass(ClassVisitor v) {
    / /... Omit irrelevant code
    PropertyDescriptor[] getters = ReflectUtils.getBeanGetters(source);
    PropertyDescriptor[] setters = ReflectUtils.getBeanSetters(target);
    
    // Scan all getter methods of source, write to map, key is attribute name;
    // To support hump, underline, we can extend the map by adding kv for hump if the attribute is underlined
    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];
        // Get the getter for the source from the target property. If you can't get the underline, use the hump
        PropertyDescriptor getter = (PropertyDescriptor)names.get(setter.getName());
        if(getter ! =null) {
           / /...}}// ...
}
Copy the code

The transformation logic, which is posted in the comments above, makes the core implementation relatively simple

Provides an underline turn hump tool for StrUtil

public class StrUtil {
    private static final char UNDER_LINE = '_';

    /** * underline to hump **@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; }}Copy the code

Then make a custom PureCglibBeanCopier, copy the previous BeanCopier code, and change the above comments in two places (refer to the project source code for the full code).

public void generateClass(ClassVisitor v) {
    / /... Omit irrelevant code
    PropertyDescriptor[] setters = ReflectUtils.getBeanSetters(target);
    
    // Scan all getter methods of source, write to map, key is attribute name;
    // To support hump, underline, we can extend the map by adding kv for hump if the attribute is underlined
    Map<String, PropertyDescriptor> names = buildGetterNameMapper(source)
   
    // ...

    for (int i = 0; i < setters.length; i++) {
        PropertyDescriptor setter = setters[i];
        // Get the getter for the source from the target property. If you can't get the underline, use the hump
        PropertyDescriptor getter = loadSourceGetter(names, setter);
        if(getter ! =null) {
           / /...}}// ...
}


/** * Gets the getter method for the target, supporting underline and hump **@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)) {// Support underline to humpnames.put(camelName, getters[i]); }}return names;
}

/** * Find source getter from target setter **@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)));
}
Copy the code

The use of gestures is the same as before, except that the BeanCopier creation can be slightly modified (BeanCopier can be cached to avoid frequent creation)

public <K, T> T copyAndParse(K source, Class<T> target) throws IllegalAccessException, InstantiationException {
    // Todo copier can be cached to avoid re-creation every time
    BeanCopier copier = PureCglibBeanCopier.create(source.getClass(), target, false);
    T res = target.newInstance();
    copier.copy(source, res, null);
    return res;
}
Copy the code

2. Hutool underline the hump

Hutool also supports underlining and humping, and does not need to modify the source code. We can only maintain a FieldMapper, which is very cheap to change. And in map2bean, Bean2map, the realization of hump underline interchange without modification, which is very good

/** * Hump conversion **@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();
    // Underline the hump
    BeanUtil.copyProperties(source, res, getCopyOptions(source.getClass()));
    return res;
}

// Cache CopyOptions (HuTool, not Cglib)

private Map<Class, CopyOptions> cacheMap = new HashMap<>();


private CopyOptions getCopyOptions(Class source) {
    CopyOptions options = cacheMap.get(source);
    if (options == null) {
        // Without locking, we believe that repeated execution is not more expensive than concurrent locking
        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;
}
Copy the code

3. mapstruct

Finally, MapStruct, although we need to code manually to implement the transformation, but the advantage is high performance, now that we have to code manually, we don’t mind the underlining and hump conversion

@Mappings({ @Mapping(target = "userName", source = "user_name"), @Mapping(target = "market_price", source = "marketPrice") })
Target2 copyAndParse(Source source);
Copy the code

4. Test

Let’s test to see if the above three work

Define a Target2. Note that it differs from Source with two fields: user_name/userName and 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(a) 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);
}
Copy the code

The output is as follows

source:Source(id=527180337, user_name= a Blog, price=7.9, ids=[-2509965589596742300, 5995028777901062972, - 1914496225005416077], marketPrice = 0.35188996791839599609375) sCglib: Target2 (id = 527180337, Blog userName = one is gray, price = 7.9, ids=[-2509965589596742300, 5995028777901062972, -1914496225005416077], Market_price = 0.35188996791839599609375) pCglib: Target2 (id = 527180337, Blog userName = one is gray, price = 7.9, ids=[-2509965589596742300, 5995028777901062972, -1914496225005416077], Market_price = 0.35188996791839599609375) huTool: Target2 (id = 527180337, Blog userName = one is gray, price = 7.9, ids=[-2509965589596742300, 5995028777901062972, -1914496225005416077], Market_price = 0.35188996791839599609375) MapStruct: Target2 (id = 527180337, Blog userName = one is gray, price = 7.9, Ids = [- 2509965589596742300, 5995028777901062972-1914496225005416077], market_price = 0.35188996791839599609375)Copy the code

The performance test

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());
}
Copy the code

Cglib, hutool, and Cglib support both hump and underline, but the result is not much different

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
Copy the code

II. The other

1. A gray Blog:liuyueyi.github.io/hexblog

A gray personal blog, recording all the study and work in the blog, welcome everyone to go to stroll

  • Project source: github.com/liuyueyi/sp…

2. Statement

As far as the letter is not as good, the above content is purely one’s opinion, due to the limited personal ability, it is inevitable that there are omissions and mistakes, if you find bugs or have better suggestions, welcome criticism and correction, don’t hesitate to appreciate

Wechat official account: One Grey Blog