Spring boot通过切面,实现超灵活的注解式数据校验过程

通过切面,实现超灵活的注解式数据校验

在企业系统的开发中,用户表单输入的场景是会经常遇见的,如何让数据校验脱离于业务代码逻辑,谁也不想在逻辑代码里对字段逐一判断。。。。

Spring MVC的校验方式

在使用Spring MVC时的时候,直接使用hibernate-validator的注解,如下:

public class User {

private Long id;

@NotBlank(message = "name不能为空")

@Size(min = 5, max = 10, message = "字符在5到10个")

private String name;

private String des;

@NotNull

@Max(value = 3, message = "type 参数错误")

@Min(value = 0, message = "type 参数错误")

private Integer type;

@Min(value = 0, message = "参数错误, limit必须大于或等于0")

private int limit;

@Pattern(regexp = "^(true|false)$", message = "参数错误, 参数isActive只能是true或者false")

private String flag;

// setters and getters

然后将User对象作为Controller的参数,交给Spring MVC去帮你校验。

通过切面实现自己的注解式数据校验

这是一个SOA的微服务应用,没有controller和Spring MVC,当然也没有所谓的容器(Tomcat、Jetty),对于来自于client的调用,也要进行参数校验。继续基于hibernate-validator,

参看validator的官方文档

引入依赖:

<dependency>

<groupId>org.hibernate</groupId>

<artifactId>hibernate-validator-cdi</artifactId>

<version>5.4.1.Final</version>

</dependency>

<dependency>

<groupId>org.glassfish</groupId>

<artifactId>javax.el</artifactId>

<version>3.0.1-b08</version>

</dependency>

这里需要引入spring boot和aop的一些知识点,自行去网上google吧。我直接上代码了,谁叫我是代码的搬运工。

定义一个切面:

@Aspect //一个切面

@Configuration // spring boot 配置类

public class RequestParamValidAspect {

private final ValidatorFactory factory = Validation.buildDefaultValidatorFactory();

private final ExecutableValidator methodValidator = factory.getValidator().forExecutables();

private final Validator beanValidator = factory.getValidator();

private <T> Set<ConstraintViolation<T>> validMethodParams(T obj, Method method, Object [] params){

return methodValidator.validateParameters(obj, method, params);

}

private <T> Set<ConstraintViolation<T>> validBeanParams(T bean) {

return beanValidator.validate(bean);

}

@Pointcut("execution(* com.jiaobuchong.commodity.service.*.*(..))")

public void soaServiceBefore(){}

/* * 通过连接点切入 */

@Before("soaServiceBefore()")

public void twiceAsOld1(JoinPoint point) {

// 获得切入目标对象

Object target = point.getThis();

// 获得切入方法参数

Object [] args = point.getArgs();

// 获得切入的方法

Method method = ((MethodSignature)point.getSignature()).getMethod();

// 校验以基本数据类型 为方法参数的

Set<ConstraintViolation<Object>> validResult = validMethodParams(target, method, args);

Iterator<ConstraintViolation<Object>> violationIterator = validResult.iterator();

while (violationIterator.hasNext()) {

// 此处可以抛个异常提示用户参数输入格式不正确

System.out.println("method check---------" + violationIterator.next().getMessage());

}

// 校验以java bean对象 为方法参数的

for (Object bean : args) {

if (null != bean) {

validResult = validBeanParams(bean);

violationIterator = validResult.iterator();

while (violationIterator.hasNext()) {

// 此处可以抛个异常提示用户参数输入格式不正确

System.out.println("bean check-------" + violationIterator.next().getMessage());

}

}

}

}

}

具体的Service

// DemoService.java

public interface DemoService {

void one(@NotNull(message = "不能为null") Integer a, @NotBlank String b);

void two(@NotNull(message = "paramsVo不能为null") ParamsVo paramsVo,

@NotNull(message = "go不能为null") String go);

}

// ParamsVo.java

public class ParamsVo {

@NotBlank(message = "不能为空")

private String name;

@NotBlank

@Length(min = 2, max = 20, message = "不可以为空,最多20个字")

private String desc;

@NotNull

@Valid // 需要加上@Valid注解,不然不会校验到Img对象

private List<Img> imgList;

@NotNull(message = "length不能为null")

@Range(min = 3, max = 100, message = "长度范围3-100")

private Integer length;

// omitted other code

}

public class Img {

@NotNull(message = "img id 不能为null")

private Long id;

@NotBlank(message = "img name 不能为空")

private String name;

// omitted other code

}

运行DemoService:

@Autowired

private DemoService demoService;

@Test

public void testGo() {

demoService.one(null, "");

ParamsVo paramsVo = new ParamsVo();

List<Img> list = new ArrayList<>();

Img img = new Img();

list.add(img);

paramsVo.setImgList(list);

paramsVo.setDesc("你");

paramsVo.setLength(1);

demoService.two(paramsVo, null);

}

运行结果:

method check———不能为空

method check———不能为null

method check———go不能为null

bean check——-img name 不能为空

bean check——-不能为空

bean check——-深度范围3-100

bean check——-img id 不能为null

bean check——-不可以为空,最多20个字

这样比Spring MVC的校验功能还强大了,

// Spring MVC中对下面这样的校验是没有作用的

void one(@NotNull(message = "不能为null") Integer a, @NotBlank String b);

经过一番改造后,啥都支持了。而且独立于业务逻辑,维护和新增校验都很方便,代码量也变少了!

Spring boot aop注解数据权限校验

注解类

@Retention(RetentionPolicy.RUNTIME)

public @interface DataAuthValid

{

//位置

public int index() default 0;

//字段 id

//public String id() default "id";

//字段 id

public String orgId() default "org_id";

//mapper

@SuppressWarnings("rawtypes")

public Class<? extends Mapper> mapper();

}

AOP切面

@Aspect

@Component

@Order(1)

public class DataAuthAop {

private static String types = "java.lang.String,java.lang.Long,long";

@Before("@annotation(dataAuth)")

public void beforeMethod(JoinPoint point,DataAuthValid dataAuth) throws Exception {

HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

Map<String, Object> payloadMap = (Map<String, Object>) request.getAttribute("payloadMap");

Long companyid = Long.parseLong(payloadMap.get("companyid")+"");

if(companyid != 1) {

Object[] args = point.getArgs();

Object obj = args[dataAuth.index()];

String ids = null;

String typeName = obj.getClass().getTypeName();

if(types.contains(typeName)) {

ids = obj + "";

}else {

Field[] fields = obj.getClass().getDeclaredFields();

for (Field f : fields) {

f.setAccessible(true);

if("id".equals(f.getName())) {

Long id = (Long) f.get(obj);

ids = id + "";

}

}

}

String[] idArr = ids.split(",");

for (String id : idArr) {

Class cla = dataAuth.mapper();

Mapper mapper = (Mapper) SpringBeanFactoryUtils.getApplicationContext().getBean(cla);

Object object = mapper.selectByPrimaryKey(Long.valueOf(id));

Field field = obj.getClass().getDeclaredField(dataAuth.orgId());

field.setAccessible(true);

Long orgId = (Long)field.get(obj);

if(!companyid.equals(orgId)) {

throw new RuntimeException();

}

}

}

}

}

使用

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。

以上是 Spring boot通过切面,实现超灵活的注解式数据校验过程 的全部内容, 来源链接: utcz.com/p/251244.html

回到顶部