背景介绍
最近在项目中写了一个公共的上传文件接口,项目中有多个业务场景会使用到上传文件,每个场景对上传的文件类型,文件大小有不同的要求。
按常规操作,我们可以在Controller层提供多个接口,然后在每个接口里写if去校验;或者是在一个接口里定义类型去区分不同的业务场景,再分别写if去校验;总而言之,就是要写if去校验。
然后呢,我就不想写if校验,觉得重复代码太多,不够优雅。于是考虑能否通过类似@RequestParam这样的注解,入参上加上一个简单注解就能实现校验。
好了,废话不多说,开始干吧。
步骤流程
首先贴一下项目目录结构
1. 定义注解
如果小伙伴们对如何自定义注解存在疑惑的话,请先阅读这篇文章自定义注解详细介绍
- 定义用于参数上的注解
package com.example.demo.aop.annotation;
import java.lang.annotation.*;
/**
* @author Dong
*/
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FileParam {
/**
* 文件后缀
*/
public String[] suffix() default {"doc", "xls", "ppt", "png", "txt"};
/**
* 文件大小
*/
public int size() default 1024;
}
- 定义用于方法上的注解
package com.example.demo.aop.annotation;
import java.lang.annotation.*;
/**
* @author Dong
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FileValid {
}
2.将注解应用于方法和参数
package com.example.demo.controller;
import com.example.demo.aop.annotation.FileParam;
import com.example.demo.aop.annotation.FileValid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author Dong
*/
@RestController
@RequestMapping("/file")
@Slf4j
public class AopTestController {
@PostMapping("/upload")
@FileValid
public String upload(@FileParam(suffix = {"doc"}) MultipartFile file, HttpServletRequest request, HttpServletResponse response) {
log.info("in the method ...");
return "success";
}
}
到这里之后,你运行项目会发现,并没有什么卵用啊,是不是很气,气就对了
所以你应该认识到,如果只是有注解,对项目其实是不起任何作用的,因为它只是相当于一个标记,真正要让它起作用,那就得写一个能识别这些注解,并且在识别到这些注解之后能做出一系列操作的处理器。这就是Java里面强大的反射,或者是Spring的AOP。
如果小伙伴们对AOP不是很了解的话,可以参考这篇文章SpringAOP详细配置与使用
3.定义切面
package com.example.demo.aop.aspect;
import com.example.demo.aop.annotation.FileParam;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
/**
* @author Dong
*/
@Component
@Aspect
@Slf4j
public class FileValidAspect {
@Pointcut("@annotation(com.example.demo.aop.annotation.FileValid)")
public void pointcut() {
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint pjp) {
// 参数值
Object[] args = pjp.getArgs();
// 方法
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
// String name = pjp.getSignature().getName();
// Method method1 = null;
// try {
// method1 = pjp.getTarget().getClass().getMethod(name, MultipartFile.class, HttpServletRequest.class, HttpServletResponse.class);
// } catch (NoSuchMethodException e) {
// e.printStackTrace();
// }
// 参数列表
Parameter[] mParameters = method.getParameters();
for (int i = 0; i < mParameters.length; i++) {
// 判断参数上是否修饰了注解
if (mParameters[i].isAnnotationPresent(FileParam.class)) {
// 获取注解进而得到注解上的参数值
Annotation annotation = mParameters[i].getAnnotation(FileParam.class);
String[] suffixs = ((FileParam) annotation).suffix();
int size = ((FileParam) annotation).size();
log.info("suffixs: {}, size: {}", suffixs, size);
// 实际文件大小
long rSize = 0L;
// 实际文件后缀
String suffix = null;
if (args[i] instanceof MultipartFile) {
MultipartFile temp = ((MultipartFile) args[i]);
rSize = temp.getSize();
suffix = temp.getOriginalFilename().split("\\.")[1];
log.info("suffix: {}, size: {}", suffix, rSize);
}
if (rSize > size) {
return String.format("文件大小:%sByte,超过限定大小:%sByte", rSize, size);
}
if (!Arrays.asList(suffixs).contains(suffix)) {
return String.format("不支持文件上传类型:%s", suffix);
}
}
}
try {
return pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return "error";
}
@Before("pointcut()")
public void before() {
log.info("before ...");
}
@AfterReturning("pointcut()")
public void afterReturning() {
log.info("afterReturning ...");
}
@After("pointcut()")
public void after() {
log.info("after ...");
}
@AfterThrowing("pointcut()")
public void afterThrowing() {
log.info("afterThrowing ...");
}
}
4.测试结果
到此呢,我们整个实战的代码就撸完了,下面跑项目看结果
致谢
最后感谢几位大佬的美文:
1.自定义注解详细介绍 https://blog.csdn.net/xsp_happyboy/article/details/80987484
2.SpringAOP详细配置与使用 https://blog.csdn.net/u010890358/article/details/80640433
3.AOP获取方法的参数名和参数值 https://blog.csdn.net/csq676622362/article/details/105098089