Java通过反射注解赋值

前段时间,领导分配一个统计销售区域汇总的数据,解决方案使用到了反射获取注解,通过注解获取属性或者设置字段属性。

问题描述

查询公司列表,分别是公司 id、区域 id、区域名称:

公司 id 区域 id 区域名称
1 1 华南
2 2 华北
3 2 华北
4 3 华东
5 3 华东

创建公司类Company

public class Company {
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Company</span><span class="hljs-params">(Integer id,  Integer areaId, String areaName)</span> </span>{
    <span class="hljs-keyword">this</span>.id = id;
    <span class="hljs-keyword">this</span>.areaId = areaId;
    <span class="hljs-keyword">this</span>.areaName = areaName;
}

<span class="hljs-comment">/**
 * 公司id
 */</span>
<span class="hljs-keyword">private</span> Integer id;

<span class="hljs-comment">/**
 * 区域id
 */</span>
<span class="hljs-keyword">private</span> Integer areaId;

<span class="hljs-comment">/**
 * 区域名称
 */</span>
<span class="hljs-keyword">private</span> String areaName;
 
<span class="hljs-comment">// 省略get/set方法 </span>

}

最终解决

要求汇总各个区域公司数量, 得到如下汇总:

区域 id 区域名称 公司总数
1 华南 1
2 华北 2
3 华东 2

最终区域实体AreaStatistic:

public class AreaStatistic {
<span class="hljs-meta">@ColumnProperty(<span class="hljs-string">"华东大区"</span>)</span>
<span class="hljs-keyword">private</span> Integer eastChina = <span class="hljs-number">0</span>;

<span class="hljs-meta">@ColumnProperty(<span class="hljs-string">"华东id"</span>)</span>
<span class="hljs-keyword">private</span> Integer eastChinaId;

<span class="hljs-meta">@ColumnProperty(<span class="hljs-string">"华南大区"</span>)</span>
<span class="hljs-keyword">private</span> Integer southChina = <span class="hljs-number">0</span>;

<span class="hljs-meta">@ColumnProperty(<span class="hljs-string">"华南id"</span>)</span>
<span class="hljs-keyword">private</span> Integer southChinaId;

<span class="hljs-meta">@ColumnProperty(<span class="hljs-string">"华北大区"</span>)</span>
<span class="hljs-keyword">private</span> Integer northChina = <span class="hljs-number">0</span>;

<span class="hljs-meta">@ColumnProperty(<span class="hljs-string">"华北id"</span>)</span>
<span class="hljs-keyword">private</span> Integer northChinaId;

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> String toString() {
    <span class="hljs-keyword">return</span> <span class="hljs-string">"AreaStatistic{\n"</span> +
            <span class="hljs-string">"华东Id="</span> + eastChinaId +
            <span class="hljs-string">",华东="</span> + eastChina +
            <span class="hljs-string">", \n华南Id="</span> + southChinaId +
            <span class="hljs-string">", 华南="</span> + southChina +
            <span class="hljs-string">", \n华北Id="</span> + northChinaId +
            <span class="hljs-string">", 华北="</span> + northChina +
            <span class="hljs-string">'}'</span>;
}
<span class="hljs-comment">// 省略get/set方法</span>

}

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 <span class="hljs-title function_">value</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-string">""</span>;

}

  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