登录消息中有很多开关属性,这些属性在赋值的时候会大段执行结构相同的代码,为了减少代码量和通用性使用反射合并这些类似的代码。


Java使用注解加反射赋值属性

  • 1. 原始代码
  • 2. 修改后的代码
  • 开始实操
  • 1. 定义注解
  • 2. 使用定义好的注解
  • 3. 使用反射读取注解并给属性赋值
  • 命名规范问题


1. 原始代码

灰色部分都是在设置开关类型的字段

Java 方法反射加速 java反射增加属性_赋值

2. 修改后的代码

// 反射写入配置
try {
    Class c = response.getClass();
    Field[] fieldArr = c.getDeclaredFields();
    APlatInfoAttribute aia;
    PropertyDescriptor descriptor;
    Method setMethod;
    for (Field f : fieldArr) {
        if (!f.isAnnotationPresent(APlatInfoAttribute.class)) {
            continue;
        }
        aia = f.getAnnotation(APlatInfoAttribute.class);
        descriptor = new PropertyDescriptor(f.getName(), c);
        setMethod = descriptor.getWriteMethod();
        switch (aia.useMethods()) {
            case "getParamsValByKey":
                setMethod.invoke(response, info.getParamsValByKey(isIos, aia.configName(), aia.valueLength(), aia.defaultValue()));
                break;
            case "getParamsOpByVersion":
                setMethod.invoke(response, info.getParamsOpByVersion(isIos, aia.configName(), msgInfo.getVersionNum()));
                break;
            case "getOpByKey":
                setMethod.invoke(response, info.getOpByKey(isIos, aia.configName()));
                break;
            case "getParamStrByKey":
                setMethod.invoke(response, info.getParamStrByKey(isIos, aia.configName(), aia.valueLength()));
                break;

            default:
                logger.error("OverseaGameLoginLogic - response." + f.getName() + " 获取失败");
                break;
        }

    }
} catch (Exception e) {
    e.printStackTrace();
}

开始实操

1. 定义注解

根据实际获取配置的方式来定义注解中的内容,以下为实际使用中的4中情况

info.getParamStrByKey(isIos, "notice", 8);
info.getParamsValByKey(isIos, "qqact", 1, 0);
info.getParamsOpByVersion(isIos, "isShowAds", msgInfo.getVersionNum());
info.getOpByKey(isIos, "energyToProp1");
  • 四种获取方式
  • 相同的参数为 isIos, key, valueLength
  • 不相同的为 默认值
  • 经过分析可以得到对应的注解结构如下,其中@Target(ElementType.FIELD)和@Retention(RetentionPolicy.RUNTIME) 是源注解,就是注解的注解,这里使用的FIELD 是作用属性字段、枚举的常量。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface APlatInfoAttribute {
    /**
     * 配置的key
     */
    String configName();

    /**
     * 使用PlatInfo的哪个方法获取值
     */
    String useMethods() default "getParamsValByKey";

    /**
     * getParamsValByKey 值的长度
     */
    int valueLength() default 1;

    /**
     * getParamsValByKey 默认值
     */
    int defaultValue() default 0;
}

2. 使用定义好的注解

在属性的上方写上 @APlatInfoAttribute(configName = “energyToProp1”, useMethods
= “getOpByKey”, valueLength = 4, defaultValue = 50) 其中 useMethods,valueLength,defaultValue 是有默认值,是可以省略不写的

@APlatInfoAttribute(configName = "stype")
private int securitytype;

@APlatInfoAttribute(configName = "security", useMethods = "getParamsOpByVersion")
private int showsecurity;

@APlatInfoAttribute(configName = "notice", useMethods = "getParamStrByKey", valueLength = 8)
private String showNotice;

@APlatInfoAttribute(configName = "bullet", valueLength = 4, defaultValue = 50)
private int perfectBetBullet;

3. 使用反射读取注解并给属性赋值

通过反射获取指定对象中带有指定注解的字段,进行反射调用对应的 set 方法,当然可以使用反射直接对属性赋值,但是有些属性赋值后需要一些逻辑判断,这里选用的 set 进行赋值。

  • 通过 Object.getClass().getDeclaredFields(); 获取类或接口声明的所有字段,包含公共、受保护、默认(包)访问和私有字段,但不包括继承的字段 。返回的数组中的元素没有排序,也没有任何特定的顺序。
  • 通过遍历获得到的数组,通过 isAnnotationPresent(APlatInfoAttribute.class) 筛选出带有我们指定注解的属性
  • 使用getAnnotation(APlatInfoAttribute.class);获取注解
  • 使用 Method m = new PropertyDescriptor(f.getName(), c).getWriteMethod(); 获取属性的 set 方法,通过m.invoke();进行调用set方法
try {
    Class c = response.getClass();
    // 获取属性
    Field[] fieldArr = c.getDeclaredFields();
    APlatInfoAttribute aia;
    Method setMethod;
    for (Field f : fieldArr) {
        // 判断是否有指定注解
        if (!f.isAnnotationPresent(APlatInfoAttribute.class)) {
            continue;
        }
        // 获取注解
        aia = f.getAnnotation(APlatInfoAttribute.class);
        // 获取set方法
        setMethod = new PropertyDescriptor(f.getName(), c).getWriteMethod();
        // 判断要使用的方法获取配置,并执行set
        switch (aia.useMethods()) {
            case "getParamsValByKey":
                setMethod.invoke(response, info.getParamsValByKey(isIos, aia.configName(), aia.valueLength(), aia.defaultValue()));
                break;
            case "getParamsOpByVersion":
                setMethod.invoke(response, info.getParamsOpByVersion(isIos, aia.configName(), msgInfo.getVersionNum()));
                break;
            case "getOpByKey":
                setMethod.invoke(response, info.getOpByKey(isIos, aia.configName()));
                break;
            case "getParamStrByKey":
                setMethod.invoke(response, info.getParamStrByKey(isIos, aia.configName(), aia.valueLength()));
                break;

            default:
                logger.error("OverseaGameLoginLogic - response." + f.getName() + " 获取失败");
                break;
        }

    }
} catch (Exception e) {
    e.printStackTrace();
}

命名规范问题

属性名如果是 b + 大写字母开头的,获取到的 get,set为 is 开头比如:
如果使用自动生成 get,set 方法,非 boolean 类型的要注意命名规范

public class C1 {
    private int bVa1;

    // 自动生成的 get set
    public int getbVa1() {
        return bVa1;
    }

    public void setbVa1(int bVa1) {
        this.bVa1 = bVa1;
    }
}

C1 c1 = new C1();
Class c = c1.getClass();
Field[] fArr = c.getDeclaredFields();
PropertyDescriptor descriptor = new PropertyDescriptor(fArr[0].getName(), c);
// 这里就会报错:java.beans.IntrospectionException: Method not found: isBVa1