Posture and performance comparison of common bean copy frameworks

Time:2022-5-19

Posture and performance comparison of common bean copy frameworks

Bean attribute copy is mainly used to compare the performance of several common copy frameworks and support function expansion

Selected frame

  • Cglib (directly using spring encapsulated beancopier)
  • apache
  • MapStruct
  • Spring
  • HuTool

<!– more –>

1. Background

When the business volume is small, no matter which framework is selected, it is OK as long as the function is supported; However, when the amount of data is large, performance may need to be considered; In the actual project, I happen to encounter this problem. It is not only slow, but also found that there will be lock competition, which is especially nip

The project uses the spring version of BeanUtils3.2.4.RELEASE, the version is relatively old, and the main problem isorg.springframework.beans.CachedIntrospectionResults.forClass

/**
 * 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;
}

Looking at the above implementation, a synchronization lock is added every time value is obtained, and the lock is globalclassCache, this is a little too much. The subtle thing is that this code comment is translated by Google

We don’t want to use synchronization here. Object references are atomic, so we can only make occasional unnecessary lookups at startup.

This means that I will use it at startup and will not use it frequently, so it is not a problem to use synchronous code blocks

But inBeanUtils#copyPropertiesThe egg hurts when you hit it. You will perform this method every time, which will prick your heart


Of course, we usually use spring 5 +, and this code has been modified for a long time. The new version is as follows, and the above concurrency problem no longer exists

/**
 * 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);
}

II. Different frames use postures

Next, let’s take a look at the usage postures of several common bean copy frameworks and comparison tests

1. apache BeanUtils

Ali specification clearly states that you should not use it. Idea will be prompted after installing Ali’s code specification plug-in

Using posture is relatively simple and introduces dependency

<!-- https://mvnrepository.com/artifact/commons-beanutils/commons-beanutils -->
<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.9.4</version>
</dependency>

Attribute copy

@Component
public class ApacheCopier {
    public <K, T> T copy(K source, Class<T> target) throws IllegalAccessException, InstantiationException, InvocationTargetException {
        T res = target.newInstance();
        //Note that the first parameter is target and the second parameter is source
        //Contrary to others 
        BeanUtils.copyProperties(res, source);
        return res;
    }
}

2. cglib BeanCopier

Cglib implements attribute copying through dynamic proxy, which is essentially different from the above reflection based implementation, which is also the main reason for its better performance

In the spring environment, there is generally no need to introduce additional dependencies; Or directly introducespring-core

<!--      cglib  -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.2.8.RELEASE</version>
    <scope>compile</scope>
</dependency>

Attribute copy

@Component
public class SpringCglibCopier {
    /**
     *Cglib object conversion
     *
     * @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;
    }
}

Of course, you can also directly use the pure version of cglib to introduce dependencies

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

Use the same posture as above

@Component
public class PureCglibCopier {
    /**
     *Cglib object conversion
     *
     * @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;
    }
}

3. spring BeanUtils

Spring is used here5.2.1.RELEASE, don’t use 3.2, otherwise the performance under concurrency is really touching

Based on introspection + reflection, attribute copying is realized with getter / setter method, and the performance is higher than that of Apache

Core dependency

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>5.2.1.RELEASE</version>
    <scope>compile</scope>
</dependency>

Attribute copy

@Component
public class SpringBeanCopier {

    /**
     *Object conversion
     *
     * @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;
    }
}

4. hutool BeanUtil

Hutool provides many Java tool classes. From the test results, its performance is a little higher than that of Apache, but lower than that of spring

Introduce dependency

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-core</artifactId>
    <version>5.6.0</version>
</dependency>

Use posture

@Component
public class HutoolCopier {

    /**
     *Bean object conversion
     *
     * @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);
    }
}

5. MapStruct

Mapstruct has stronger performance and obvious disadvantages. It needs to declare the conversion interface of beans and realize copying by means of automatic code generation. Its performance is comparable to that of direct get / set

Introduce dependency

<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>

Use posture

@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);
    }
}

The disadvantages are also obvious, and the interface conversion declaration needs to be displayed

6. Test

Define two beans for conversion test. The member property names and types of the two beans are exactly the same

@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;
}

6.1 function test

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 ("a gray 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);
}

The output results are as follows, which meet the expectations

Source: source (id = 1337715455, user_name = a gray blog, price = 7.1, IDS = [7283949433132389385, 3441022909341384204, 8273318310870260875], marketprice = 0.04279220104217529296875)
Apache: target (id = 1337715455, user_name = a gray blog, price = 7.1, IDS = [7283949433132389385, 3441022909341384204, 8273318310870260875], marketprice = 0.04279220104217529296875)
Spring: target (id = 1337715455, user_name = a gray blog, price = 7.1, IDS = [7283949433132389385, 3441022909341384204, 8273318310870260875], marketprice = 0.04279220104217529296875)
Scglib: target (id = 1337715455, user_name = a gray blog, price = 7.1, IDS = [7283949433132389385, 3441022909341384204, 8273318310870260875], marketprice = 0.04279220104217529296875)
Pcglib: target (id = 1337715455, user_name = a gray blog, price = 7.1, IDS = [7283949433132389385, 3441022909341384204, 8273318310870260875], marketprice = 0.04279220104217529296875)
Hutool: target (id = 1337715455, user_name = a gray blog, price = 7.1, IDS = [7283949433132389385, 3441022909341384204, 8273318310870260875], marketprice = 0.04279220104217529296875)
Mapstructure: target (id = 1337715455, user_name = a gray blog, price = 7.1, IDS = [7283949433132389385, 3441022909341384204, 8273318310870260875], marketprice = 0.04279220104217529296875)

6.2 performance test

Next, let’s focus on the performance comparison of different toolkits to realize attribute copy

public void test() throws Exception {
    //First time for preheating
    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;
}

The output results are as follows

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

In the above test, there is a different variable, that is, the same source object is not used to test the conversion of different tools, but this difference will not affect the performance comparison of different frameworks. Basically, from the above running results

  • Mapstruct, cglib and spring performed best
  • Apache performed worst

The basic trend is equivalent to:

apache -> 10 hutool -> 28 spring -> 45 cglib -> 83 mapstruct

If we need to implement a simple bean copy, cglib or spring is a good choice

3. Other

1. A gray bloghttps://blog.hhui.top

A gray personal blog, which records all blog posts in study and work. Welcome to visit

2. Declaration

The above contents are only the words of one family. Due to limited personal ability, it is inevitable that there are omissions and mistakes. If you find a bug or have better suggestions, you are welcome to criticize and correct and be grateful

  • Microblog address:Small gray blog
  • QQ: Yihui / 3302797840
  • Wechat official account:A gray blog

3. Scan attention

A gray blog