前置知识点
说明
自定义注解是一种元编程的方式,即编写能影响其他代码行为的代码。
:::
原理
- Java 提供了反射等特性,可以在编译期和运行期访问到类或者方法,由此衍生出来了注解的机制,这为代码的动态生成和执行提供了基础。
- spring 作为 Java 的一层封装,底层还是通过反射和注解的机制,通过依赖注入,实现对象的动态创建,由此 spring 容器获得对象的控制和管理权。
:::
- spring随后对Java的注解机制进行了再次的封装,提供了自定义注解的方法
- @interface 来创建注解;
- @Retention 定义注解生效的作用域/使用范围(运行时等)
- @Target 来定义注解生效的对象是哪些 Java 代码(类还是方法)
:::
导入依赖
阶段性目标
在这个阶段,我们的目标是为项目添加所有必要的库和依赖。
- 添加 EasyExcel 依赖
- 在项目的 pom.xml 文件或者其他依赖管理文件中,添加 EasyExcel 的依赖。
- 确保依赖没有冲突,并且与项目其他依赖兼容。
- 配套依赖
- 如果项目需要其他额外库(例如日志库、测试库等),也在这个阶段进行添加。
:::
- 如果项目需要其他额外库(例如日志库、测试库等),也在这个阶段进行添加。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.6</version>
</dependency>
实体类
阶段性目标
定义一个或多个实体类,这些实体类将用于映射 Excel 文件的数据。
- 基本字段
- 根据需求和 Excel 文件的结构,为实体类定义相应的字段。
- 类型匹配
- 确保实体类字段的类型与 Excel 文件中的相应列匹配。
- 命名规范
- 遵循 Java 命名规范,使得代码易于阅读和维护。
- 其他自定义方法(可添加一些处理在实体类)
- 比如数据校验方法或者格式转换方法。
通过这个阶段,我们应该得到一个或多个定义完善的实体类,这些实体类将用于后续的 Excel 数据导入和导出操作。
:::
@Data
public class User {
private String username;
private String email;
......
}
创建ExcelService
阶段性目标
我们需要创建一个服务类(或工具类)ExcelService,它将负责实现 Excel 文件的导入和导出功能。该类将使用 EasyExcel 库。
实现任务如下:
- 导出功能
- 从前端接收一个实体类列表(例如 List),这个列表包含了需要导出到 Excel 的所有数据。
- 使用 EasyExcel API 来创建一个 Excel 文件,并将接收到的数据写入到这个文件中。
- 将创建的 Excel 文件转换成字节数组,并返回给前端,以便用户可以下载。
- 导入功能
- 从前端接收一个 Excel 文件。
- 使用 EasyExcel API 来读取该 Excel 文件,并将其内容转换为实体类列表(例如 List)。
- 返回转换得到的实体类列表,以便后续操作(例如保存到数据库)。
目标是通过这个服务类,我们可以简化 Controller 层的代码,同时也可以在 AOP 切面中复用这个服务类的方法,从而实现更高级的功能,如日志记录、异常处理等。
:::
实现代码
import com.alibaba.excel.EasyExcel;
import java.io.OutputStream;
import java.util.List;
@Service
public class ExcelService {
// 使用 EasyExcel 进行 Excel 文件的导出操作
public void exportExcel(List<User> userList, OutputStream outputStream) {
EasyExcel.write(outputStream, User.class)
.sheet("Sheet1")
.doWrite(userList);
}
// 使用 EasyExcel 进行 Excel 文件的导入操作
public List<User> importExcel(InputStream inputStream) {
List<User> userList = EasyExcel.read(inputStream)
.head(User.class)
.sheet(0)
.doReadSync();
return userList;
}
}
创建自定义注解
阶段性目标
在这个阶段,我们的目标是创建一组自定义注解,这些注解将用于简化 Excel 文件的导入和导出操作。这些注解将在后续阶段用于 AOP 切面编程,从而极大地简化 Excel 文件的导入和导出操作。
:::
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelExport {
String sheetName() default "Sheet1";
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelImport {
}
知识点:
@Retention(RetentionPolicy.RUNTIME)
这个元注解(注解的注解)指定了你的自定义注解在什么级别可用。RetentionPolicy.RUNTIME 表示该注解不仅被保存到 class 文件中,而且在运行时还能通过反射(reflection)被读取。
@Target(ElementType.METHOD)
这个元注解指定了你的自定义注解可以用在哪些 Java 元素上。ElementType.METHOD 表示这个注解只能用在方法上。
:::
AOP
阶段性目标
在这个阶段,我们将专注于使用 AOP 来封装和优化 Excel 文件的导入和导出操作。具体目标如下:
- 定义切点,确定需要拦截的方法或者注解,为这些方法或注解定义切点。
- 编写前置、后置逻辑(Advice):在 AOP 的 Advice 中,编写导入或导出 Excel 文件的核心逻辑。这里会调用 ExcelService 中的方法。
- 参数解析和转换:在 AOP 切面内部,对 Controller 方法的参数进行解析和转换,以符合 ExcelService 方法的需求。
- 异常处理:添加适当的异常处理逻辑,确保在出错时能给出明确的错误信息。
- 日志记录:在关键步骤添加日志记录,以便于后续的问题排查。
- 返回值处理:根据业务需求,处理 Controller 方法的返回值。例如,在 Excel 导出功能中,我们可能需要返回一个包含 Excel 文件的 HTTP 响应。
通过完成这个阶段,我们应该得到一个功能完整、模块化好的 AOP 切面,它能自动处理 Excel 文件的导入和导出,并且与业务代码解耦。
这样,整个 Excel 文件操作流程就会变得更加简洁和高效。这也是我们这个阶段的最终目标。
:::
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.List;
@Aspect
@Component
public class ExcelAspect {
@Autowired
private ExcelService excelService;
@Pointcut("@annotation(ExcelExport)")
public void excelExportPointcut() {
}
@Pointcut("@annotation(ExcelImport)")
public void excelImportPointcut() {
}
@Around("excelExportPointcut()")
public ResponseEntity<byte[]> handleExcelExport(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
// 假设第一个参数是 List<User> 类型的数据
List<User> users = (List<User>) args[0];
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
excelService.exportExcel(User.class, users, outputStream);
// 在这里可以添加额外的处理逻辑,比如设置HttpHeaders对象
HttpHeaders headers = new HttpHeaders();
headers.setContentDispositionFormData("attachment", "UserData.xlsx");
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
ResponseEntity<byte[]> response = (ResponseEntity<byte[]>) joinPoint.proceed();
// 在这里可以添加额外的处理逻辑,比如设置HttpHeaders对象
// 使用新的 HttpHeaders 对象来创建一个新的 ResponseEntity 对象
ResponseEntity<byte[]> Response = new ResponseEntity<>(response.getBody(), headers, response.getStatusCode());
return Response;
}
@Around("excelImportPointcut()")
public ResponseEntity<String> handleExcelImport(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
// 假设第一个参数是 MultipartFile 类型
InputStream inputStream = ((MultipartFile) args[0]).getInputStream();
List<User> users = excelService.importExcel(User.class, inputStream);
// 调用原方法(这里是 Controller 中的方法)并获取其返回值
ResponseEntity<String> response = (ResponseEntity<String>) joinPoint.proceed();
// 在这里可以添加额外的处理逻辑,比如将 users 保存到数据库
// 这里可以定义一下,比如上传成功的响应结果类response,然后将它返回出去
return response;
}
}
待优化点
- 导入数据库的响应返回结果:我们在 AOP 切面里进行了上传 Excle 到数据库,需要给一个明确的响应结果,以便调用方了解操作是否成功。
- 异常处理:在 AOP 切面里,我们可以捕获 Service 层或其他地方抛出的异常,并进行统一的异常处理。这样可以使 Controller 和 Service 层代码更加简洁。
- 日志记录:在 AOP 切面里,我们可以方便地添加日志记录逻辑,这样就能轻易追踪每一次导入或导出操作的详细情况。
- 权限检查:如果只有特定角色的用户允许进行 Excel 导入/导出操作,这些逻辑也可以在 AOP 切面里进行。
- 数据校验:在将数据保存到数据库之前,可以进行一些基础的数据有效性检查。
- 性能监控:可以在 AOP 切面中添加一些代码来监控导入/导出操作的性能,如操作耗时等。
- 文件大小和格式限制:在处理上传的文件之前,可以检查文件的大小和格式是否符合预期。
:::
Controller 代码调用示例
调用示例
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.List;
@RestController
@RequestMapping("/excel")
public class ExcelController {
@Autowired
private ExcelService excelService;
@ExcelExport
@PostMapping("/export")
public ResponseEntity<byte[]> exportExcel(@RequestBody List<User> users) throws IOException {
// 这里的具体实现逻辑由 AOP 切面处理
// 我们只需要关注业务逻辑,不需要处理 Excel 的读写操作
return null;
}
@ExcelImport
@PostMapping("/import")
public ResponseEntity<String> importExcel(@RequestParam("file") MultipartFile file) throws Exception {
// 这里的具体实现逻辑将 AOP 切面处理
// 我们只需要关注业务逻辑,不需要处理 Excel 的读写操作
return null;
}
}