Bean property copying, mainly for several commonly used copying framework usage posture introduction, and corresponding performance comparison

Selected frame

  • Cglib (Spring-packaged BeanCoier and native BeanCopier)
  • apache
  • MapStruct
  • Spring
  • HuTool

Common Bean copying frameworks use posture and performance comparisons

I. background

When business volume is not high, it doesn’t matter which framework you choose, as long as the functionality is supported, it’s OK. But when you have a large amount of data, you may need to consider performance; Then in the actual project, just encountered this problem, not only slow, but also found that there will be lock competition, which is the f * * king IP

BeanUtils, version of the project is used in the Spring 3.2.4. RELEASE, relatively old version, the main problem lies in the org. 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, newWeakReference<CachedIntrospectionResults>(results)); }}}return results;
}
Copy the code

See the above implementation, each time a value is acquired, a lock is added to the global classCache lock, which is a bit excessive

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

This probably means that I use it at startup, and I don’t use it very often, so using a synchronized code block is not a problem…

But in BeanUtils#copyProperties it hurts, executing this method every time


Of course, we now generally use Spring5+, this code has long been modified, the new version is as follows, there is no longer the above concurrency problem

/**
 * 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 = newCachedIntrospectionResults(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);
}
Copy the code

II. Different frames using posture

Let’s take a look at the usage posture of several common bean copy frameworks, as well as comparative testing

1. apache BeanUtils

Ali specification, explicitly stated, do not use it, idea after installing ali code specification plug-in, will be prompted

Using gestures is easy and introduces dependencies

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

Copy attributes

@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
        // As opposed to the others
        BeanUtils.copyProperties(res, source);
        returnres; }}Copy the code

2. cglib BeanCopier

Cglib uses dynamic proxy to copy attributes, which is fundamentally different from the reflection – based implementation. This is the main reason for its better performance

In the Spring environment, there is generally no need for additional imported dependencies; Or you could introduce Spring-core directly

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

Copy attributes

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

You can also use a clean version of Cglib to introduce dependencies

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
Copy the code

The posture is exactly the same 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);
        returnres; }}Copy the code

3. spring BeanUtils

Use Spring 5.2.1.RELEASE here, don’t use 3.2, otherwise the performance under concurrency is really impressive

Based on introspection + reflection, with getter/setter methods to achieve property copy, higher performance than Apache

The core depends on

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>5.21..RELEASE</version>
    <scope>compile</scope>
</dependency>
Copy the code

Copy attributes

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

4. hutool BeanUtil

Hutool provides a number of Java utility classes, and in terms of test results it performs slightly better than Apache, but less well than Spring

Introduction of depend on

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

Use the pose

@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 {
        returnBeanUtil.toBean(source, target); }}Copy the code

5. MapStruct

MapStruct is more powerful and has obvious disadvantages. It needs to declare the bean conversion interface, automatic code generation method to achieve copy, performance comparable to direct GET /set

Introduction of depend on

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.4.2. The Final</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.4.2. The Final</version>
</dependency>
Copy the code

Use the pose

@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) {
        returnmapStructCopier.copy(source); }}Copy the code

The disadvantage is also obvious, the need to display the interface conversion declaration

Test 6.

Define two beans for conversion testing, with identical member attribute names and types

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

6.1 Functional Tests

private Random random = new Random();

public Source genSource(a) {
    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.0 d);
    source.setUser_name("A Gray Blog");
    return source;
}


 private void copyTest(a) 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);
}
Copy the code

The output is as follows

source: Source(id=1337715455, user_name= a Blog, price=7.1, ids=[7283949433132389385, 3441022909341384204, 8273318310870260875], marketPrice = 0.04279220104217529296875) apache: Target(id=1337715455, user_name= Blog, price=7.1, ids=[7283949433132389385, 3441022909341384204, 8273318310870260875], marketPrice = 0.04279220104217529296875) spring: Target(id=1337715455, user_name= Blog, price=7.1, ids=[7283949433132389385, 3441022909341384204, 8273318310870260875], marketPrice sCglib = 0.04279220104217529296875) : Target(id=1337715455, user_name= Blog, price=7.1, ids=[7283949433132389385, 3441022909341384204, 8273318310870260875], marketPrice pCglib = 0.04279220104217529296875) : Target(id=1337715455, user_name= Blog, price=7.1, ids=[7283949433132389385, 3441022909341384204, 8273318310870260875], marketPrice huTool = 0.04279220104217529296875) : Target(id=1337715455, user_name= Blog, price=7.1, ids=[7283949433132389385, 3441022909341384204, 8273318310870260875], marketPrice mapStruct = 0.04279220104217529296875) : Target(id=1337715455, user_name= Blog, price=7.1, ids=[7283949433132389385, 3441022909341384204, 8273318310870260875], marketPrice = 0.04279220104217529296875)Copy the code

6.2 Performance Test

Let’s take a look at the performance of different toolkits for property copying

public void test(a) throws Exception {
    // The 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;
}
Copy the code

The output is 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% mapStructCopy the code
1w 10w 50w 100w
apache 0.488136600 s / 084% 4.646282100 s / 083% 23.115325200 s / 083% 46.865332600 s / 083%
spring cglib 0.009363500 s / 002% 0.096047200 s / 002% 0.481878600 s / 002% 1.019163600 s / 002%
pure cglibg 0.009385500 s / 002% 0.093815600 s / 002% 0.475181600 s / 002% 1.033701100 s / 002%
hutool 0.053982900 s / 009% 0.548897800 s / 010% 2.750257900 s / 010% 5.897726100 s / 010%
spring 0.016976500 s / 003% 0.169937400 s / 003% 0.855448400 s / 003% 1.706155900 s / 003%
mapstruct 0.005290900 s / 001% 0.052851800 s / 001% 0.268651300 s / 001% 0.619404300 s / 001%
total 0.583135900 s 5.607831900 s 27.946743000 s 57.141483600 s

In the above tests, there was a different variable, that is, the same source object was not used to test different tool conversions, but this difference did not affect the performance comparison of the different frameworks much, based on the above run results

  • Mapstruct, Cglib and Spring performed best
  • Apache fared worst

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

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

  • QQ: a gray /3302797840
  • Wechat official account: One Grey Blog