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#copyProperties
The 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 blog: https://blog.hhui.top
A gray personal blog, which records all blog posts in study and work. Welcome to visit
- Project source code:https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/004-bean-util
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