首先说一下遇到的问题:准备用map2bean工具,选择了BeanUtils中的BeanUtils.populate(bean, map);方法。
下面是我的bean。个别字段不是驼峰格式,因为MAP是别人提供的,其实把Map中属性名称改成驼峰样子我遇到的问题就消失了map2bean正常工作,但是我得知道他为啥都是大写字母开头最后一个变量咋正常呢。
public class ExtendedPropertie implements Serializable{
private static final long serialVersionUID = 1625175373011702860L;
private String PlateStoreFlag;
private String copilot_safebelt;
private String department;
private String ICE2MQ_Receive;
public String getPlateStoreFlag() {
return PlateStoreFlag;
}
public void setPlateStoreFlag(String plateStoreFlag) {
PlateStoreFlag = plateStoreFlag;
}
public String getCopilot_safebelt() {
return copilot_safebelt;
}
public void setCopilot_safebelt(String copilot_safebelt) {
this.copilot_safebelt = copilot_safebelt;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
public String getICE2MQ_Receive() {
return ICE2MQ_Receive;
}
public void setICE2MQ_Receive(String iCE2MQ_Receive) {
ICE2MQ_Receive = iCE2MQ_Receive;
}
}
遇到的问题map2bean后PlateStoreFlag为null,其他正常。打断点测试。
BeanUtils.populate(bean, map);->org.apache.commons.beanutils.populate--->org.apache.commons.beanutils.BeanUtilsBean.getInstance().populate(bean, properties);
--->org.apache.commons.beanutils.BeanUtilsBean.setProperty(bean, name, entry.getValue());
此方法中包含这样一行
descriptor =
getPropertyUtils().getPropertyDescriptor(target, name);
descriptor 中有方法的描述,类变量的描述等,接着追根溯源。
接着找下去看到这个了
final BeanIntrospectionData data = getIntrospectionData(bean.getClass());
BeanIntrospectionData 类中有这么两个成员变量,一个是目标类的成员变量描述,一个是可写的方法就是set方法
/** An array with property descriptors for the managed bean class. */
private final PropertyDescriptor[] descriptors;
/** A map for remembering the write method names for properties. */
private final Map<String, String> writeMethodNames;
接着看代码:
private BeanIntrospectionData getIntrospectionData(final Class<?> beanClass) {
if (beanClass == null) {
throw new IllegalArgumentException("No bean class specified");
}
// Look up any cached information for this bean class
BeanIntrospectionData data = descriptorsCache.get(beanClass);
if (data == null) {
data = fetchIntrospectionData(beanClass);
descriptorsCache.put(beanClass, data);
}
return data;
}
可以看出来先从缓存中取BeanIntrospectionData 如果没有 fetchIntrospectionData(beanClass)
接着往下走,
private BeanIntrospectionData fetchIntrospectionData(final Class<?> beanClass) {
final DefaultIntrospectionContext ictx = new DefaultIntrospectionContext(beanClass);
// /** The list with BeanIntrospector objects. */
// private final List<BeanIntrospector> introspectors;
for (final BeanIntrospector bi : introspectors) {
try {
bi.introspect(ictx);
} catch (final IntrospectionException iex) {
log.error("Exception during introspection", iex);
}
}
return new BeanIntrospectionData(ictx.getPropertyDescriptors());
}
打断点监测发现问题发生在这一行 bi.introspect(ictx);这一行之后问题就出现了。查看introspect这个方法在interface BeanIntrospector中有三个实现,
进入发现是这个类class DefaultBeanIntrospector implements BeanIntrospector
此类中的方法introspect中有这么几行
BeanInfo beanInfo = null;
beanInfo = Introspector.getBeanInfo(icontext.getTargetClass());
PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
BeanInfo ,Introspector是
java.beans中的类
在eclipse中调试jdk的源码,(关联源码的帮助:https://jingyan.baidu.com/album/915fc41483a39551394b20bb.html?picindex=1)接着找原因
Introspector类中的方法:public static BeanInfo getBeanInfo(Class<?> beanClass)
进入这个方法了beanInfo = new Introspector(beanClass, null, USE_ALL_BEANINFO).getBeanInfo();
先看Introspector实例化的过程,先找单参数构造器 beanInfo,然后是父类beaninfo,见一下方法
superBeanInfo = getBeanInfo(superClass, stopClass, newFlags);
继续深入方法
bi = getBeanInfo(beanClass);
经过这个方法bi中终于有东西了,但是那是父类object的bean描述,至此红色代码的实例化结束
继续往下走该是自定的bean中的东西了吧,进入红色代码的getBeanInfo方法。
private BeanInfo getBeanInfo() throws IntrospectionException {
// the evaluation order here is import, as we evaluate the
// event sets and locate PropertyChangeListeners before we
// look for properties.
BeanDescriptor bd = getTargetBeanDescriptor();
MethodDescriptor mds[] = getTargetMethodInfo();
EventSetDescriptor esds[] = getTargetEventInfo();
PropertyDescriptor pds[] = getTargetPropertyInfo();
int defaultEvent = getTargetDefaultEventIndex();
int defaultProperty = getTargetDefaultPropertyIndex();
return new GenericBeanInfo(bd, esds, defaultEvent, pds,
defaultProperty, mds, explicitBeanInfo);
}
都是干货,终于找到源头了。进getTargetPropertyInfo,我给代码加注释,看注释吧
private PropertyDescriptor[] getTargetPropertyInfo() {
// Check if the bean has its own BeanInfo that will provide
// explicit information.
PropertyDescriptor[] explicitProperties = null;
if (explicitBeanInfo != null) { //explicitBeanInfo 是null
explicitProperties = getPropertyDescriptors(this.explicitBeanInfo);
}
if (explicitProperties == null && superBeanInfo != null) {
// We have no explicit BeanInfo properties. Check with our parent.
addPropertyDescriptors(getPropertyDescriptors(this.superBeanInfo)); //把父类的beaninfo中的PropertyDescriptors信息加入进来
}
for (int i = 0; i < additionalBeanInfo.length; i++) {
addPropertyDescriptors(additionalBeanInfo[i].getPropertyDescriptors());
}
//三大类 beaninfo处理完了。 private BeanInfo explicitBeanInfo;
// private BeanInfo superBeanInfo;
// private BeanInfo additionalBeanInfo[];
if (explicitProperties != null) {//试图系通过部分 参数的构造方法找到properties
// Add the explicit BeanInfo data to our results.
addPropertyDescriptors(explicitProperties);
} else {//没办法了,出绝招了
// Apply some reflection to the current class.
//拿到pulic 方法
// First get an array of all the public methods at this level
Method methodList[] = getPublicDeclaredMethods(beanClass);
// Now analyze each method.
for (int i = 0; i < methodList.length; i++) {
Method method = methodList[i];
if (method == null) {
continue;
}
// skip static methods.
int mods = method.getModifiers();
if (Modifier.isStatic(mods)) {//去掉静态方法
continue;
}
String name = method.getName();
Class argTypes[] = method.getParameterTypes();
Class resultType = method.getReturnType();
int argCount = argTypes.length;
PropertyDescriptor pd = null;
//去掉长度小于三的,get,set加方法名大于3,去掉is开头的方法
if (name.length() <= 3 && !name.startsWith(IS_PREFIX)) {
// Optimization. Don't bother with invalid propertyNames.
continue;
}
try {
//参数为零 get方法
if (argCount == 0) {
if (name.startsWith(GET_PREFIX)) {//get开头的方法
// Simple getter
pd = new PropertyDescriptor(this.beanClass, name.substring(3), method, null); //最关键的一步了,mark一下
} else if (resultType == boolean.class && name.startsWith(IS_PREFIX)) {
// Boolean getter
pd = new PropertyDescriptor(this.beanClass, name.substring(2), method, null);
}
} else if (argCount == 1) {
if (int.class.equals(argTypes[0]) && name.startsWith(GET_PREFIX)) {
pd = new IndexedPropertyDescriptor(this.beanClass, name.substring(3), null, null, method, null);
} else if (void.class.equals(resultType) && name.startsWith(SET_PREFIX)) {
// Simple setter
pd = new PropertyDescriptor(this.beanClass, name.substring(3), null, method);
if (throwsException(method, PropertyVetoException.class)) {
pd.setConstrained(true);
}
}
} else if (argCount == 2) {
if (void.class.equals(resultType) && int.class.equals(argTypes[0]) && name.startsWith(SET_PREFIX)) {
pd = new IndexedPropertyDescriptor(this.beanClass, name.substring(3), null, null, null, method);
if (throwsException(method, PropertyVetoException.class)) {
pd.setConstrained(true);
}
}
}
} catch (IntrospectionException ex) {
// This happens if a PropertyDescriptor or IndexedPropertyDescriptor
// constructor fins that the method violates details of the deisgn
// pattern, e.g. by having an empty name, or a getter returning
// void , or whatever.
pd = null;
}
if (pd != null) {
// If this class or one of its base classes is a PropertyChange
// source, then we assume that any properties we discover are "bound".
if (propertyChangeSource) {
pd.setBound(true);
}
addPropertyDescriptor(pd);
}
}
}
processPropertyDescriptors();
// Allocate and populate the result array.
PropertyDescriptor result[] = new PropertyDescriptor[properties.size()];
result = (PropertyDescriptor[])properties.values().toArray(result);
// Set the default index.
if (defaultPropertyName != null) {
for (int i = 0; i < result.length; i++) {
if (defaultPropertyName.equals(result[i].getName())) {
defaultPropertyIndex = i;
}
}
}
return result;
}
这行 pd = new PropertyDescriptor(this.beanClass, name.substring(3), method, null); //最关键的一步了,mark一下,继续深入
//传了三个参数,class,get方法去掉get,get Method 对象,null
PropertyDescriptor(Class<?> bean, String base, Method read, Method write) throws IntrospectionException {
if (bean == null) {
throw new IntrospectionException("Target Bean class is null");
}
setClass0(bean);
setName(Introspector.decapitalize(base));
setReadMethod(read);
setWriteMethod(write);
this.baseName = base;
}
setName(Introspector.decapitalize(base));见真相
/**
* Utility method to take a string and convert it to normal Java variable
* name capitalization. This normally means converting the first
* character from upper case to lower case, but in the (unusual) special
* case when there is more than one character and both the first and
* second characters are upper case, we leave it alone.
* <p>
* Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays
* as "URL".
*
* @param name The string to be decapitalized.
* @return The decapitalized version of the string.
*/
public static String decapitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
Character.isUpperCase(name.charAt(0))){
return name;
}
char chars[] = name.toCharArray();
chars[0] = Character.toLowerCase(chars[0]);
return new String(chars);
}
翻译下这个方法的注释。从公用的方法截取字段作为变量名。截取的字段第一个大写就把他变成小写,但是特殊的字段不止第一个字母大写,第二个也大写,所以我们就不管他了。
这也解释了PlateStoreFlag不正常,ICE2MQ_Receive正常的原因的。
至于为啥不通过Field[] fields = class1.getFields();获得feild,因为变量是私有变量。
我来解释下整个过程。
首先根据Map中的key和value还有要转的Bean,
去获取PropertyDescriptor,先根据bean拿到BeanIntrospectionData,要获得BeanIntrospectionData 先拿到BeanInfo,拿到BeanInfo的时候已经把大小写转化做完了,保存在PropertyDescriptor。然后根据name(map中的key)去data.getDescriptor(name)获取,当然获取不到的,因为data中的name 肯定是小写开头的。而map中给的是大写开头的。正常情况下拿到PropertyDescriptor,然后根据PropertyDescriptor获得数据类型然后 将Map中的value转换成合适的类型。然后进入往变量写数据的流程,要写数据就要拿到set方法。然后再根据bean获得PropertyDescriptor ,因为第一次有缓存所以去缓存中取。然后获得set方法最后执行 invokeMethod(writeMethod, bean, values);写入数据
下面是我的bean。个别字段不是驼峰格式,因为MAP是别人提供的,其实把Map中属性名称改成驼峰样子我遇到的问题就消失了map2bean正常工作,但是我得知道他为啥都是大写字母开头最后一个变量咋正常呢。
public class ExtendedPropertie implements Serializable{
private static final long serialVersionUID = 1625175373011702860L;
private String PlateStoreFlag;
private String copilot_safebelt;
private String department;
private String ICE2MQ_Receive;
public String getPlateStoreFlag() {
return PlateStoreFlag;
}
public void setPlateStoreFlag(String plateStoreFlag) {
PlateStoreFlag = plateStoreFlag;
}
public String getCopilot_safebelt() {
return copilot_safebelt;
}
public void setCopilot_safebelt(String copilot_safebelt) {
this.copilot_safebelt = copilot_safebelt;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
public String getICE2MQ_Receive() {
return ICE2MQ_Receive;
}
public void setICE2MQ_Receive(String iCE2MQ_Receive) {
ICE2MQ_Receive = iCE2MQ_Receive;
}
}
遇到的问题map2bean后PlateStoreFlag为null,其他正常。打断点测试。
BeanUtils.populate(bean, map);->org.apache.commons.beanutils.populate--->org.apache.commons.beanutils.BeanUtilsBean.getInstance().populate(bean, properties);
--->org.apache.commons.beanutils.BeanUtilsBean.setProperty(bean, name, entry.getValue());
此方法中包含这样一行
descriptor =
getPropertyUtils().getPropertyDescriptor(target, name);
descriptor 中有方法的描述,类变量的描述等,接着追根溯源。
接着找下去看到这个了
final BeanIntrospectionData data = getIntrospectionData(bean.getClass());
BeanIntrospectionData 类中有这么两个成员变量,一个是目标类的成员变量描述,一个是可写的方法就是set方法
/** An array with property descriptors for the managed bean class. */
private final PropertyDescriptor[] descriptors;
/** A map for remembering the write method names for properties. */
private final Map<String, String> writeMethodNames;
接着看代码:
private BeanIntrospectionData getIntrospectionData(final Class<?> beanClass) {
if (beanClass == null) {
throw new IllegalArgumentException("No bean class specified");
}
// Look up any cached information for this bean class
BeanIntrospectionData data = descriptorsCache.get(beanClass);
if (data == null) {
data = fetchIntrospectionData(beanClass);
descriptorsCache.put(beanClass, data);
}
return data;
}
可以看出来先从缓存中取BeanIntrospectionData 如果没有 fetchIntrospectionData(beanClass)
接着往下走,
private BeanIntrospectionData fetchIntrospectionData(final Class<?> beanClass) {
final DefaultIntrospectionContext ictx = new DefaultIntrospectionContext(beanClass);
// /** The list with BeanIntrospector objects. */
// private final List<BeanIntrospector> introspectors;
for (final BeanIntrospector bi : introspectors) {
try {
bi.introspect(ictx);
} catch (final IntrospectionException iex) {
log.error("Exception during introspection", iex);
}
}
return new BeanIntrospectionData(ictx.getPropertyDescriptors());
}
打断点监测发现问题发生在这一行 bi.introspect(ictx);这一行之后问题就出现了。查看introspect这个方法在interface BeanIntrospector中有三个实现,
进入发现是这个类class DefaultBeanIntrospector implements BeanIntrospector
此类中的方法introspect中有这么几行
BeanInfo beanInfo = null;
beanInfo = Introspector.getBeanInfo(icontext.getTargetClass());
PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
BeanInfo ,Introspector是
java.beans中的类
在eclipse中调试jdk的源码,(关联源码的帮助:https://jingyan.baidu.com/album/915fc41483a39551394b20bb.html?picindex=1)接着找原因
Introspector类中的方法:public static BeanInfo getBeanInfo(Class<?> beanClass)
进入这个方法了beanInfo = new Introspector(beanClass, null, USE_ALL_BEANINFO).getBeanInfo();
先看Introspector实例化的过程,先找单参数构造器 beanInfo,然后是父类beaninfo,见一下方法
superBeanInfo = getBeanInfo(superClass, stopClass, newFlags);
继续深入方法
bi = getBeanInfo(beanClass);
经过这个方法bi中终于有东西了,但是那是父类object的bean描述,至此红色代码的实例化结束
继续往下走该是自定的bean中的东西了吧,进入红色代码的getBeanInfo方法。
private BeanInfo getBeanInfo() throws IntrospectionException {
// the evaluation order here is import, as we evaluate the
// event sets and locate PropertyChangeListeners before we
// look for properties.
BeanDescriptor bd = getTargetBeanDescriptor();
MethodDescriptor mds[] = getTargetMethodInfo();
EventSetDescriptor esds[] = getTargetEventInfo();
PropertyDescriptor pds[] = getTargetPropertyInfo();
int defaultEvent = getTargetDefaultEventIndex();
int defaultProperty = getTargetDefaultPropertyIndex();
return new GenericBeanInfo(bd, esds, defaultEvent, pds,
defaultProperty, mds, explicitBeanInfo);
}
都是干货,终于找到源头了。进getTargetPropertyInfo,我给代码加注释,看注释吧
private PropertyDescriptor[] getTargetPropertyInfo() {
// Check if the bean has its own BeanInfo that will provide
// explicit information.
PropertyDescriptor[] explicitProperties = null;
if (explicitBeanInfo != null) { //explicitBeanInfo 是null
explicitProperties = getPropertyDescriptors(this.explicitBeanInfo);
}
if (explicitProperties == null && superBeanInfo != null) {
// We have no explicit BeanInfo properties. Check with our parent.
addPropertyDescriptors(getPropertyDescriptors(this.superBeanInfo)); //把父类的beaninfo中的PropertyDescriptors信息加入进来
}
for (int i = 0; i < additionalBeanInfo.length; i++) {
addPropertyDescriptors(additionalBeanInfo[i].getPropertyDescriptors());
}
//三大类 beaninfo处理完了。 private BeanInfo explicitBeanInfo;
// private BeanInfo superBeanInfo;
// private BeanInfo additionalBeanInfo[];
if (explicitProperties != null) {//试图系通过部分 参数的构造方法找到properties
// Add the explicit BeanInfo data to our results.
addPropertyDescriptors(explicitProperties);
} else {//没办法了,出绝招了
// Apply some reflection to the current class.
//拿到pulic 方法
// First get an array of all the public methods at this level
Method methodList[] = getPublicDeclaredMethods(beanClass);
// Now analyze each method.
for (int i = 0; i < methodList.length; i++) {
Method method = methodList[i];
if (method == null) {
continue;
}
// skip static methods.
int mods = method.getModifiers();
if (Modifier.isStatic(mods)) {//去掉静态方法
continue;
}
String name = method.getName();
Class argTypes[] = method.getParameterTypes();
Class resultType = method.getReturnType();
int argCount = argTypes.length;
PropertyDescriptor pd = null;
//去掉长度小于三的,get,set加方法名大于3,去掉is开头的方法
if (name.length() <= 3 && !name.startsWith(IS_PREFIX)) {
// Optimization. Don't bother with invalid propertyNames.
continue;
}
try {
//参数为零 get方法
if (argCount == 0) {
if (name.startsWith(GET_PREFIX)) {//get开头的方法
// Simple getter
pd = new PropertyDescriptor(this.beanClass, name.substring(3), method, null); //最关键的一步了,mark一下
} else if (resultType == boolean.class && name.startsWith(IS_PREFIX)) {
// Boolean getter
pd = new PropertyDescriptor(this.beanClass, name.substring(2), method, null);
}
} else if (argCount == 1) {
if (int.class.equals(argTypes[0]) && name.startsWith(GET_PREFIX)) {
pd = new IndexedPropertyDescriptor(this.beanClass, name.substring(3), null, null, method, null);
} else if (void.class.equals(resultType) && name.startsWith(SET_PREFIX)) {
// Simple setter
pd = new PropertyDescriptor(this.beanClass, name.substring(3), null, method);
if (throwsException(method, PropertyVetoException.class)) {
pd.setConstrained(true);
}
}
} else if (argCount == 2) {
if (void.class.equals(resultType) && int.class.equals(argTypes[0]) && name.startsWith(SET_PREFIX)) {
pd = new IndexedPropertyDescriptor(this.beanClass, name.substring(3), null, null, null, method);
if (throwsException(method, PropertyVetoException.class)) {
pd.setConstrained(true);
}
}
}
} catch (IntrospectionException ex) {
// This happens if a PropertyDescriptor or IndexedPropertyDescriptor
// constructor fins that the method violates details of the deisgn
// pattern, e.g. by having an empty name, or a getter returning
// void , or whatever.
pd = null;
}
if (pd != null) {
// If this class or one of its base classes is a PropertyChange
// source, then we assume that any properties we discover are "bound".
if (propertyChangeSource) {
pd.setBound(true);
}
addPropertyDescriptor(pd);
}
}
}
processPropertyDescriptors();
// Allocate and populate the result array.
PropertyDescriptor result[] = new PropertyDescriptor[properties.size()];
result = (PropertyDescriptor[])properties.values().toArray(result);
// Set the default index.
if (defaultPropertyName != null) {
for (int i = 0; i < result.length; i++) {
if (defaultPropertyName.equals(result[i].getName())) {
defaultPropertyIndex = i;
}
}
}
return result;
}
这行 pd = new PropertyDescriptor(this.beanClass, name.substring(3), method, null); //最关键的一步了,mark一下,继续深入
//传了三个参数,class,get方法去掉get,get Method 对象,null
PropertyDescriptor(Class<?> bean, String base, Method read, Method write) throws IntrospectionException {
if (bean == null) {
throw new IntrospectionException("Target Bean class is null");
}
setClass0(bean);
setName(Introspector.decapitalize(base));
setReadMethod(read);
setWriteMethod(write);
this.baseName = base;
}
setName(Introspector.decapitalize(base));见真相
/**
* Utility method to take a string and convert it to normal Java variable
* name capitalization. This normally means converting the first
* character from upper case to lower case, but in the (unusual) special
* case when there is more than one character and both the first and
* second characters are upper case, we leave it alone.
* <p>
* Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays
* as "URL".
*
* @param name The string to be decapitalized.
* @return The decapitalized version of the string.
*/
public static String decapitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
Character.isUpperCase(name.charAt(0))){
return name;
}
char chars[] = name.toCharArray();
chars[0] = Character.toLowerCase(chars[0]);
return new String(chars);
}
翻译下这个方法的注释。从公用的方法截取字段作为变量名。截取的字段第一个大写就把他变成小写,但是特殊的字段不止第一个字母大写,第二个也大写,所以我们就不管他了。
这也解释了PlateStoreFlag不正常,ICE2MQ_Receive正常的原因的。
至于为啥不通过Field[] fields = class1.getFields();获得feild,因为变量是私有变量。
我来解释下整个过程。
首先根据Map中的key和value还有要转的Bean,
去获取PropertyDescriptor,先根据bean拿到BeanIntrospectionData,要获得BeanIntrospectionData 先拿到BeanInfo,拿到BeanInfo的时候已经把大小写转化做完了,保存在PropertyDescriptor。然后根据name(map中的key)去data.getDescriptor(name)获取,当然获取不到的,因为data中的name 肯定是小写开头的。而map中给的是大写开头的。正常情况下拿到PropertyDescriptor,然后根据PropertyDescriptor获得数据类型然后 将Map中的value转换成合适的类型。然后进入往变量写数据的流程,要写数据就要拿到set方法。然后再根据bean获得PropertyDescriptor ,因为第一次有缓存所以去缓存中取。然后获得set方法最后执行 invokeMethod(writeMethod, bean, values);写入数据