前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >拦截过滤器模式实现SpringBoot的灵活的参数校验

拦截过滤器模式实现SpringBoot的灵活的参数校验

作者头像
明明如月学长
发布2021-08-31 14:53:15
6290
发布2021-08-31 14:53:15
举报
文章被收录于专栏:明明如月的技术专栏

一、背景

之前针对参数校验提供了一个通用方案(见文末),但是新增一个校验代价比较大,需要修改多个类。

本文结合过滤器拦截器模式,给出一个改进方案,新增校验时只需新增一个校验类即可,并且校验可以指定分组。

这样同样的一个组件,可以实现多个分组,指定分组条件可以让不同的方法只走指定分组的校验。

比如创建和更新的内部和外部校验不一样,但是底层代码时一致的,那么外部参数和内部参数对象不同即可。

如果真的要使用同一个参数对象,就可以指定使用不同的分组即可。

二、代码

2.1 项目结构

2.2 实体

代码语言:javascript
复制
package com.chujianyun.entity.param;

import lombok.Data;

@Data
public class UserParam {
    /**
     * 用户ID
     */
    private Long userId;
    /**
     * 等级
     */
    private Integer level;
}

枚举

代码语言:javascript
复制
package com.chujianyun.entity.enums;

/**
 * 校验分组枚举
 */
public enum UserValidateGroupEnum {
    // 所有条件都校验
    ALL,
    // 某种条件才校验
    SOME
}

结果类

代码语言:javascript
复制
package com.chujianyun.entity.dto;

import lombok.Data;

import java.util.List;

@Data
public class UserDTO {

    private String message;

    public UserDTO() {
    }

    public UserDTO(String message) {
        this.message = message;
    }
}

2.3 校验抽象类

代码语言:javascript
复制
package com.chujianyun.component;

import lombok.Data;

import java.util.Set;

@Data
public abstract class Validator {
    /**
     * 校验分组,枚举
     */
    private Set groups;

    /**
     * 验证参数
     */
    abstract void validate(P param);


}

2.4 具体校验类

等级校验

代码语言:javascript
复制
package com.chujianyun.component;

import com.chujianyun.entity.param.UserParam;
import com.chujianyun.exception.BusinessException;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.stereotype.Component;

@Component
public class UserLevelValidator extends Validator {

    @Override
    public void validate(UserParam param) {
        System.out.println("验证等级");
        if (param == null) {
            throw new BusinessException("");
        }
        // 模拟服务,根据userId查询等级
        boolean isBiggerThan50 = RandomUtils.nextBoolean();
        if (!isBiggerThan50) {
            throw new BusinessException("低于50级");
        }
    }
}

性别校验

代码语言:javascript
复制
package com.chujianyun.component;

import com.chujianyun.entity.param.UserParam;
import com.chujianyun.exception.BusinessException;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.stereotype.Component;

@Component
public class UserSexValidator extends Validator {
    
    @Override
    void validate(UserParam param) {
        System.out.println("验证性别");
        if (param == null) {
            throw new BusinessException("");
        }
        // 模拟服务,根据userId查询性别
        boolean isFemale = RandomUtils.nextBoolean();
        if (!isFemale) {
            throw new BusinessException("仅限女性玩家哦!");
        }
    }
}

某个特殊类型校验

代码语言:javascript
复制
package com.chujianyun.component;

import com.chujianyun.entity.enums.UserValidateGroupEnum;
import com.chujianyun.entity.param.UserParam;
import com.chujianyun.exception.BusinessException;
import org.apache.commons.collections4.SetUtils;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 特殊情况才校验
 */
@Component
public class UserSomeValidator extends Validator {

    @PostConstruct
    public void init() {
        setGroups(Stream.of(UserValidateGroupEnum.SOME).collect(Collectors.toSet()));
    }

    @Override
    void validate(UserParam param) {
        System.out.println("仅检测Some类型的");
        if (param == null) {
            throw new BusinessException("");
        }
        // 模拟服务,根据userId某个条件
        boolean isSome = RandomUtils.nextBoolean();
        if (!isSome) {
            throw new BusinessException("某种条件不满足哦!");
        }
    }
}

2.5 校验链

代码语言:javascript
复制
package com.chujianyun.component;

import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.ResolvableType;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Collectors;

@Component
public class ValidatorChain implements ApplicationContextAware {

    private Map> validatorMap = new HashMap<>();

    /**
     * 参数校验
     */
    public  void checkParam(P param) {
        checkParam(param, validator -> true);
    }

    /**
     * 符合某种条件才参数校验
     */
    public  void checkParam(P param, Predicate predicate) {
        List validators = getValidators(param.getClass());
        if (CollectionUtils.isNotEmpty(validators)) {
            validators.stream()
                    .filter(predicate)
                    .forEach(validator -> validator.validate(param));
        }
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map beansOfType = applicationContext.getBeansOfType(Validator.class);
        this.validatorMap = beansOfType.values().stream().collect(Collectors.groupingBy(validator -> getParamType(validator.getClass())));
    }

    /**
     * 查找相关的所有校验器
     */
    private List getValidators(Class clazz) {
        return validatorMap.get(clazz);
    }

    /**
     * 解析泛型待校验参数类型
     */
    private Class getParamType(Class clazz) {
        ResolvableType resolvableType = ResolvableType.forClass(clazz);
        return resolvableType.getSuperType().getGeneric(0).resolve();
    }
}

构造校验泛型和对应校验器的map

调用时识别参数类型,如果有调用所有校验器或者满足指定条件的校验器。

2.6 服务层

代码语言:javascript
复制
package com.chujianyun.service;

import com.chujianyun.entity.dto.UserDTO;
import com.chujianyun.entity.param.UserParam;

public interface UserService {

    UserDTO checkUser(UserParam userParam);

    UserDTO checkUserSome(UserParam userParam);
}

服务实现

代码语言:javascript
复制
package com.chujianyun.service.impl;

import com.chujianyun.component.ValidatorChain;
import com.chujianyun.entity.dto.UserDTO;
import com.chujianyun.entity.enums.UserValidateGroupEnum;
import com.chujianyun.entity.param.UserParam;
import com.chujianyun.service.UserService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class UserServiceImpl implements UserService {

    @Resource
    private ValidatorChain validatorChain;

    @Override
    public UserDTO checkUser(UserParam userParam) {

        // 参数校验
        validatorChain.checkParam(userParam);
        // 业务逻辑
        return new UserDTO("测试");
    }

    @Override
    public UserDTO checkUserSome(UserParam userParam) {
        // 参数校验(只校验类型为Some的)
        validatorChain.checkParam(userParam, param -> param.getGroups().contains(UserValidateGroupEnum.SOME));

        // 业务逻辑
        return new UserDTO("测试");
    }
}

还可以设置除了分组之外的其他条件,满足条件才会校验。

2.7 控制层

2.8 异常处理

代码语言:javascript
复制
package com.chujianyun.web;

import com.chujianyun.exception.BusinessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class MyExceptionHandler {

    @ExceptionHandler(value = Exception.class)
    public ResponseEntity handle(Exception e) {
        if (e instanceof BusinessException) {
            return new ResponseEntity<>("[业务异常]" + e.getMessage(), HttpStatus.BAD_REQUEST);
        }
        return new ResponseEntity<>("[系统异常]" + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

三、测试

直接执行check校验所有的校验器(下面只给出其中一种情况的截图)

checkSome则只会校验指定的条件

四、总结

设计模式里有一个开闭原则即“对拓展开放,对修改关闭”,本例就是一个实践。

不过还有很多可以改进的地方,大家可以考虑使用接口来实现,欢迎大家一起完善,欢迎提PR。

大家在平时开发时,要多尝试将设计模式使用到项目中,来提高代码的可维护性,灵活性。

源码地址:https://github.com/chujianyun/checkparam

之前的一个通用方案:https://cloud.tencent.com/developer/article/1870280

过滤器拦截器模式:https://www.runoob.com/design-pattern/intercepting-filter-pattern.html

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019/07/13 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、背景
  • 二、代码
    • 2.1 项目结构
      • 2.2 实体
        • 2.3 校验抽象类
          • 2.4 具体校验类
            • 2.5 校验链
              • 2.6 服务层
                • 2.7 控制层
                • 2.8 异常处理
                • 三、测试
                • 四、总结
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档