自定义注解结合AOP之实战应用

背景介绍

最近在项目中写了一个公共的上传文件接口,项目中有多个业务场景会使用到上传文件,每个场景对上传的文件类型,文件大小有不同的要求。
按常规操作,我们可以在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

  • 15
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
Java中,我们可以使用自定义注解来实现AOP(面向切面编程)。AOP是一种编程范型,它允许开发者在程序运行时动态地将代码切入到已有代码的特定位置。 下面是一个简单的示例,演示如何使用自定义注解实现AOP。 首先,我们需要定义一个自定义注解: ``` @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Loggable { } ``` 这个注解用来标记需要记录日志的方法。它的@Target注解指定了它只能用于方法上,@Retention注解指定了它的生命周期是运行时。 接下来,我们创建一个切面类,用来实现AOP的逻辑: ``` @Aspect @Component public class LoggingAspect { @Before("@annotation(com.example.Loggable)") public void logMethodCall(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); System.out.println("Method " + methodName + " called"); } } ``` 这个类使用Spring AOP框架提供的@Aspect注解来标记它是一个切面类。它的@Before注解指定了它要在被@Loggable注解标记的方法之前执行。JoinPoint参数包含了被拦截的方法的信息,我们可以从中获取方法名等信息。 最后,在需要记录日志的方法上加上@Loggable注解即可: ``` @Component public class MyService { @Loggable public void doSomething() { // do something } } ``` 当doSomething()方法被调用时,LoggingAspect中的logMethodCall()方法会被执行,记录方法调用信息。 这就是使用自定义注解实现AOP的基本步骤。当然,实际应用中会更加复杂,需要更多的切面逻辑和注解参数等。但是这个简单的示例可以帮助你理解如何使用自定义注解实现AOP
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值