一、使用场景:
实体类要转化为一串特定的String,且多个成员变量在转化时使用方法一致,若直接对实体类的成员变量一个一个的转义,拼接就显得很麻烦,如果下次再有类似的客户需求还要进行对其他实体信息的拼接,就还要重复一次上边的操作,代码十分臃肿,且重复率高,可复用性也很低。
二、简单的注解+反射拼接实体类信息
1、注解类1——变量名称标签:只有一个value属性,非空
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NameLabel {
String value();
}
?2、注解类2——使用方法名称:只有一个value属性,默认为空
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConvertMethod {
String value() default "";
}
3、转义工具类——用反射来拼接传入实体信息
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Objects;
public class FieldAndAnnotionUtil {
private static final String EMPTY = "";
public static String getLabelAndValue(Object obj) {
if (Objects.isNull(obj)) {
return EMPTY;
}
//获取反射
Field[] fields = obj.getClass().getDeclaredFields();
if (0 == fields.length) {
return EMPTY;
}
StringBuilder sb = new StringBuilder();
try {
for (Field field : fields) {
//授权
field.setAccessible(true);
Object value = field.get(obj);
//获取到的属性为null跳出
if (null == value) {
continue;
}
//属性没有添加NameLabel注解跳出
if (!field.isAnnotationPresent(NameLabel.class)) {
continue;
}
NameLabel nameLabel = field.getAnnotation(NameLabel.class);
//属性没有添加ConvertMethod注解跳出,用默认拼接方法
if (!field.isAnnotationPresent(ConvertMethod.class)) {
sb.append(nameLabel.value()).append(":").append(value).append(",").append(" ");
continue;
}
//获取注解中的方法名,通过反射执行该方法,实现不同变量的不同转义方式
ConvertMethod methodName = field.getAnnotation(ConvertMethod.class);
//处理注解方法名为空的情况,依然用默认方法拼接
if("".equals(methodName.value())){
sb.append(nameLabel.value()).append(":").append(value).append(",").append(" ");
continue;
}
Method method = obj.getClass().getMethod(methodName.value());
sb.append(nameLabel.value()).append(":").append(method.invoke(obj)).append(",").append(" ");
}
} catch (Exception e) {
e.printStackTrace();
}
String str = sb.toString();
//处理末尾的逗号和空格
return !"".equals(str) ? str.substring(0, str.length() - 2) : str;
}
}
4、实体类——使用自定义注解
public class Person {
@NameLabel(value = "姓名")
@ConvertMethod("nameLabel")
private String name;
@NameLabel(value = "性别")
@ConvertMethod("sexLabel")
private String sex;
@NameLabel(value = "年龄")
@ConvertMethod("ageLabel")
private Integer age;
@NameLabel(value = "身高")
private Integer height;
private String occupation;
public Person(String name, String sex, Integer age, Integer height, String occupation) {
this.name = name;
this.sex = sex;
this.age = age;
this.height = height;
this.occupation = occupation;
}
public String nameLabel() {
if(name == null || name.trim().equals("")){
return null;
}
return name;
}
public String sexLabel() {
if(sex == null || sex.trim().equals("")){
return null;
}
return sex + "性";
}
public String ageLabel() {
if(age == null){
return null;
}
return age + "岁";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getHeight() {
return height;
}
public void setHeight(Integer height) {
this.height = height;
}
public String getOccupation() {
return occupation;
}
public void setOccupation(String occupation) {
this.occupation = occupation;
}
}
5、主方法——测试一下
public class FieldAndAnnotionTest {
public static void main(String[] args) {
Person person = new Person("张三", "男", 18, 175, "无");
String personDesc = FieldAndAnnotionUtil.getLabelAndValue(person);
System.out.println(personDesc);
}
}
6、执行结果
姓名:张三, 性别:男性, 年龄:18岁, 身高:175
?7、分析:
Person类中未给变量 height 添加 @ConvertMethod 注解,但是转义工具类给了一个默认的注解就是用变量的 @NameLabel 中的名称拼接变量的值。
occupation 变量未给加任何注解,因此在转义中被忽略掉。
因此我们想对类成员变量进行转义的加上,注解,添加特定的实现方法即可。这个地方若是只添加 @ConvertMethod 注解,却没有对应的实现方法是会抛出 java.lang.NoSuchMethodException 异常的。
三、改进版:@NameLabel注解参数添加一个sort属性,让它支持自定义排序
1、改进注解@NameLabel——添加sort属性,默认为0
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NameLabel {
String value();
int sort() default 0;
}
2、Person类注解添加自定义顺序
public class Person {
@NameLabel(value = "姓名")
@ConvertMethod("nameLabel")
private String name;
@NameLabel(value = "性别", sort = 3)
@ConvertMethod("sexLabel")
private String sex;
@NameLabel(value = "年龄", sort = 2)
@ConvertMethod("ageLabel")
private Integer age;
@NameLabel(value = "身高", sort = 4)
private Integer height;
private String occupation;
public Person(String name, String sex, Integer age, Integer height, String occupation) {
this.name = name;
this.sex = sex;
this.age = age;
this.height = height;
this.occupation = occupation;
}
public String nameLabel() {
if (name == null || name.trim().equals("")) {
return null;
}
return name;
}
public String sexLabel() {
if (sex == null || sex.trim().equals("")) {
return null;
}
return sex + "性";
}
public String ageLabel() {
if (age == null) {
return null;
}
return age + "岁";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getHeight() {
return height;
}
public void setHeight(Integer height) {
this.height = height;
}
public String getOccupation() {
return occupation;
}
public void setOccupation(String occupation) {
this.occupation = occupation;
}
}
3、修改转义工具类添加根据sort排序方法
public class FieldAndAnnotionUtil {
private static final String EMPTY = "";
public static String getLabelAndValue(Object obj) {
if (Objects.isNull(obj)) {
return EMPTY;
}
//获取反射
Field[] fields = obj.getClass().getDeclaredFields();
if (0 == fields.length) {
return EMPTY;
}
List<Field> fieldList = Arrays.stream(fields).
//获取反射类中添加过@NameLabel注解的属性
filter(field -> field.isAnnotationPresent(NameLabel.class)).
//根据@NameLabel注解的sort值进行分组
collect(Collectors.toMap(f -> f.getAnnotation(NameLabel.class).sort(), f -> f))
//转化后根据sort排序
.entrySet().stream().
sorted(Map.Entry.comparingByKey()).
//转化为List
map(map -> map.getValue()).
collect(Collectors.toList());
StringBuilder sb = new StringBuilder();
try {
for (Field field : fieldList) {
//授权
field.setAccessible(true);
Object value = field.get(obj);
//获取到的属性为null跳出
if (null == value) {
continue;
}
//属性没有添加NameLabel注解跳出
if (!field.isAnnotationPresent(NameLabel.class)) {
continue;
}
NameLabel nameLabel = field.getAnnotation(NameLabel.class);
//属性没有添加ConvertMethod注解跳出,用默认拼接方法
if (!field.isAnnotationPresent(ConvertMethod.class)) {
sb.append(nameLabel.value()).append(":").append(value).append(",").append(" ");
continue;
}
//获取注解中的方法名,通过反射执行该方法,实现不同变量的不同转义方式
ConvertMethod methodName = field.getAnnotation(ConvertMethod.class);
//处理注解方法名为空的情况,依然用默认方法拼接
if ("".equals(methodName.value())) {
sb.append(nameLabel.value()).append(":").append(value).append(",").append(" ");
continue;
}
Method method = obj.getClass().getMethod(methodName.value());
sb.append(nameLabel.value()).append(":").append(method.invoke(obj)).append(",").append(" ");
}
} catch (Exception e) {
e.printStackTrace();
}
String str = sb.toString();
//处理末尾的逗号和空格
return !"".equals(str) ? str.substring(0, str.length() - 2) : str;
}
}
4、执行结果
姓名:张三, 年龄:18岁, 性别:男性, 身高:175
?5、分析:
Person 类 name 属性注解中未加 sort 值,所以默认为,0 排序靠最前,age、sex、height 都是根据sort值排序值展示。
需要注意的是,这里还存在一个隐患:如果你设置的sort值两个值相等,会导致程序在转换成map进行排序的时候执行错误,抛出:java.lang.IllegalStateException: Duplicate key XXX
四、有类继承时,同样也可以实现子类加父类信息的组合转义
1、新建一个 Programmer 类 继承 Person 类
public class Programmer extends Person {
@NameLabel(value = "秃头", sort = 5)
@ConvertMethod("tuTouLabel")
private Boolean tuTou;
@NameLabel(value = "单身", sort = 6)
@ConvertMethod("singleLabel")
private Boolean single;
private Boolean zhaiMan;
public String tuTouLabel() {
return this.tuTou ? "是" : "否";
}
public String singleLabel() {
return this.single ? "是" : "否";
}
public Programmer(String name, String sex, Integer age, Integer height, String occupation, Boolean tuTou, Boolean single, Boolean zhaiMan) {
super(name, sex, age, height, occupation);
this.tuTou = tuTou;
this.single = single;
this.zhaiMan = zhaiMan;
}
public Boolean getTuTou() {
return tuTou;
}
public void setTuTou(Boolean tuTou) {
this.tuTou = tuTou;
}
public Boolean getSingle() {
return single;
}
public void setSingle(Boolean single) {
this.single = single;
}
}
2、Person 类做一个小调整——修改顺序,给occupation添加@NameLabel注解,使用默认方法转义,排序为7,在Programer类两个属性排序之后
public class Person {
@NameLabel(value = "姓名", sort = 1)
@ConvertMethod("nameLabel")
private String name;
@NameLabel(value = "性别", sort = 2)
@ConvertMethod("sexLabel")
private String sex;
@NameLabel(value = "年龄", sort = 3)
@ConvertMethod("ageLabel")
private Integer age;
@NameLabel(value = "身高", sort = 4)
private Integer height;
@NameLabel(value = "职业", sort = 7)
private String occupation;
public Person(String name, String sex, Integer age, Integer height, String occupation) {
this.name = name;
this.sex = sex;
this.age = age;
this.height = height;
this.occupation = occupation;
}
public String nameLabel() {
if (name == null || name.trim().equals("")) {
return null;
}
return name;
}
public String sexLabel() {
if (sex == null || sex.trim().equals("")) {
return null;
}
return sex + "性";
}
public String ageLabel() {
if (age == null) {
return null;
}
return age + "岁";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getHeight() {
return height;
}
public void setHeight(Integer height) {
this.height = height;
}
public String getOccupation() {
return occupation;
}
public void setOccupation(String occupation) {
this.occupation = occupation;
}
}
3、修改转义工具类,获取父类的所有反射属性
public class FieldAndAnnotionUtil {
private static final String EMPTY = "";
public static String getLabelAndValue(Object obj) {
if (Objects.isNull(obj)) {
return EMPTY;
}
//获取反射
Field[] fields = obj.getClass().getDeclaredFields();
List<Field> fieldList = new ArrayList<>(Arrays.asList(fields));
//获取父类的所有反射属性
Field[] fatherFields = obj.getClass().getSuperclass().getDeclaredFields();
Collections.addAll(fieldList, fatherFields);
if (0 == fieldList.size()) {
return EMPTY;
}
fieldList = fieldList.stream().
//获取反射类中添加过@NameLabel注解的属性
filter(field -> field.isAnnotationPresent(NameLabel.class)).
//根据@NameLabel注解的sort值进行分组
collect(Collectors.toMap(f -> f.getAnnotation(NameLabel.class).sort(), f -> f))
//去重转化后根据sort排序
.entrySet().stream().
sorted(Map.Entry.comparingByKey()).
//转化为List
map(Map.Entry::getValue).
collect(Collectors.toList());
StringBuilder sb = new StringBuilder();
try {
for (Field field : fieldList) {
//授权
field.setAccessible(true);
Object value = field.get(obj);
//获取到的属性为null跳出
if (null == value) {
continue;
}
//属性没有添加NameLabel注解跳出
if (!field.isAnnotationPresent(NameLabel.class)) {
continue;
}
NameLabel nameLabel = field.getAnnotation(NameLabel.class);
//属性没有添加ConvertMethod注解跳出,用默认拼接方法
if (!field.isAnnotationPresent(ConvertMethod.class)) {
sb.append(nameLabel.value()).append(":").append(value).append(",").append(" ");
continue;
}
//获取注解中的方法名,通过反射执行该方法,实现不同变量的不同转义方式
ConvertMethod methodName = field.getAnnotation(ConvertMethod.class);
//处理注解方法名为空的情况,依然用默认方法拼接
if ("".equals(methodName.value())) {
sb.append(nameLabel.value()).append(":").append(value).append(",").append(" ");
continue;
}
Method method = obj.getClass().getMethod(methodName.value());
sb.append(nameLabel.value()).append(":").append(method.invoke(obj)).append(",").append(" ");
}
} catch (Exception e) {
e.printStackTrace();
}
String str = sb.toString();
//处理末尾的逗号和空格
return !"".equals(str) ? str.substring(0, str.length() - 2) : str;
}
}
4、测试类
public class FieldAndAnnotionTest {
public static void main(String[] args) {
// Person person = new Person("张三", "男", 18, 175, "无");
// String personDesc = FieldAndAnnotionUtil.getLabelAndValue(person);
// System.out.println(personDesc);
Programmer Programmer = new Programmer("张三", "男", 18, 175, "程序员", true, true, true);
String personDesc = FieldAndAnnotionUtil.getLabelAndValue(Programmer);
System.out.println(personDesc);
}
}
5、执行结果
姓名:张三, 性别:男性, 年龄:18岁, 身高:175, 秃头:是, 单身:是, 职业:程序员
6、分析:
获取到父类的属性和值进行转义,且转义顺序按照sort值进行排序,一切正常。
五、结语
使用注解+反射来提高代码的适用性,降低代码的重复率,大体思路是这个样子,具体实现还是要视集体情况而定,盲目照抄永远不是解决问题的最好方式,且以上代码还存在两个容易发生系统错误的漏洞,还是可以继续优化的。
|