一个使用反射机制的简单示例,这个示例将展示如何使用反射来实现一个通用的数据导出功能。
首先,让我们创建必要的项目结构和文件:
- 首先修改 pom.xml 添加依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.yam</groupId>
<artifactId>reflection-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
- 创建实体类:
package cn.yam.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("user")
public class User {
private Long id;
private String username;
private String email;
private Integer age;
}
- 创建一个自定义注解,用于标记需要导出的字段:
package cn.yam.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExportField {
String value() default "";
}
- 更新实体类,添加注解:
package cn.yam.entity;
import cn.yam.annotation.ExportField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("user")
public class User {
private Long id;
@ExportField("用户名")
private String username;
@ExportField("邮箱")
private String email;
@ExportField("年龄")
private Integer age;
}
- 创建通用的导出工具类:
package cn.yam.util;
import cn.yam.annotation.ExportField;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
public class ExportUtil {
public static <T> List<String[]> exportData(List<T> dataList) {
List<String[]> result = new ArrayList<>();
if (dataList == null || dataList.isEmpty()) {
return result;
}
// 使用反射获取类的所有字段
Class<?> clazz = dataList.get(0).getClass();
Field[] fields = clazz.getDeclaredFields();
// 获取标题行
List<Field> exportFields = new ArrayList<>();
List<String> headers = new ArrayList<>();
for (Field field : fields) {
ExportField exportField = field.getAnnotation(ExportField.class);
if (exportField != null) {
exportFields.add(field);
headers.add(exportField.value());
}
}
// 添加标题行
result.add(headers.toArray(new String[0]));
// 添加数据行
for (T data : dataList) {
List<String> rowData = new ArrayList<>();
for (Field field : exportFields) {
try {
field.setAccessible(true);
Object value = field.get(data);
rowData.add(value == null ? "" : value.toString());
} catch (IllegalAccessException e) {
rowData.add("");
}
}
result.add(rowData.toArray(new String[0]));
}
return result;
}
}
- 创建 Service 和 Controller:
package cn.yam.service;
import cn.yam.entity.User;
import com.baomidou.mybatisplus.extension.service.IService;
public interface UserService extends IService<User> {
}
package cn.yam.service.impl;
import cn.yam.entity.User;
import cn.yam.mapper.UserMapper;
import cn.yam.service.UserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
package cn.yam.controller;
import cn.yam.entity.User;
import cn.yam.service.UserService;
import cn.yam.util.ExportUtil;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
@GetMapping("/export")
public List<String[]> exportUsers() {
List<User> users = userService.list();
return ExportUtil.exportData(users);
}
}
- 创建 Mapper 接口:
package cn.yam.mapper;
import cn.yam.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
- 最后更新主类:
package cn.yam;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("cn.yam.mapper")
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
详细剖析实现细节
好的,我来详细解读 ExportUtil 的实现细节:
- 首先看方法签名:
public static <T> List<String[]> exportData(List<T> dataList)
- 这是一个静态泛型方法,
<T>
表示可以接收任意类型的列表 - 返回类型是
List<String[]>
,即字符串数组的列表,每个数组代表一行数据
- 输入检查:
List<String[]> result = new ArrayList<>();
if (dataList == null || dataList.isEmpty()) {
return result;
}
- 创建结果集容器
- 如果输入列表为空或null,直接返回空结果集
- 获取类的字段信息:
Class<?> clazz = dataList.get(0).getClass();
Field[] fields = clazz.getDeclaredFields();
- 通过第一个对象获取类的Class对象
getDeclaredFields()
获取该类声明的所有字段(不包括继承的字段)
- 处理标题行:
List<Field> exportFields = new ArrayList<>();
List<String> headers = new ArrayList<>();
for (Field field : fields) {
ExportField exportField = field.getAnnotation(ExportField.class);
if (exportField != null) {
exportFields.add(field);
headers.add(exportField.value());
}
}
- 创建两个列表:一个存储要导出的字段,一个存储表头
- 遍历所有字段,检查是否有
@ExportField
注解 - 如果有注解,将字段添加到 exportFields,将注解的value值添加到headers
- 添加标题行到结果集:
result.add(headers.toArray(new String[0]));
- 将headers转换为字符串数组并添加为结果集的第一行
- 处理数据行:
for (T data : dataList) {
List<String> rowData = new ArrayList<>();
for (Field field : exportFields) {
try {
field.setAccessible(true);
Object value = field.get(data);
rowData.add(value == null ? "" : value.toString());
} catch (IllegalAccessException e) {
rowData.add("");
}
}
result.add(rowData.toArray(new String[0]));
}
- 外层循环遍历每个数据对象
- 内层循环遍历要导出的字段
field.setAccessible(true)
允许访问私有字段field.get(data)
获取字段值- 将字段值转换为字符串,如果为null则转换为空字符串
- 如果访问出错(比如没有访问权限),添加空字符串
- 将每行数据转换为字符串数组并添加到结果集
让我用一个具体例子来说明整个过程:
// 示例类
public class User {
@ExportField("姓名")
private String name;
@ExportField("年龄")
private int age;
private String password; // 不导出
}
// 使用示例
List<User> users = Arrays.asList(
new User("张三", 20),
new User("李四", 25)
);
List<String[]> result = ExportUtil.exportData(users);
执行过程:
- 首先获取User类的所有字段(name, age, password)
- 找到带有@ExportField注解的字段(name, age)
- 生成标题行 [“姓名”, “年龄”]
- 处理第一行数据 [“张三”, “20”]
- 处理第二行数据 [“李四”, “25”]
最终结果:
[
["姓名", "年龄"], // 标题行
["张三", "20"], // 数据行1
["李四", "25"] // 数据行2
]
这个示例展示了如何使用反射机制实现一个通用的数据导出功能:
- 我们创建了一个
@ExportField
注解,用于标记需要导出的字段。 - 在
ExportUtil
类中,我们使用反射机制:- 获取类的所有字段 (
clazz.getDeclaredFields()
) - 获取字段上的注解 (
field.getAnnotation(ExportField.class)
) - 动态访问对象的字段值 (
field.get(data)
)
- 获取类的所有字段 (
这样的设计有以下优点:
- 通用性强:可以用于任何带有
@ExportField
注解的实体类 - 可扩展性好:只需要在实体类的字段上添加注解就可以控制导出
- 维护方便:导出逻辑集中在一个工具类中
要运行这个示例,你需要:
- 创建对应的数据库和表
- 在 application.yml 中配置数据库连接信息
- 启动应用并访问
http://localhost:8080/user/export
接口
这个示例展示了反射机制在实际业务中的应用,通过反射我们可以在运行时动态获取类的信息,实现更加灵活和通用的功能。