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(); } } } } }
使用
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- Spring IOC中的Bean對象用法
- 使用註解@Validated和BindingResult對入參進行非空校驗方式
- SpringBoot如何使用ApplicationContext獲取bean對象
- 如何使用Bean Validation 解決業務中參數校驗
- Spring 中@Validated 分組校驗的使用解析