Java通過反射註解賦值

2022-07-22 12:05:58

前段時間,領導分配一個統計銷售區域彙總的資料,解決方案使用到了反射獲取註解,通過註解獲取屬性或者設定欄位屬性。

問題描述

查詢公司列表,分別是公司id、區域id、區域名稱:

公司id 區域id 區域名稱
1 1 華南
2 2 華北
3 2 華北
4 3 華東
5 3 華東

建立公司類Company

public class Company {

    public Company(Integer id,  Integer areaId, String areaName) {
        this.id = id;
        this.areaId = areaId;
        this.areaName = areaName;
    }

    /**
     * 公司id
     */
    private Integer id;

    /**
     * 區域id
     */
    private Integer areaId;

    /**
     * 區域名稱
     */
    private String areaName;
     
    // 省略get/set方法 

}

最終解決

要求彙總各個區域公司數量,得到如下彙總:

區域id 區域名稱 公司總數
1 華南 1
2 華北 2
3 華東 2

最終區域實體AreaStatistic:

public class AreaStatistic {

    @ColumnProperty("華東大區")
    private Integer eastChina = 0;

    @ColumnProperty("華東id")
    private Integer eastChinaId;

    @ColumnProperty("華南大區")
    private Integer southChina = 0;

    @ColumnProperty("華南id")
    private Integer southChinaId;

    @ColumnProperty("華北大區")
    private Integer northChina = 0;

    @ColumnProperty("華北id")
    private Integer northChinaId;

    @Override
    public String toString() {
        return "AreaStatistic{\n" +
                "華東Id=" + eastChinaId +
                ",華東=" + eastChina +
                ", \n華南Id=" + southChinaId +
                ", 華南=" + southChina +
                ", \n華北Id=" + northChinaId +
                ", 華北=" + northChina +
                '}';
    }
    // 省略get/set方法
}

if/else 普通解法

AreaStatistic areaStatistic = new AreaStatistic();
for (Company company:companyList) {
    String areaName = company.getAreaName();
    if ("華南".equals(areaName)) {
        areaStatistic.setSouthChina(areaStatistic.getSouthChina()+1);
        areaStatistic.setSouthChinaId(company.getAreaId());
    } else if ("華北".equals(areaName)) {
        areaStatistic.setNorthChina(areaStatistic.getNorthChina()+1);
        areaStatistic.setNorthChinaId(company.getAreaId());
    } else if ("華東".equals(areaName)) {
        areaStatistic.setEastChina(areaStatistic.getEastChina()+1);
        areaStatistic.setEastChinaId(company.getAreaId());
    }
}

輸出:

華東Id=3,華東=2, 
華南Id=1, 華南=1, 
華北Id=2, 華北=2

這種做法的缺點:

  • 要寫大量的條件判斷語句,非常的繁瑣。
  • 增加和減少統計區域,都要修改程式碼。

針對上面的缺點,使用反射獲取註解,通過註解獲取屬性賦值。

通過反射註解賦值屬性

解題思路

  1. 遍歷公司列表,獲取到區域id和區域名稱。
  2. 建立自定義註解@ColumnProperty:
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ColumnProperty {

    String value() default "";

}
  1. 通過反射獲取屬性,然後遍歷欄位屬性獲取註解。

AreaStatistic欄位屬性上新增註解:

@ColumnProperty("華東大區")
private Integer eastChina = 0;

@ColumnProperty("華東id")
private Integer eastChinaId;

@ColumnProperty("華南大區")
private Integer southChina = 0;

@ColumnProperty("華南id")
private Integer southChinaId;

@ColumnProperty("華北大區")
private Integer northChina = 0;

@ColumnProperty("華北id")
private Integer northChinaId;
  1. 通過反射獲取屬性,然後遍歷欄位屬性獲取註解。
Class staticClass = areaStatistic.getClass();
Field[] fields = staticClass.getDeclaredFields();
for (Field field : fields) {
    ColumnProperty property = field.getAnnotation(ColumnProperty.class);
    String value = property.value();
}
  1. 匹配區域名稱和欄位屬性,比如遍歷公司區域是華東,就遍歷到華東大區註解對應的欄位,並賦值或者獲取欄位值。
if (value != null) {
    int indexOf = value.indexOf("大區");
    if (indexOf != -1 && value.length() == 4) {
        if (areaName.equals(value.substring(0,2))) {
            field.setAccessible(true);
            field.set(areaStatistic,(Integer) field.get(areaStatistic) + 1);  
        }
     }
}
  1. 區域id賦值也是相同的解題思路。

根據上面的思路,有如下程式碼彙總

// 遍歷公司
for (Company company:companyList) {
  setAreaProperty(areaStatistic2,company.getAreaName(),company.getAreaId());
}

private void setAreaProperty(AreaStatistic areaStatistic,String areaName,Integer areaId) throws IllegalAccessException {
    // 反射獲取註解 
    Class staticClass = areaStatistic.getClass();
    Field[] fields = staticClass.getDeclaredFields();
    for (Field field : fields) {
        ColumnProperty property = field.getAnnotation(ColumnProperty.class);
        String value = property.value();
        if (value != null) {
            int indexOf = value.indexOf("大區");
            if (indexOf != -1 && value.length() == 4) {
                // 匹配到註解屬性並賦值 
                if (areaName.equals(value.substring(0,2))) {
                    field.setAccessible(true);
                    field.set(areaStatistic,(Integer) field.get(areaStatistic) + 1);
                    for (Field idField : fields) {
                        ColumnProperty idProperty = idField.getAnnotation(ColumnProperty.class);
                        String idValue = idProperty.value();
                        if (idValue.equals(areaName+"id")) {
                            idField.setAccessible(true);
                            idField.set(areaStatistic,areaId);
                            break;
                        }
                    }
                    break;
                }
            }
        }
    }
}

輸出:

華東Id=3,華東=2, 
華南Id=1, 華南=1, 
華北Id=2, 華北=2

彙總某些欄位的和

上面算出各個區域的彙總之後,還要算出全部區域的總和,這裡還是使用到註解,把屬性欄位包含大區都累加起來:

AreaStatistic statistic = new AreaStatistic();
statistic.setEastChina(2);
statistic.setNorthChina(3);
statistic.setSouthChina(1);
int sum = 0;
Class staticClass = statistic.getClass();
Field[] fields = staticClass.getDeclaredFields();
for (Field field : fields) {
    ColumnProperty property = field.getAnnotation(ColumnProperty.class);
    String value = property.value();
    if (value.indexOf("大區") != -1) {
        field.setAccessible(true);
        sum += field.get(statistic) == null ? 0 : (Integer) field.get(statistic);
    }
}
System.out.println(sum);

輸出結果:

6

總結

  • 自定義註解,通過反射獲取註解
  • 通過匹配註解值,獲取或者複製對應的欄位屬性。

賦值主要程式碼為:

field.setAccessible(true);
field.set(Model,value);

原始碼地址

https://github.com/jeremylai7/java-codes/blob/master/basis/src/main/java/reflect/SetValueByAnnotation.java