运用spring aop+注解+反射,优雅实现参数校验和修改

231 阅读2分钟

背景:

前端有这样一个时间组件,可以传入两个时间戳(beginDateendDate)作为时间范围,\color{purple}{前端有这样一个时间组件,可以传入两个时间戳(beginDate、endDate) 作为时间范围,} \color{purple}{而开始和结束时间如果为同一天那么就只能查询一秒钟的数据,这显然是有问题的 ,}$$\color{purple}{想要查询7.29当天数据我们需要把endDate的时间往后加上一天}

直接解决:

Date beginDate=param.getBeginDate();
Date endDate=param.getEndDate();
if ((beginDate != null && endDate != null) && beginDate.equals(endDate)) {
                //如果传入的开始和结束时间一样则结束时间加一天
                c.setTime(beginDate);
                c.add(Calendar.DAY_OF_MONTH, 1);
                Date endTime = c.getTime();
                endDateMap.get(arg.getClass()).set(arg, endTime);
            }

但是这样的话每一个查询时间范围查询方法中都要加这么一段代码,很影响整洁性,\color{red}{但是这样的话每一个查询时间范围查询方法中都要加这么一段代码,很影响整洁性,}

因此考虑使用对方法体无侵入的注解方式实现这个入参修改\color{red}{因此考虑使用对方法体无侵入的注解方式实现这个入参修改}

自定义注解+AOP:

首先项目里要确保引入了

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

然后自定义注解,用于标注时间范围查询方法

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TimeSpanCheck {

    //String beginDateFieldName();

    //String endDateFieldName();
}

原本想直接手动定义beginDate和endDate字段名,但想了想这样并不好,如果字段改名这里很可能会因为没有一起改而报错, 既然我们都已经使用了注解做标识那不如再使用一次

@Documented
//注意这里的@Target和上面不同,说明该注解用于标注变量
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BeginDate {
}

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EndDate {
}

定义完注解我们把它加到对应的方法和字段上,然后就可以用反射获取了

接下来就可以写aop类了,这里直接上代码+注释

@Component
@Aspect
@Slf4j
public class TimeSpanCheckAop {
    private Calendar c = Calendar.getInstance();
    //缓存Field避免每次方法调用都做反射的开销
    private Map<Class, Field> beginDateMap = new HashMap<>();
    private Map<Class, Field> endDateMap = new HashMap<>();

    //切点,也就是aop需要执行的地方(在方法实现package中和有@TimeSpanCheck注解的方法)
    @Pointcut("execution(* xxx.xxx.service.impl..*(..)) && @annotation(xxx.xxx.service.aop.TimeSpanCheck)")
    public void pointCut() {
    }

    /**
     * 如果方法上有@TimeSpanCheck注解且begindate和enddate相同,enddate加一天
     */
    @Around("pointCut()")
    public Object TimeSpanCheck(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        TimeSpanCheck timeSpanCheck = method.getAnnotation(TimeSpanCheck.class);
        if (timeSpanCheck == null) {
            return true;
        }

        Object arg = args[0];
        if (beginDateMap.get(arg.getClass()) == null && endDateMap.get(arg.getClass()) == null) {
            //遍历入参变量,找到标注了@BeginDate @EndDate的Field并存入缓存map
            for (Field field : arg.getClass().getDeclaredFields()) {
                BeginDate beginDate = field.getAnnotation(BeginDate.class);
                EndDate endDate = field.getAnnotation(EndDate.class);

                if (beginDate != null) {
                    field.setAccessible(true);
                    beginDateMap.put(arg.getClass(), field);
                }
                if (endDate != null) {
                    field.setAccessible(true);
                    endDateMap.put(arg.getClass(), field);
                }
            }
        }
        //获取Field并修改入参
        Field beginDateField = beginDateMap.get(arg.getClass());
        Field endDateField = endDateMap.get(arg.getClass());
        if (beginDateField != null && endDateField != null) {
            Date beginDate = (Date) beginDateField.get(arg);
            Date endDate = (Date) endDateField.get(arg);
            if ((beginDate != null && endDate != null) && beginDate.equals(endDate)) {
                //如果传入的开始和结束时间一样则结束时间加一天
                c.setTime(beginDate);
                c.add(Calendar.DAY_OF_MONTH, 1);
                Date endTime = c.getTime();
                endDateMap.get(arg.getClass()).set(arg, endTime);
            }
        }
        //原方法执行
        return joinPoint.proceed(args);
    }


}

验证:

可以看到,执行完方法后endDate变成了beginDate+1天,达到了我们想要的效果\color{blue}{可以看到,执行完方法后 endDate 变成了 beginDate + 1天,达到了我们想要的效果}