AOP + Aviator calibration parameters to achieve

During the development process, can not always be avoided is the checksum parameter, parameter validation and business code coupled together, the code becomes more and more bloated, impact of post-maintenance, beautiful code is not enough.

Aviator is Google 表达式求值引擎. Use Aviatorprimarily to verify the parameters. It supports most of the arithmetic operators, including arithmetic operators, relational operators, logic operators, regular matching operator (~ =), ternary expressions:?, And the operator support bracket forced priority and priority.

Because Aviator used in previous projects, and I used Assert断言to check the parameters. Because Assert断言thrown abnormalities IllegalArgumentExceptionmay throw an exception for user-unfriendly. Why we want to develop something of a parameter check.

rely

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.9.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.ler</groupId>
	<artifactId>jcheck</artifactId>
	<version>1.0.0-SNAPSHOT</version>
	<name>jcheck</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

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

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>

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

		<!--AOP依赖-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>

		<!--Aviator依赖-->
		<dependency>
			<groupId>com.googlecode.aviator</groupId>
			<artifactId>aviator</artifactId>
			<version>3.3.0</version>
		</dependency>

		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.56</version>
		</dependency>

		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.8.1</version>
		</dependency>

		<!--swagger-->
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger-ui</artifactId>
			<version>2.9.2</version>
		</dependency>

		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger2</artifactId>
			<version>2.9.2</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>
复制代码

First thought was that comment, but because there will be more general calibration parameters, it is necessary to use more annotations on the same method. But before Java8, with a note that can not be reused in the same position.

Although you can reuse notes, in fact, this is a syntactic sugar, but it is still more annotations to use a container wrapped up after compilation.

Here is the comment:

package com.ler.jcheck.annation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author lww
 */
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
//这个注解就是可以让一个注解同一个方法上标注多次
@Repeatable(CheckContainer.class)
public @interface Check {

	String ex() default "";

	String msg() default "";

}
复制代码
  • exIs the need to check the expression, you can use regular expressions. key is a parameter name, JOSN object, then, is the key parameter name attribute, see the examples below specifically.
  • msgYou are prompted an error message and in line with global exception blocker use.
  • Parameter check sequence, the sequence is annotated.
package com.ler.jcheck.annation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author lww
 */
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckContainer {

	Check[] value();
}
复制代码

The comment is a container, when a plurality of annotations, the compiler will use the annotations to wrap the plurality of the same note. So AOP section, should be monitored Checkand CheckContainer.

Core classes AopConfig

package com.ler.jcheck.config;

import com.googlecode.aviator.AviatorEvaluator;
import com.ler.jcheck.annation.Check;
import com.ler.jcheck.annation.CheckContainer;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.util.StringUtils;

/**
 * @author lww
 * @date 2019-09-03 20:35
 */
@Aspect
@Configuration
public class AopConfig {

	/**
	 * 切面,监视多个注解,因为一个注解的时候是Check 多个注解编译后是CheckContainer
	 */
	@Pointcut("@annotation(com.ler.jcheck.annation.CheckContainer) || @annotation(com.ler.jcheck.annation.Check)")
	public void pointcut() {
	}

	@Before("pointcut()")
	public Object before(JoinPoint point) {
		//获取参数
		Object[] args = point.getArgs();
		//用于获取参数名字
		Method method = ((MethodSignature) point.getSignature()).getMethod();
		LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
		String[] paramNames = u.getParameterNames(method);

		CheckContainer checkContainer = method.getDeclaredAnnotation(CheckContainer.class);
		List<Check> value = new ArrayList<>();

		if (checkContainer != null) {
			value.addAll(Arrays.asList(checkContainer.value()));
		} else {
			Check check = method.getDeclaredAnnotation(Check.class);
			value.add(check);
		}
		for (int i = 0; i < value.size(); i++) {
			Check check = value.get(i);
			String ex = check.ex();
			//规则引擎中null用nil表示
			ex = ex.replaceAll("null", "nil");
			String msg = check.msg();
			if (StringUtils.isEmpty(msg)) {
				msg = "服务器异常...";
			}

			Map<String, Object> map = new HashMap<>(16);
			for (int j = 0; j < paramNames.length; j++) {
				//防止索引越界
				if (j > args.length) {
					continue;
				}
				map.put(paramNames[j], args[j]);
			}
			Boolean result = (Boolean) AviatorEvaluator.execute(ex, map);
			if (!result) {
				throw new UserFriendlyException(msg);
			}
		}
		return null;
	}
}
复制代码

Notes that very clear. Let's look at the specific use.

Use of the Controller

General parameters

	@ApiOperation("测试普通参数")
	@ApiImplicitParams({
			@ApiImplicitParam(name = "name", value = "姓名"),
			@ApiImplicitParam(name = "age", value = "年龄"),
			@ApiImplicitParam(name = "phone", value = "手机号"),
			@ApiImplicitParam(name = "idCard", value = "身份证号"),
	})
	@GetMapping("/simple")
	@Check(ex = "name != null", msg = "姓名不能为空")
	@Check(ex = "age != null", msg = "年龄不能为空")
	@Check(ex = "age > 18", msg = "年龄要大于18岁")
	@Check(ex = "phone != null", msg = "手机号不能为空")
	@Check(ex = "phone =~ /^(1)[0-9]{10}$/", msg = "手机号格式错误")
	@Check(ex = "string.startsWith(phone,\"1\")", msg = "手机号要以1开头")
	@Check(ex = "idCard != null", msg = "身份证号不能为空")
	//不先判空 com.googlecode.aviator.exception.ExpressionRuntimeException
	@Check(ex = "idCard =~ /^[1-9]\\d{5}[1-9]\\d{3}((0[1-9])||(1[0-2]))((0[1-9])||(1\\d)||(2\\d)||(3[0-1]))\\d{3}([0-9]||X)$/", msg = "身份证号格式错误")
	//没有,不会抛出 NoSuchMethodException 或者 NullPointerException 异常
	@Check(ex = "gender == 1", msg = "性别")
	@Check(ex = "date =~ /^[1-9][0-9]{3}-((0)[1-9]|(1)[0-2])-((0)[1-9]|[1,2][0-9]|(3)[0,1])$/", msg = "日期格式错误")
	@Check(ex = "date > '2019-12-20 00:00:00:00'", msg = "日期要大于 2019-12-20")
	public HttpResult simple(String name, Integer age, String phone, String idCard, String date) {
		System.out.println("name = " + name);
		System.out.println("age = " + age);
		System.out.println("phone = " + phone);
		System.out.println("idCard = " + idCard);
		System.out.println("date = " + date);
		return HttpResult.success();
	}
复制代码

To verify the parameter should be non-empty first determination, if the check is not, general parameters are not given, as age > 18. But if it is a regular expression, it will be thrown ExpressionRuntimeException.

When the calibration date, such as date > '2019-12-20 00:00:00:00, should first check the format, because if parameter format can not be compared with the date, Aviator is not comparable. It will not be verified.

If the check is not the argument, the result is false, throw annotations directly in msgthe.

@RequestBody parameters

/*
		{
			"age": 0,
            "bornDate": "string",
            "idCard": "string",
            "name": "string",
            "phone": "string"
		}
	*/
	@ApiOperation("测试 @RequestBody")
	@PostMapping("/body")
	@Check(ex = "user.name != null", msg = "姓名不能为空")
	@Check(ex = "user.age != null", msg = "年龄不能为空")
	@Check(ex = "user.age > 18", msg = "年龄要大于18岁")
	@Check(ex = "user.phone =~ /^(1)[0-9]{10}$/", msg = "手机号格式错误")
	@Check(ex = "user.name != null && user.age != null", msg = "姓名和年龄不能为空")
	//先要检查日期格式,bornDate="string" 这种非正常数据,不会比较大小
	@Check(ex = "user.bornDate =~ /^[1-9][0-9]{3}-((0)[1-9]|(1)[0-2])-((0)[1-9]|[1,2][0-9]|(3)[0,1])$/", msg = "日期格式错误")
	@Check(ex = "user.bornDate > '2019-12-20'", msg = "日期要大于 2019-12-20")
	//@Check(ex = "user.gender == 1", msg = "性别")
	//Caused by: java.lang.NoSuchMethodException: Unknown property 'gender' on class 'class com.ler.jcheck.domain.User'
	public HttpResult body(@RequestBody User user) {
		String jsonString = JSONObject.toJSONString(user);
		System.out.println(jsonString);
		return HttpResult.success();
	}
复制代码

Parameters are passed over in the form of JSON, ex expressions for the key parameter. Attribute name.

Nothing wrong parameter is passed, if you want to pass air, is a transfer {}, check order is the order of the notes. Basic and general parameters above the same, a little different is that if there is no ex properties, throwsjava.lang.NoSuchMethodException

Use the Service in

Parameter calibration using AOP aspects, monitoring Checkand CheckContainertwo annotations, so as long as the agent Spring classes can be used to complete the annotation parameter check.

code show as below:

Controller

	@ApiOperation("添加 在 Service 中校验")
	@PostMapping("/addUser")
	public HttpResult addUser(@RequestBody User user) {
		userService.addUser(user);
		return HttpResult.success();
	}

	@ApiOperation("删除 在 Service 中校验")
	@ApiImplicitParams({
			@ApiImplicitParam(name = "id", value = "id"),
	})
	@PostMapping("/delete")
	public HttpResult delete(Long id) {
		userService.deleteUser(id);
		return HttpResult.success();
	}
复制代码

Service

package com.ler.jcheck.service;

import com.ler.jcheck.domain.User;

/**
 * @author lww
 */
public interface UserService {

	void addUser(User user);

	void deleteUser(Long id);
}
复制代码

ServiceImpl

package com.ler.jcheck.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.ler.jcheck.annation.Check;
import com.ler.jcheck.domain.User;
import com.ler.jcheck.service.UserService;
import org.springframework.stereotype.Service;

/**
 * @author lww
 * @date 2019-10-10 15:33
 */
@Service
public class UserServiceImpl implements UserService {

	@Override
	@Check(ex = "user.name != null", msg = "姓名不能为空")
	public void addUser(User user) {
		System.out.println(JSONObject.toJSONString(user));
	}

	@Override
	@Check(ex = "id != null", msg = "id不能为空!")
	public void deleteUser(Long id) {
		System.out.println("id = " + id);
	}
}
复制代码

In Service, the Controller and in fact in use is the same.

Item code GitHub

Can also be further put this project as a Starter, direct introduction in the development of dependence, ready to use. I can look at my blog creation framework JCheck check their SpringBoot-Starter parameters where the item into a package SpringBoot-Starter, and integrates Swagger configuration, runtime configuration, global exception interceptor, cross-domain configuration. Finally, there blog address Git project, there are some test pictures.

Guess you like

Origin juejin.im/post/5d9ef350e51d45784d3f8645