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