做Java开发的同学肯定都遇到过这种场景:把数据库查询结果封装成实体类,或者把前端传的表单数据转成对象时,要写一堆
setXxx()
方法手动赋值。如果实体类有几十个字段,光是复制粘贴字段名就能让人崩溃,而且还容易因为手误把字段名写错导致bug。
new关键字创建对象,再调用相关方法——这是“编译期”就确定好的操作。User user = new User();创建对象,然后
user.setName("张三");赋值,这是编译期就确定要操作User类。而用反射,就算我们不知道类名,只要拿到类的全路径(比如字符串"com.example.User"),就能创建对象并赋值。Class(代表类的字节码对象)、
Field(代表类的字段)、
Method(代表类的方法)、
Constructor(代表类的构造器)。日常开发中,前三个用得最多。@Override、
@Controller、
@Autowired这些都是注解。说白了,注解就是给代码打标签,用来传递额外信息。@FieldName注解,给User类的name字段打上
@FieldName("user_name")的标签,意思就是“这个字段对应数据库里的user_name列”。后面通过反射读取到这个标签,就能知道字段和数据库列的对应关系了。Map(模拟数据库查询结果或前端传参)中,自动给实体类对象赋值。比如
Map里有key为“user_name”和“user_age”的数据,能自动赋给User类的name和age字段。@MapKey,用来标记字段对应的
Map中的key。注解里需要一个属性
value(),用来存储对应的key值。@Target(ElementType.FIELD):表示这个注解只能用在字段上;@Retention(RetentionPolicy.RUNTIME):表示这个注解在运行时仍然有效,这样反射才能读取到。import java.lang.annotation.*;
// 元注解:指定注解作用在字段上
@Target(ElementType.FIELD)
// 元注解:指定注解在运行时保留,允许反射读取
@Retention(RetentionPolicy.RUNTIME)
public @interface MapKey {
// 注解的属性:存储对应Map中的key值
String value();
}
@MapKey注解,明确它们对应的
Map中的key。比如name字段对应Map的“user_name”,age字段对应“user_age”。public class User {
// 标记该字段对应Map中的key为"user_name"
@MapKey("user_name")
private String name;
// 标记该字段对应Map中的key为"user_age"
@MapKey("user_age")
private Integer age;
// 为了方便查看结果,生成getter和toString方法
public String getName() {
return name;
}
public Integer getAge() {
return age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
ReflectionUtils,里面提供一个静态方法
autoSetValue,接收两个参数:要赋值的对象和存储数据的
Map。obj.getClass());getDeclaredFields());@MapKey注解(通过
field.getAnnotation(MapKey.class));field.setAccessible(true));field.set(obj, value))。import java.lang.reflect.Field;
import java.util.Map;
public class ReflectionUtils {
/**
* 实现对象自动赋值
* @param obj 要赋值的对象
* @param dataMap 存储数据的Map(key对应注解值,value对应要赋的值)
*/
public static void autoSetValue(Object obj, Map<String, Object> dataMap) {
// 1. 获取对象的Class字节码对象
Class<?> clazz = obj.getClass();
// 2. 获取类的所有字段(包括私有字段)
Field[] fields = clazz.getDeclaredFields();
// 3. 遍历每个字段
for (Field field : fields) {
// 4. 判断字段上是否有@MapKey注解
MapKey mapKey = field.getAnnotation(MapKey.class);
if (mapKey != null) {
// 5. 拿到注解上的value(即Map中的key)
String key = mapKey.value();
// 6. 从Map中取出对应的值
Object value = dataMap.get(key);
if (value != null) {
try {
// 7. 打开字段的访问权限(私有字段需要这一步)
field.setAccessible(true);
// 8. 给字段赋值(反射的核心操作)
field.set(obj, value);
} catch (IllegalAccessException e) {
// 处理异常(实际开发中建议用日志框架记录)
e.printStackTrace();
}
}
}
}
}
}
Map模拟数据源,然后创建User对象,调用工具类的方法完成自动赋值,最后打印结果看看是否成功。import java.util.HashMap;
import java.util.Map;
public class TestAutoSet {
public static void main(String[] args) {
// 1. 模拟数据源:比如从数据库查询的结果,或前端传的参数
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("user_name", "张三");
dataMap.put("user_age", 25);
// 这里可以加一个无关的key,测试不会赋值给没有注解的字段
dataMap.put("user_gender", "男");
// 2. 创建要赋值的对象
User user = new User();
System.out.println("赋值前:" + user); // 此时name和age都是默认值null和null
// 3. 调用工具类自动赋值
ReflectionUtils.autoSetValue(user, dataMap);
// 4. 打印赋值后的结果
System.out.println("赋值后:" + user);
}
}
赋值前:User{name='null', age=null}
赋值后:User{name='张三', age=25}
@MapKey注解增加一个
defaultValue属性,默认值为空字符串。import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MapKey {
// 对应Map中的key
String value();
// 默认值,当Map中没有对应key时使用
String defaultValue() default "";
}
import java.lang.reflect.Field;
import java.util.Map;
public class ReflectionUtils {
public static void autoSetValue(Object obj, Map<String, Object> dataMap) {
Class<?> clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
MapKey mapKey = field.getAnnotation(MapKey.class);
if (mapKey != null) {
String key = mapKey.value();
// 1. 优先取Map中的值,没有则取注解的默认值
Object value = dataMap.getOrDefault(key, mapKey.defaultValue());
// 跳过空值(如果默认值也为空,就不赋值)
if (value == null || "".equals(value)) {
continue;
}
try {
field.setAccessible(true);
// 2. 根据字段类型进行类型转换
Object convertValue = convertValue(value, field.getType());
// 3. 赋值
field.set(obj, convertValue);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
/**
* 类型转换:把Map中的值转换成字段对应的类型
* @param value 要转换的值
* @param targetType 目标类型(字段的类型)
* @return 转换后的值
*/
private static Object convertValue(Object value, Class<?> targetType) {
// 如果值的类型已经和目标类型一致,直接返回
if (targetType.isInstance(value)) {
return value;
}
String valueStr = value.toString().trim();
// 根据目标类型进行转换
if (targetType == Integer.class || targetType == int.class) {
return Integer.parseInt(valueStr);
} else if (targetType == Long.class || targetType == long.class) {
return Long.parseLong(valueStr);
} else if (targetType == Boolean.class || targetType == boolean.class) {
return Boolean.parseBoolean(valueStr);
} else if (targetType == Double.class || targetType == double.class) {
return Double.parseDouble(valueStr);
}
// 其他类型暂时直接返回字符串(实际开发中可扩展)
return valueStr;
}
}
public class User {
@MapKey("user_name")
private String name;
// 增加默认值18
@MapKey(value = "user_age", defaultValue = "18")
private Integer age;
// 新增一个Integer类型的score字段,对应Map中的字符串值
@MapKey("user_score")
private Integer score;
// 生成getter和toString方法
public String getName() {
return name;
}
public Integer getAge() {
return age;
}
public Integer getScore() {
return score;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
import java.util.HashMap;
import java.util.Map;
public class TestAutoSet {
public static void main(String[] args) {
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("user_name", "李四");
// 不存user_age,测试默认值
dataMap.put("user_score", "95"); // 字符串类型的数值
User user = new User();
ReflectionUtils.autoSetValue(user, dataMap);
System.out.println("赋值后:" + user);
}
}
赋值后:User{name='李四', age=18, score=95}
@Result注解标记数据库列和实体类字段的对应关系,MyBatis就是通过反射读取注解,把查询结果自动封装成实体类对象;@Autowired自动注入、
@Value读取配置文件值,底层都是通过反射+注解实现的——注解标记需要注入的字段,反射找到对应的bean或配置值并赋值;setAccessible(true)可以访问私有字段和方法,这违背了类的封装设计。所以实际开发中,尽量只对自己控制的类使用反射,不要滥用;@Target指定作用范围(比如只能用在字段上),用
@Retention指定保留策略(运行时保留才能用反射读取);setXxx()赋值,其实就是重复劳动。而反射+注解的组合,本质就是把这种重复劳动交给程序自动完成,解放我们的双手。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69963761/viewspace-3103663/ ,如需转载,请注明出处,否则将追究法律责任。