[讀懂]深入淺出Beanutils的不足與自定義
案由
某案的java bean綜合各種因素,變數命名與一般命名規則不一致,導致Beanutils操作異常
一般Java命名規範
- 變數名
- 首字母小寫,多個單詞组成時,除首個單詞,其他單詞首字母都要大寫
- password, userName
- 常數名
- 全部大寫,多個單詞,用'_'分隔
- CACHE_EXPIRED_TIME
這個案子的Table Column,是使用全部大寫+'_'的模式;DB Model的變數名規定與Table Column一致
其實一般Java命名規範也只是個不成文規定,實際上編譯也是沒有什麼問題,大部分執行也是沒有異常,直到有一天,Table Column出現了特例...
環境
建置一個Maven專案,pom.xml:
<dependencies>
<!-- https://mvnrepository.com/artifact/commons-beanutils/commons-beanutils -->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
情境
以下準備了一些常見的java bean,與業主的java bean
- Object1:一般Java命名規範
- Object2:變形1
- Object3:變形2 業主使用格式
- Object4:變形3 業主使用格式
/** 一般Java命名規範 */
public class Object1 {
private int abc;
private int def;
private int ghiJkl;
public int getAbc() {
return abc;
}
public void setAbc(int abc) {
this.abc = abc;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
public int getGhiJkl() {
return ghiJkl;
}
public void setGhiJkl(int ghiJkl) {
this.ghiJkl = ghiJkl;
}
}
/** 變形1 */
public class Object2 {
private int a0_abc;
private int a1_def;
private int a2_ghi_jkl;
public int getA0_abc() {
return a0_abc;
}
public void setA0_abc(int a0_abc) {
this.a0_abc = a0_abc;
}
public int getA1_def() {
return a1_def;
}
public void setA1_def(int a1_def) {
this.a1_def = a1_def;
}
public int getA2_ghi_jkl() {
return a2_ghi_jkl;
}
public void setA2_ghi_jkl(int a2_ghi_jkl) {
this.a2_ghi_jkl = a2_ghi_jkl;
}
}
/** 變形2 */
public class Object3 {
private int A0_ABC;
private int A1_DEF;
private int A2_GHI_JKL;
public int getA0_ABC() {
return A0_ABC;
}
public void setA0_ABC(int a0_ABC) {
A0_ABC = a0_ABC;
}
public int getA1_DEF() {
return A1_DEF;
}
public void setA1_DEF(int a1_DEF) {
A1_DEF = a1_DEF;
}
public int getA2_GHI_JKL() {
return A2_GHI_JKL;
}
public void setA2_GHI_JKL(int a2_GHI_JKL) {
A2_GHI_JKL = a2_GHI_JKL;
}
}
/** 變形3 */
public class Object4 {
private int ABC;
private int DEF;
private int GHI_JKL;
public int getABC() {
return ABC;
}
public void setABC(int aBC) {
ABC = aBC;
}
public int getDEF() {
return DEF;
}
public void setDEF(int dEF) {
DEF = dEF;
}
public int getGHI_JKL() {
return GHI_JKL;
}
public void setGHI_JKL(int gHI_JKL) {
GHI_JKL = gHI_JKL;
}
}
接著,來模擬一下發生問題的地方:使用beanutils讀取bean屬性
public class Test1 {
private static final Logger logger = Logger.getLogger(Test1.class);
public static void main(String[] args) {
try {
test1();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
test2();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
test3();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
test4();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private static void test1() throws Exception {
Object1 obj = new Object1();
obj.setAbc(1);
obj.setDef(2);
logger.debug(BeanUtils.getProperty(obj, "abc"));
logger.debug(BeanUtils.getProperty(obj, "def"));
logger.debug(BeanUtils.getProperty(obj, "ghiJkl"));
}
private static void test2() throws Exception {
Object2 obj = new Object2();
obj.setA0_abc(1);
obj.setA1_def(2);
logger.debug(BeanUtils.getProperty(obj, "a0_abc"));
logger.debug(BeanUtils.getProperty(obj, "a1_def"));
logger.debug(BeanUtils.getProperty(obj, "a2_ghi_jkl"));
}
private static void test3() throws Exception {
Object3 obj = new Object3();
obj.setA0_ABC(1);
obj.setA1_DEF(2);
logger.debug(BeanUtils.getProperty(obj, "A0_ABC"));
logger.debug(BeanUtils.getProperty(obj, "A1_DEF"));
}
private static void test4() throws Exception {
Object4 obj = new Object4();
obj.setABC(1);
obj.setDEF(2);
logger.debug(BeanUtils.getProperty(obj, "ABC"));
logger.debug(BeanUtils.getProperty(obj, "DEF"));
logger.debug(BeanUtils.getProperty(obj, "GHI_JKL"));
}
}
可以猜一下發生問題的地方:
java.lang.NoSuchMethodException: Unknown property 'A0_ABC' on class 'class test.Object3'
at org.apache.commons.beanutils.PropertyUtilsBean.getSimpleProperty(PropertyUtilsBean.java:1270)
at org.apache.commons.beanutils.PropertyUtilsBean.getNestedProperty(PropertyUtilsBean.java:809)
at org.apache.commons.beanutils.BeanUtilsBean.getNestedProperty(BeanUtilsBean.java:711)
at org.apache.commons.beanutils.BeanUtilsBean.getProperty(BeanUtilsBean.java:737)
at org.apache.commons.beanutils.BeanUtils.getProperty(BeanUtils.java:380)
at test.Test1.test3(Test1.java:63)
at test.Test1.main(Test1.java:23)
這邊尷尬的地方,就是雖然 `變形1` `變形2` `變形3` 都不是一般Java命名規範,但只有 `變形2` 會發生異常。目前暫時歸納出,第一個字元為大寫,第二個字元為數字的屬性命名會有問題。未來有機會再來追究為何只有 `變形2` 會發生異常。
分析過程
先來看一下拋錯位置
public class PropertyUtilsBean {
//...
public Object getSimpleProperty(final Object bean, final String name)
throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
if (bean == null) {
throw new IllegalArgumentException("No bean specified");
}
if (name == null) {
throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
}
// Validate the syntax of the property name
if (resolver.hasNested(name)) {
throw new IllegalArgumentException("Nested property names are not allowed: Property '" + name
+ "' on bean class '" + bean.getClass() + "'");
} else if (resolver.isIndexed(name)) {
throw new IllegalArgumentException("Indexed property names are not allowed: Property '" + name
+ "' on bean class '" + bean.getClass() + "'");
} else if (resolver.isMapped(name)) {
throw new IllegalArgumentException("Mapped property names are not allowed: Property '" + name
+ "' on bean class '" + bean.getClass() + "'");
}
// Handle DynaBean instances specially
if (bean instanceof DynaBean) {
final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name);
if (descriptor == null) {
throw new NoSuchMethodException(
"Unknown property '" + name + "' on dynaclass '" + ((DynaBean) bean).getDynaClass() + "'");
}
return (((DynaBean) bean).get(name));
}
// Retrieve the property getter method for the specified property
final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
if (descriptor == null) {
throw new NoSuchMethodException("Unknown property '" + name + "' on class '" + bean.getClass() + "'");
//拋錯位置
}
final Method readMethod = getReadMethod(bean.getClass(), descriptor);
if (readMethod == null) {
throw new NoSuchMethodException(
"Property '" + name + "' has no getter method in class '" + bean.getClass() + "'");
}
// Call the property getter and return the value
final Object value = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
return (value);
}
public PropertyDescriptor getPropertyDescriptor(Object bean,
String name)
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodException {
if (bean == null) {
throw new IllegalArgumentException("No bean specified");
}
if (name == null) {
throw new IllegalArgumentException("No name specified for bean class '" +
bean.getClass() + "'");
}
// Resolve nested references
while (resolver.hasNested(name)) {
final String next = resolver.next(name);
final Object nestedBean = getProperty(bean, next);
if (nestedBean == null) {
throw new NestedNullException
("Null property value for '" + next +
"' on bean class '" + bean.getClass() + "'");
}
bean = nestedBean;
name = resolver.remove(name);
}
// Remove any subscript from the final name value
name = resolver.getProperty(name);
// Look up and return this property from our cache
// creating and adding it to the cache if not found.
if (name == null) {
return (null);
}
final BeanIntrospectionData data = getIntrospectionData(bean.getClass());
PropertyDescriptor result = data.getDescriptor(name);
log.debug("bean:"+bean.getClass()+" name:"+name+" result:"+result);
if (result != null) {
return result;
}
FastHashMap mappedDescriptors =
getMappedPropertyDescriptors(bean);
if (mappedDescriptors == null) {
mappedDescriptors = new FastHashMap();
mappedDescriptors.setFast(true);
mappedDescriptorsCache.put(bean.getClass(), mappedDescriptors);
}
result = (PropertyDescriptor) mappedDescriptors.get(name);
if (result == null) {
// not found, try to create it
try {
result = new MappedPropertyDescriptor(name, bean.getClass());
} catch (final IntrospectionException ie) {
/* Swallow IntrospectionException
* TODO: Why?
*/
}
if (result != null) {
mappedDescriptors.put(name, result);
}
}
return result;
}
//...
}
為了分析錯誤的原因並且找出解決方案,可以試圖在原始碼加上log。
Hint:透過Maven可以取得原始碼,並複製一份放在自己的專案內,可以覆蓋掉,並依需求修改或增加log
public PropertyDescriptor getPropertyDescriptor(Object bean, String name)
throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
//...
final BeanIntrospectionData data = getIntrospectionData(bean.getClass());
PropertyDescriptor result = data.getDescriptor(name);
log.debug("bean class:" + bean.getClass().getName() + " name:" + name + " result:" + result);
if (result != null) {
return result;
}
//...
}
2022-06-15 17:25:17 DEBUG PropertyUtils:920 - bean class:test.Object1 name:abc
2022-06-15 17:25:17 DEBUG PropertyUtils:922 - result HIT
2022-06-15 17:25:17 DEBUG StringConverter:140 - Converting 'Integer' value '1' to type 'String'
2022-06-15 17:25:17 DEBUG Test1:41 - 1
2022-06-15 17:25:17 DEBUG PropertyUtils:920 - bean class:test.Object1 name:def
2022-06-15 17:25:17 DEBUG PropertyUtils:922 - result HIT
2022-06-15 17:25:17 DEBUG StringConverter:140 - Converting 'Integer' value '2' to type 'String'
2022-06-15 17:25:17 DEBUG Test1:42 - 2
2022-06-15 17:25:17 DEBUG PropertyUtils:920 - bean class:test.Object1 name:ghiJkl
2022-06-15 17:25:17 DEBUG PropertyUtils:922 - result HIT
2022-06-15 17:25:17 DEBUG StringConverter:140 - Converting 'Integer' value '0' to type 'String'
2022-06-15 17:25:17 DEBUG Test1:43 - 0
2022-06-15 17:25:17 DEBUG PropertyUtils:920 - bean class:test.Object2 name:a0_abc
2022-06-15 17:25:17 DEBUG PropertyUtils:922 - result HIT
2022-06-15 17:25:17 DEBUG StringConverter:140 - Converting 'Integer' value '1' to type 'String'
2022-06-15 17:25:17 DEBUG Test1:52 - 1
2022-06-15 17:25:17 DEBUG PropertyUtils:920 - bean class:test.Object2 name:a1_def
2022-06-15 17:25:17 DEBUG PropertyUtils:922 - result HIT
2022-06-15 17:25:17 DEBUG StringConverter:140 - Converting 'Integer' value '2' to type 'String'
2022-06-15 17:25:17 DEBUG Test1:53 - 2
2022-06-15 17:25:17 DEBUG PropertyUtils:920 - bean class:test.Object2 name:a2_ghi_jkl
2022-06-15 17:25:17 DEBUG PropertyUtils:922 - result HIT
2022-06-15 17:25:17 DEBUG StringConverter:140 - Converting 'Integer' value '0' to type 'String'
2022-06-15 17:25:17 DEBUG Test1:54 - 0
2022-06-15 17:25:17 DEBUG PropertyUtils:920 - bean class:test.Object3 name:A0_ABC
2022-06-15 17:25:17 WARN PropertyUtils:925 - result LOSE
java.lang.NoSuchMethodException: Unknown property 'A0_ABC' on class 'class test.Object3'
at org.apache.commons.beanutils.PropertyUtilsBean.getSimpleProperty(PropertyUtilsBean.java:1227)
at org.apache.commons.beanutils.PropertyUtilsBean.getNestedProperty(PropertyUtilsBean.java:774)
at org.apache.commons.beanutils.BeanUtilsBean.getNestedProperty(BeanUtilsBean.java:711)
at org.apache.commons.beanutils.BeanUtilsBean.getProperty(BeanUtilsBean.java:737)
at org.apache.commons.beanutils.BeanUtils.getProperty(BeanUtils.java:380)
at test.Test1.test3(Test1.java:63)
at test.Test1.main(Test1.java:23)
2022-06-15 17:25:17 DEBUG PropertyUtils:920 - bean class:test.Object4 name:ABC
2022-06-15 17:25:17 DEBUG PropertyUtils:922 - result HIT
2022-06-15 17:25:17 DEBUG StringConverter:140 - Converting 'Integer' value '1' to type 'String'
2022-06-15 17:25:17 DEBUG Test1:73 - 1
2022-06-15 17:25:17 DEBUG PropertyUtils:920 - bean class:test.Object4 name:DEF
2022-06-15 17:25:17 DEBUG PropertyUtils:922 - result HIT
2022-06-15 17:25:17 DEBUG StringConverter:140 - Converting 'Integer' value '2' to type 'String'
2022-06-15 17:25:17 DEBUG Test1:74 - 2
2022-06-15 17:25:17 DEBUG PropertyUtils:920 - bean class:test.Object4 name:GHI_JKL
2022-06-15 17:25:17 DEBUG PropertyUtils:922 - result HIT
2022-06-15 17:25:17 DEBUG StringConverter:140 - Converting 'Integer' value '0' to type 'String'
2022-06-15 17:25:17 DEBUG Test1:75 - 0
可以觀測到,在取得PropertyDescriptor這個物件時發生問題。
PropertyDescriptor主要用於取得屬性的getter與setter,一般來說,當輸入不存在的屬性名時,就會報錯 `Unknown property '${name}' on class '${bean.class}'`
本來是很合理,但 `Object3` 試圖取得已知屬性值時,卻報此異常,這邊目標就訂為嘗試修正可以正常回傳PropertyDescriptor
class BeanIntrospectionData {
//...
public PropertyDescriptor getDescriptor(final String name) {
for (final PropertyDescriptor pd : getDescriptors()) {
if (name.equals(pd.getName())) {
//name一致才會回傳
return pd;
}
}
return null;
}
//...
}
name一致才會回傳,這邊試圖展開 `getDescriptors()` 看一下預設的內容
public PropertyDescriptor getPropertyDescriptor(Object bean, String name)
throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
//...
final BeanIntrospectionData data = getIntrospectionData(bean.getClass());
log.debug("bean class:" + bean.getClass().getName() + " name:" + name);
for (PropertyDescriptor pd : data.getDescriptors()) {
log.debug("pd.getName():" + pd.getName());
}
PropertyDescriptor result = data.getDescriptor(name);
if (result != null) {
log.debug("result HIT");
return result;
}
log.warn("result LOSE");
//...
}
2022-06-15 17:45:10 DEBUG PropertyUtils:916 - bean class:test.Object1 name:abc
2022-06-15 17:45:10 DEBUG PropertyUtils:918 - pd.getName():abc
2022-06-15 17:45:10 DEBUG PropertyUtils:918 - pd.getName():def
2022-06-15 17:45:10 DEBUG PropertyUtils:918 - pd.getName():ghiJkl
...
2022-06-15 17:45:10 DEBUG PropertyUtils:916 - bean class:test.Object2 name:a0_abc
2022-06-15 17:45:10 DEBUG PropertyUtils:918 - pd.getName():a1_def
2022-06-15 17:45:10 DEBUG PropertyUtils:918 - pd.getName():a0_abc
2022-06-15 17:45:10 DEBUG PropertyUtils:918 - pd.getName():a2_ghi_jkl
...
2022-06-15 17:45:10 DEBUG PropertyUtils:916 - bean class:test.Object3 name:A0_ABC
2022-06-15 17:45:10 DEBUG PropertyUtils:918 - pd.getName():a1_DEF
2022-06-15 17:45:10 DEBUG PropertyUtils:918 - pd.getName():a0_ABC
2022-06-15 17:45:10 DEBUG PropertyUtils:918 - pd.getName():a2_GHI_JKL
...
2022-06-15 17:45:10 DEBUG PropertyUtils:916 - bean class:test.Object4 name:ABC
2022-06-15 17:45:10 DEBUG PropertyUtils:918 - pd.getName():ABC
2022-06-15 17:45:10 DEBUG PropertyUtils:918 - pd.getName():DEF
2022-06-15 17:45:10 DEBUG PropertyUtils:918 - pd.getName():GHI_JKL
可以觀測到,PropertyDescriptor並不是沒有產生,而是產生時name設定異常,第一個字母被改為小寫了!
也就是說,可以嘗試在 `getDescriptors()` 加入我們所期待的 `PropertyDescriptor` :name= A0_ABC
public class PropertyUtilsBean {
//...
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;
}
private BeanIntrospectionData fetchIntrospectionData(final Class<?> beanClass) {
final DefaultIntrospectionContext ictx = new DefaultIntrospectionContext(beanClass);
for (final BeanIntrospector bi : introspectors) {
try {
bi.introspect(ictx);
} catch (final IntrospectionException iex) {
log.error("Exception during introspection", iex);
}
}
return new BeanIntrospectionData(ictx.getPropertyDescriptors());
}
/**
* Resets the {@link BeanIntrospector} objects registered at this instance.
* After this method was called, only the default {@code BeanIntrospector} is
* registered.
*
* @since 1.9
*/
public final void resetBeanIntrospectors() {
introspectors.clear();
introspectors.add(DefaultBeanIntrospector.INSTANCE);
introspectors.add(SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS);
}
/**
* Adds a <code>BeanIntrospector</code>. This object is invoked when the
* property descriptors of a class need to be obtained.
*
* @param introspector the <code>BeanIntrospector</code> to be added (must not
* be <b>null</b>
* @throws IllegalArgumentException if the argument is <b>null</b>
* @since 1.9
*/
public void addBeanIntrospector(final BeanIntrospector introspector) {
if (introspector == null) {
throw new IllegalArgumentException("BeanIntrospector must not be null!");
}
introspectors.add(introspector);
}
//...
}
`getIntrospectionData()` 呼叫 `fetchIntrospectionData()` 的過程,會透過 `List<BeanIntrospector> introspectors` 產生預設值
`DefaultBeanIntrospector` 會針對所有屬性產生 `PropertyDescriptor` ; `SuppressPropertiesBeanIntrospector` 則會去除掉多產生出來的 `PropertyDescriptor [name='class']`
處理
嘗試實作 `BeanIntrospector` ,並且透過 `addBeanIntrospector()` 設定,來補完需求部分
public class Test2 {
private static final Logger logger = Logger.getLogger(Test2.class);
public static void main(String[] args) {
test5();
try {
test1();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
test2();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
test3();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
test4();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private static void test1() throws Exception {
Object1 obj = new Object1();
obj.setAbc(1);
obj.setDef(2);
logger.debug(BeanUtils.getProperty(obj, "abc"));
logger.debug(BeanUtils.getProperty(obj, "def"));
logger.debug(BeanUtils.getProperty(obj, "ghiJkl"));
}
private static void test2() throws Exception {
Object2 obj = new Object2();
obj.setA0_abc(1);
obj.setA1_def(2);
logger.debug(BeanUtils.getProperty(obj, "a0_abc"));
logger.debug(BeanUtils.getProperty(obj, "a1_def"));
logger.debug(BeanUtils.getProperty(obj, "a2_ghi_jkl"));
}
private static void test3() throws Exception {
Object3 obj = new Object3();
obj.setA0_ABC(1);
obj.setA1_DEF(2);
logger.debug(BeanUtils.getProperty(obj, "A0_ABC"));
logger.debug(BeanUtils.getProperty(obj, "A1_DEF"));
logger.debug(BeanUtils.getProperty(obj, "A2_GHI_JKL"));
}
private static void test4() throws Exception {
Object4 obj = new Object4();
obj.setABC(1);
obj.setDEF(2);
logger.debug(BeanUtils.getProperty(obj, "ABC"));
logger.debug(BeanUtils.getProperty(obj, "DEF"));
logger.debug(BeanUtils.getProperty(obj, "GHI_JKL"));
}
private static void test5() {
BeanUtilsBean.getInstance().getPropertyUtils().addBeanIntrospector(new BeanIntrospector() {
public void introspect(IntrospectionContext icontext) throws IntrospectionException {
List<PropertyDescriptor> additionalPropertyDescriptors = new ArrayList<>();
for (String propertyName : icontext.propertyNames()) {
PropertyDescriptor pd = icontext.getPropertyDescriptor(propertyName);
// 第一個字元為大寫,第二個字元為數字 會被異常轉換為第一個字元為小寫,進行修正
if (propertyName.length() >= 2 && Character.isLowerCase(propertyName.charAt(0))
&& Character.isDigit(propertyName.charAt(1))) {
logger.debug("Custom BeanIntrospector propertyName:" + propertyName);
logger.debug("Custom BeanIntrospector pd.getName:" + pd.getName());
logger.debug("Custom BeanIntrospector icontext.getTargetClass:" + icontext.getTargetClass());
logger.debug("Custom BeanIntrospector pd.readMethodName:" + pd.getReadMethod().getName());
logger.debug("Custom BeanIntrospector pd.writeMethodName:" + pd.getWriteMethod().getName());
// readMethodName,writeMethodName正確,可延用
String newPropertyName = propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
logger.debug("Custom BeanIntrospector newPropertyName:" + newPropertyName);
// 第一個字元為大寫改回大寫
additionalPropertyDescriptors
.add(new PropertyDescriptor(newPropertyName, icontext.getTargetClass(),
pd.getReadMethod().getName(), pd.getWriteMethod().getName()));
}
}
icontext.addPropertyDescriptors(additionalPropertyDescriptors.toArray(new PropertyDescriptor[0]));
}
});
}
}
2022-06-17 11:58:59 DEBUG Test2:99 - Custom BeanIntrospector propertyName:a1_def
2022-06-17 11:58:59 DEBUG Test2:100 - Custom BeanIntrospector pd.getName:a1_def
2022-06-17 11:58:59 DEBUG Test2:101 - Custom BeanIntrospector icontext.getTargetClass:class test.Object2
2022-06-17 11:58:59 DEBUG Test2:102 - Custom BeanIntrospector pd.readMethodName:getA1_def
2022-06-17 11:58:59 DEBUG Test2:103 - Custom BeanIntrospector pd.writeMethodName:setA1_def
2022-06-17 11:58:59 DEBUG Test2:107 - Custom BeanIntrospector newPropertyName:A1_def
2022-06-17 11:58:59 DEBUG Test2:99 - Custom BeanIntrospector propertyName:a0_abc
2022-06-17 11:58:59 DEBUG Test2:100 - Custom BeanIntrospector pd.getName:a0_abc
2022-06-17 11:58:59 DEBUG Test2:101 - Custom BeanIntrospector icontext.getTargetClass:class test.Object2
2022-06-17 11:58:59 DEBUG Test2:102 - Custom BeanIntrospector pd.readMethodName:getA0_abc
2022-06-17 11:58:59 DEBUG Test2:103 - Custom BeanIntrospector pd.writeMethodName:setA0_abc
2022-06-17 11:58:59 DEBUG Test2:107 - Custom BeanIntrospector newPropertyName:A0_abc
2022-06-17 11:58:59 DEBUG Test2:99 - Custom BeanIntrospector propertyName:a2_ghi_jkl
2022-06-17 11:58:59 DEBUG Test2:100 - Custom BeanIntrospector pd.getName:a2_ghi_jkl
2022-06-17 11:58:59 DEBUG Test2:101 - Custom BeanIntrospector icontext.getTargetClass:class test.Object2
2022-06-17 11:58:59 DEBUG Test2:102 - Custom BeanIntrospector pd.readMethodName:getA2_ghi_jkl
2022-06-17 11:58:59 DEBUG Test2:103 - Custom BeanIntrospector pd.writeMethodName:setA2_ghi_jkl
2022-06-17 11:58:59 DEBUG Test2:107 - Custom BeanIntrospector newPropertyName:A2_ghi_jkl
2022-06-17 11:58:59 DEBUG PropertyUtils:916 - bean class:test.Object2 name:a0_abc
2022-06-17 11:58:59 DEBUG PropertyUtils:922 - result HIT
2022-06-17 11:58:59 DEBUG StringConverter:140 - Converting 'Integer' value '1' to type 'String'
2022-06-17 11:58:59 DEBUG Test2:62 - 1
2022-06-17 11:58:59 DEBUG PropertyUtils:916 - bean class:test.Object2 name:a1_def
2022-06-17 11:58:59 DEBUG PropertyUtils:922 - result HIT
2022-06-17 11:58:59 DEBUG StringConverter:140 - Converting 'Integer' value '2' to type 'String'
2022-06-17 11:58:59 DEBUG Test2:63 - 2
2022-06-17 11:58:59 DEBUG PropertyUtils:916 - bean class:test.Object2 name:a2_ghi_jkl
2022-06-17 11:58:59 DEBUG PropertyUtils:922 - result HIT
2022-06-17 11:58:59 DEBUG StringConverter:140 - Converting 'Integer' value '0' to type 'String'
2022-06-17 11:58:59 DEBUG Test2:64 - 0
2022-06-17 11:58:59 DEBUG Test2:99 - Custom BeanIntrospector propertyName:a1_DEF
2022-06-17 11:58:59 DEBUG Test2:100 - Custom BeanIntrospector pd.getName:a1_DEF
2022-06-17 11:58:59 DEBUG Test2:101 - Custom BeanIntrospector icontext.getTargetClass:class test.Object3
2022-06-17 11:58:59 DEBUG Test2:102 - Custom BeanIntrospector pd.readMethodName:getA1_DEF
2022-06-17 11:58:59 DEBUG Test2:103 - Custom BeanIntrospector pd.writeMethodName:setA1_DEF
2022-06-17 11:58:59 DEBUG Test2:107 - Custom BeanIntrospector newPropertyName:A1_DEF
2022-06-17 11:58:59 DEBUG Test2:99 - Custom BeanIntrospector propertyName:a0_ABC
2022-06-17 11:58:59 DEBUG Test2:100 - Custom BeanIntrospector pd.getName:a0_ABC
2022-06-17 11:58:59 DEBUG Test2:101 - Custom BeanIntrospector icontext.getTargetClass:class test.Object3
2022-06-17 11:58:59 DEBUG Test2:102 - Custom BeanIntrospector pd.readMethodName:getA0_ABC
2022-06-17 11:58:59 DEBUG Test2:103 - Custom BeanIntrospector pd.writeMethodName:setA0_ABC
2022-06-17 11:58:59 DEBUG Test2:107 - Custom BeanIntrospector newPropertyName:A0_ABC
2022-06-17 11:58:59 DEBUG Test2:99 - Custom BeanIntrospector propertyName:a2_GHI_JKL
2022-06-17 11:58:59 DEBUG Test2:100 - Custom BeanIntrospector pd.getName:a2_GHI_JKL
2022-06-17 11:58:59 DEBUG Test2:101 - Custom BeanIntrospector icontext.getTargetClass:class test.Object3
2022-06-17 11:58:59 DEBUG Test2:102 - Custom BeanIntrospector pd.readMethodName:getA2_GHI_JKL
2022-06-17 11:58:59 DEBUG Test2:103 - Custom BeanIntrospector pd.writeMethodName:setA2_GHI_JKL
2022-06-17 11:58:59 DEBUG Test2:107 - Custom BeanIntrospector newPropertyName:A2_GHI_JKL
2022-06-17 11:58:59 DEBUG PropertyUtils:916 - bean class:test.Object3 name:A0_ABC
2022-06-17 11:58:59 DEBUG PropertyUtils:922 - result HIT
2022-06-17 11:58:59 DEBUG StringConverter:140 - Converting 'Integer' value '1' to type 'String'
2022-06-17 11:58:59 DEBUG Test2:73 - 1
2022-06-17 11:58:59 DEBUG PropertyUtils:916 - bean class:test.Object3 name:A1_DEF
2022-06-17 11:58:59 DEBUG PropertyUtils:922 - result HIT
2022-06-17 11:58:59 DEBUG StringConverter:140 - Converting 'Integer' value '2' to type 'String'
2022-06-17 11:58:59 DEBUG Test2:74 - 2
2022-06-17 11:58:59 DEBUG PropertyUtils:916 - bean class:test.Object3 name:A2_GHI_JKL
2022-06-17 11:58:59 DEBUG PropertyUtils:922 - result HIT
2022-06-17 11:58:59 DEBUG StringConverter:140 - Converting 'Integer' value '0' to type 'String'
2022-06-17 11:58:59 DEBUG Test2:75 - 0
備註: `Object2:變形1` 雖然也被偵測到,第一個字元由小寫改為大寫,但是原來的 `PropertyDescriptor ` 沒有被移除,新增的部分也不會被使用到,故沒有實質影響。
結語
其實這個異常和修改方式都是偏冷門,最主要的原因還是沒有遵守 `一般Java命名規範` ,所以還是建議大家還是盡量依照 `一般Java命名規範` 做開發。