java反射調用get/set方法,你還在拼接方法名嗎?


前言

最新工作中,遇到了通過反射調用get/set方法的地方,雖然反射的性能不是很好,但是相比較於硬編碼的不易擴展,getDeclareFields可以拿到所有的成員變量,后續添加或刪除成員變量時,不用修改代碼,且應用次數只在修改數據時使用,故犧牲一些性能提高擴展性

傳統的方式

見過很多人通過反射調用get/set方法都是通過獲取屬性的name,然后通過字符串截取將首字母大寫,再拼上get/set來做

String fieldName = field.getName();
String getMethodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);

還有稍微好一點的同學,通過fieldName轉成字符數組,首個字符-32來避免字符串截取的

String fieldName = field.getName();
char[] chars = fieldName.toCharArray();
chars[0] = (char)(chars[0] - 32);
String getMethodName = "get" + new String(chars);

誠然,我覺得兩種方式都可以,但是不知道有沒有遇到過,生成的get/set方法並不是已get/set開頭的,而是以is開頭的,比如boolean類型的成員變量。這個時候我們就需要去判斷屬性的類型,然后用不同的前綴來拼接get/set方法名。其實,在jdk中已經包含了這樣的工具類

Introspector和PropertyDescriptor

關於這兩個類的詳細介紹,我這里就不說了,簡單的理解就是對象信息的描述,里面提供了一些API方便我們拿到對象的信息

BeanInfo beanInfo;
try {
    beanInfo = Introspector.getBeanInfo(template.getClass());
} catch (IntrospectionException e) {
    log.info("xxxxxxxxxxxxxxxx", e);
    return null;
}

List<PropertyDescriptor> descriptors = Arrays.stream(beanInfo.getPropertyDescriptors()).filter(p -> {
    String name = p.getName();
    //過濾掉不需要修改的屬性
    return !"class".equals(name) && !"id".equals(name);
}).collect(Collectors.toList());

for (PropertyDescriptor descriptor : descriptors) {
    //descriptor.getWriteMethod()方法對應set方法
    Method readMethod = descriptor.getReadMethod();
    System.out.println(descriptor.getName());
    try {
        Object o = readMethod.invoke(template);
        System.out.println(o);
    } catch (IllegalAccessException | InvocationTargetException e) {
        log.info("xxxxxxxxxxxxxxxx", e);
        return null;
    }
}

PropertyDescriptor類提供了getReadMethod和getWriteMethod,其實就是對於get/set方法,至於方法名稱不需要我們來關於,這樣就可以避免方法名拼錯的情況了。

另外PropertyDescriptor除了可以通過Introspector獲取,也可以自己new來創建,其構造方法還是比較全的

通常傳遞一個屬性的名稱和類對象class就可以了

List<Field> fields = Arrays.stream(template.getClass().getDeclaredFields()).filter(f -> {
    String name = f.getName();
    //過濾掉不需要修改的屬性
    return !"id".equals(name) && !"serialVersionUID".equals(name);
}).collect(Collectors.toList());

for (Field field : fields) {
    try {
        PropertyDescriptor descriptor = new PropertyDescriptor(field.getName(), template.getClass());
        Method readMethod = descriptor.getReadMethod();
        Object o = readMethod.invoke(template);
        System.out.println(o);
    } catch (IntrospectionException | IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }
}

通過上面兩種不同的實現方式可以看到,Introspector會額外有一個class屬性,但是類似serialVersionUID不會算在內;而自定義PropertyDescriptor需要通過反射拿到所有的屬性,雖然不會有class屬性,但是serialVersionUID會算在內,使用的時候需要注意一下。

如果你以為這就是Introspector的全部功能,那就大錯特錯了。Introspector不同於普通的反射,反射一次,一段時間內可重復使用,為什么不是永久呢,看下源碼

/**
     * Introspect on a Java Bean and learn about all its properties, exposed
     * methods, and events.
     * <p>
     * If the BeanInfo class for a Java Bean has been previously Introspected
     * then the BeanInfo class is retrieved from the BeanInfo cache.
     *
     * @param beanClass  The bean class to be analyzed.
     * @return  A BeanInfo object describing the target bean.
     * @exception IntrospectionException if an exception occurs during
     *              introspection.
     * @see #flushCaches
     * @see #flushFromCaches
     */
    public static BeanInfo getBeanInfo(Class<?> beanClass)
        throws IntrospectionException
    {
        if (!ReflectUtil.isPackageAccessible(beanClass)) {
            return (new Introspector(beanClass, null, USE_ALL_BEANINFO)).getBeanInfo();
        }
        ThreadGroupContext context = ThreadGroupContext.getContext();
        BeanInfo beanInfo;
        synchronized (declaredMethodCache) { beanInfo = context.getBeanInfo(beanClass); } if (beanInfo == null) {
            beanInfo = new Introspector(beanClass, null, USE_ALL_BEANINFO).getBeanInfo();
            synchronized (declaredMethodCache) {
                context.putBeanInfo(beanClass, beanInfo);
            }
        }
        return beanInfo;
    }

注意中間加粗標紅的代碼,這里除了同步之外,還做了一個本地的緩存

BeanInfo getBeanInfo(Class<?> type) {
    return (this.beanInfoCache != null)
            ? this.beanInfoCache.get(type)
            : null;
}

這個beanInfoCache 其實是一個WeakHashMap,每次gc被回收,所以上面說一段時間內可以重復使用而不是永久,也是為了避免OOM吧

 總結

大概先說這么多吧,雖然算不上什么高級技術,但是能將工作中遇到的小問題解決也是成長啊!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM