aop方式的参数校验

背景

由于历史背景,公司项目中的接口都使用json格式入参,使用com.alibaba.fastjson.JSONObject来接收,形如

public @ResponseBody
    Result xxx(@RequestBody JSONObject reqJsonObj) {
        String a = reqJsonObj.getString("a");
        String b = reqJsonObj.getString("b");
        String c = reqJsonObj.getString("b");
        String d = reqJsonObj.getString("b");
        String e = reqJsonObj.getString("b");

        if (StringUtils.isEmpty(a)) {
            logger.error("参数不全");
            return new ErrorResult();
        }
        ...

所有接口都是如此风格,想来大家师承一家。现在说说我的修改

参数校验注解

在SpringMVC中提供了很多校验参数的注解,比如@NotNull等。那么我们直接使用注解岂不美滋滋,说干就干,创建实体,在需要参数的上面加规则,执行,没作用。。。

public @ResponseBody
    Result xxx(@RequestBody ClassA obj) {

啊,没加@Valid

public @ResponseBody
    Result xxx(@RequestBody @Valid ClassA obj) {

起作用了,客户端调用会报400,但是这样太粗暴了,系统希望给客户端返回一个错误信息,而不是直接抛异常。那么这里就需要配合BindingResult使用,

public @ResponseBody
    Result xxx(@RequestBody @Valid ClassA obj, BindingResult bindingResult) {

这里Spring会将错误信息注入到BindingResult中,然后我们通过判断BindingResult来处理后续操作,例如

if(bindingResult.hasError()) {
    return new ErrorResult(bindingResult.getFieldError().getDefaultMessage())
}

这里可以获取到注解中写入到信息,比如

@NotNull(message = "名字不能为空")
    private String name;

这里我多加一步操作,因为我们需要返回一些事先和客户端定义好的状态码,所以我自定了错误码注解,然后在报错的地方获取Field,再获取到错误码。

String fieldName = bindingResult.getFieldError().getField();
        Field field = null;
        try {
// klass 为需要校验的类
            field = klass.getDeclaredField(fieldName);
        } catch (NoSuchFieldException e) {

            e.printStackTrace();
        }
        
        if (field != null && field.isAnnotationPresent(ErrorParameter.class)) {

            ErrorParameter errMsg = field.getAnnotation(ErrorParameter.class);

            if (errMsg != null) { // 获取定义的异常码

                code = errMsg.errorCode();
            }
        }

满心欢喜再次测试,嗯,没啥问题,正经测试下,模拟真实数据,然后400…Orz

原因是,入参比我们接收实体中的参数多,所以导致报错,这个关于@RequestBody的知识点自己是真不知道,罪过罪过,挖坑,后面会写Spring学习笔记。

原因找到了,怎么解决呢,虽然多出来的参数后台并不需要(为什么传。。。),但是线上已经是这样了,客户端也无法修改(也不会改~.~),所以只能我这里处理。

要不还是改回去吧,原来的代码也没那么难看。。。
不行不行。。。

自定义注解校验

既然无法使用现成的方法,那就自己写。思路:使用JSONObject接收参数,然后转换为自定义实体类,然后校验实体类参数,最后返回结果。

// 转换
AForm form = jsonObject.toJavaObject(AForm.class);
// 通用校验方法 object 就是上面的form
Field[] fieldArray = object.getClass().getDeclaredFields();

        for (Field field : fieldArray) { // 遍历属性

            if (field.isAnnotationPresent(MyRule.class)) { // 拿出需要检查的属性
                // 自定义校验规则注解
                MyRule rule = field.getAnnotation(MyRule.class);
                String fieldName = field.getName();
                MyRuleEnum[] ruleEnums = rule.rules();

                if (!ruleCheck(ruleEnums, field, object)) { // 检查不通过则返回想要的结果
                    return false;
                }
            }
        }
// 这里有点丑
private static boolean check(MyRuleEnum[] myRuleEnum, Field field, Object object) {
        // 这里就是拼一个get方法
        Object value = getValue(field, object);

        for (MyRuleEnum rule : myRuleEnum) {

            switch (rule) {
                case NOT_NULL_RULE:
                    if (value == null)
                        return false;
                    break;
                case NOT_BLANK_RULE:
                    if (value == null)
                        return false;
                    if (value instanceof String) {

                        String tmp = (String) value;
                        if ("".equals(tmp))
                            return false;
                    }
                    break;
            }
        }

        return true;
    }

自定义注解如下

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MyRule {

    MyRuleEnum[] rules() default MyRuleEnum.NOT_NULL_RULE;

    int errorCode() default 1;

    String errorMsg() default "";

}

那么现在接口部分大概如下

// 转换
AForm form = jsonObject.toJavaObject(AForm.class);
if(!CommonUtil.check(form)) {
    // 错误执行代码
}

运行测试通过~~

嗯。。。有点不舒服,每个接口都要写一遍~~来吧,再改下

aop吧~

我的需求很简单,就是在接口入参的地方对入参进行一些校验,校验规则事先定义好,最好还能返回自定义的一些信息,这些需求上面的代码都已经实现,现在再整理下需求,发现一个点,入参的地方,那不就是aop么,在入参的地方一切,悄悄做些操作,接口也不知道,美滋滋,好,干~

思路:拦截controller层,获取入参,获取入参类型,重复上面的校验方法。

aop配置这里就不说了;因为项目中接口很多,不可能一次改完,而且还要测试,所以只能一点一点改,没有修改的接口最好不受影响,所以这里我定义一个注解,表示这个接口需要进行校验。并且可以声明这个接口的入参类型,一举两得 美滋滋。

@MyAPICheck
public @ResponseBody
    Result xxx(@RequestBody JSONObject reqJsonObj)  
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAPICheck {

    Class klass() default SomeClass.class;
}

那现在就剩拦截获取注解了

public Object handle(ProceedingJoinPoint pjd) throws Throwable {

        Object[] objects = pjd.getArgs();

        if (objects != null && objects.length > 0) {
// 没有参数就直接跳过
            Class klass = pjd.getTarget().getClass();
            Method method = ((MethodSignature) pjd.getSignature()).getMethod();

            if (method.isAnnotationPresent(MyAPICheck.class)) {
// 没有这个注解就跳过
                MyAPICheck MyAPICheck = method.getAnnotation(MyAPICheck.class);
                Class parameterClass = MyAPICheck.klass();

                Object arg0 = objects[0];

                JSONObject jsonObject = (JSONObject) arg0;
                Object form = jsonObject.toJavaObject(parameterClass);
                
                if(!CommonUtil.check(form)) {
                    // 错误执行代码
                }
            }
        }

        return pjd.proceed();
    }

到此算是真的把自己坑进去了。。。前面说到希望可以温和的返回异常结果,但是现在这里只能抛异常。。。Orz

要不改回原来的版本吧,能用就行,要啥自行车~
不行不行。。。

aop 异常拦截

不幸中的万幸,上面抛出的异常进入了项目controller层的异常拦截(之前的@valid导致的400异常为什么不进?啊,我的Spring~),也是使用aop实现,在这里我只需要根据异常类型返回需要的错误结果即可。

总结

到此一次入参校验的“小改动”算是完成了。。。

ProceedingJoinPoint.getTarget() 获取拦截的对象
Method method = ((MethodSignature) pjd.getSignature()).getMethod();获取拦截的方法

急需补充Spring的知识。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值