Spring通过自定义注解和 AOP 简化 Spring 项目中的 Excel 操作

前置知识点

说明

自定义注解是一种元编程的方式,即编写能影响其他代码行为的代码。
:::

原理
  1. Java 提供了反射等特性,可以在编译期和运行期访问到类或者方法,由此衍生出来了注解的机制,这为代码的动态生成和执行提供了基础。
  2. spring 作为 Java 的一层封装,底层还是通过反射和注解的机制,通过依赖注入,实现对象的动态创建,由此 spring 容器获得对象的控制和管理权。
    :::

  1. spring随后对Java的注解机制进行了再次的封装,提供了自定义注解的方法
  2. @interface 来创建注解;
  3. @Retention 定义注解生效的作用域/使用范围(运行时等)
  4. @Target 来定义注解生效的对象是哪些 Java 代码(类还是方法)
    :::

导入依赖

阶段性目标

在这个阶段,我们的目标是为项目添加所有必要的库和依赖。

  1. 添加 EasyExcel 依赖
    • 在项目的 pom.xml 文件或者其他依赖管理文件中,添加 EasyExcel 的依赖。
    • 确保依赖没有冲突,并且与项目其他依赖兼容。
  2. 配套依赖
    • 如果项目需要其他额外库(例如日志库、测试库等),也在这个阶段进行添加。
      :::
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>easyexcel</artifactId>
  <version>2.2.6</version>
</dependency>

实体类

阶段性目标

定义一个或多个实体类,这些实体类将用于映射 Excel 文件的数据。

  1. 基本字段
    • 根据需求和 Excel 文件的结构,为实体类定义相应的字段。
  2. 类型匹配
    • 确保实体类字段的类型与 Excel 文件中的相应列匹配。
  3. 命名规范
    • 遵循 Java 命名规范,使得代码易于阅读和维护。
  4. 其他自定义方法(可添加一些处理在实体类)
    • 比如数据校验方法或者格式转换方法。

通过这个阶段,我们应该得到一个或多个定义完善的实体类,这些实体类将用于后续的 Excel 数据导入和导出操作。
:::

@Data

public class User {

    private String username;

    private String email;

    ......

}

创建ExcelService

阶段性目标

我们需要创建一个服务类(或工具类)ExcelService,它将负责实现 Excel 文件的导入和导出功能。该类将使用 EasyExcel 库。
实现任务如下:

  1. 导出功能
    • 从前端接收一个实体类列表(例如 List),这个列表包含了需要导出到 Excel 的所有数据。
    • 使用 EasyExcel API 来创建一个 Excel 文件,并将接收到的数据写入到这个文件中。
    • 将创建的 Excel 文件转换成字节数组,并返回给前端,以便用户可以下载。
  2. 导入功能
    • 从前端接收一个 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 文件的导入和导出操作。具体目标如下:

  1. 定义切点,确定需要拦截的方法或者注解,为这些方法或注解定义切点。
  2. 编写前置、后置逻辑(Advice):在 AOP 的 Advice 中,编写导入或导出 Excel 文件的核心逻辑。这里会调用 ExcelService 中的方法。
  3. 参数解析和转换:在 AOP 切面内部,对 Controller 方法的参数进行解析和转换,以符合 ExcelService 方法的需求。
  4. 异常处理:添加适当的异常处理逻辑,确保在出错时能给出明确的错误信息。
  5. 日志记录:在关键步骤添加日志记录,以便于后续的问题排查。
  6. 返回值处理:根据业务需求,处理 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;
    }
}

待优化点
  1. 导入数据库的响应返回结果:我们在 AOP 切面里进行了上传 Excle 到数据库,需要给一个明确的响应结果,以便调用方了解操作是否成功。
  2. 异常处理:在 AOP 切面里,我们可以捕获 Service 层或其他地方抛出的异常,并进行统一的异常处理。这样可以使 Controller 和 Service 层代码更加简洁。
  3. 日志记录:在 AOP 切面里,我们可以方便地添加日志记录逻辑,这样就能轻易追踪每一次导入或导出操作的详细情况。
  4. 权限检查:如果只有特定角色的用户允许进行 Excel 导入/导出操作,这些逻辑也可以在 AOP 切面里进行。
  5. 数据校验:在将数据保存到数据库之前,可以进行一些基础的数据有效性检查。
  6. 性能监控:可以在 AOP 切面中添加一些代码来监控导入/导出操作的性能,如操作耗时等。
  7. 文件大小和格式限制:在处理上传的文件之前,可以检查文件的大小和格式是否符合预期。
    :::

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;
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值