Java Beanutils

[讀懂]深入淺出Beanutils的不足與自定義

葉宗原 2022/06/17 15:43:21
758

案由

某案的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命名規範` 做開發。

葉宗原