介绍
1、小编是开发新手,如果有写的不好的地方或者缺乏优化的请您指教哈。
2、虽然说一些工具网上有很成熟的解决方案,但是我们动手写一写也可以巩固一下我们开发技术。
3、本篇文章知识点:Spring AOP、Spring 全局异常处理,注解开发、JAVA反射
DEMO 下载
码云:https://gitee.com/chen_haidong/paramsVerify
csdn:https://download.youkuaiyun.com/download/qq_34916296/86404318
效果预览
枚举消息
用枚举来存储一些常用的返回消息,后期维护在这添加或修改即可
public enum MSG {
// TODO 20xxx 基础消息
SUCCESS(20000,"操作成功",true),
ERROR(20001,"操作失败",false),
// TODO 30xxx 身份证认证
LOGIN_ERROR(30000,"账号或密码错误",false),
USER_STATUS_ERROR(30001,"账号被停用",false),
SMS_CODE_ERROR(30002,"短信验证码错误",false),
VERIFY_CODE_ERROR(30002,"验证码错误",false),
// TODO 40xxx 基础业务错误
UPDATE_USER_INFO_ERROR(40000,"更新用户详情失败",false),
UPDATE_USER_PASSWORD_ERROR(40001,"更新密码失败",false),
OLD_PASSWORD_ERROR(40002,"旧密码错误",false),
REPEAT_PASSWORD_ERROR(40003,"确认密码不一致",false),
UPDATE_USER_AVATAR_ERROR(40004,"更新用户头像失败",false),
ACCOUNT_EXIST_ERROR(40006,"账号已被使用",false),
MOBILE_EXIST_ERROR(40007,"手机号已被使用",false),
UPLOAD_FILE_ERROR(40008,"上传文件失败",false),
UPLOAD_FILE_SUFFIX_ERROR(40009,"不支持该文件类型",false),
UPLOAD_FILE_SIZE_ERROR(40010,"上传文件大小不合法",false),
READ_FILE_ERROR(40011,"解析文件错误",false),
READ_TEMPLATE_ERROR(40012,"上传文件模板不合法",false),
SMS_WAIT_ERROR(40013,"短信发送太快了~请稍后再试",false),
SMS_UPPER_LIMIT_ERROR(40014,"今天发送短信已上限",false),
PARAMS_ERROR(40015,"参数不合法",false),
// TODO 50xxx 基础系统错误
SYSTEM_INDEX_OUT_OF_BOUNDS(50000, "系统错误,数组越界!",false),
SYSTEM_ARITHMETIC_BY_ZERO(50001, "系统错误,无法除零!",false),
SYSTEM_NULL_POINTER(50002, "系统错误,空指针!",false),
SYSTEM_NUMBER_FORMAT(50003, "系统错误,数字转换异常!",false),
SYSTEM_PARSE(50004, "系统错误,解析异常!",false),
SYSTEM_IO(50005, "系统错误,IO输入输出异常!",false),
SYSTEM_FILE_NOT_FOUND(50006, "系统错误,文件未找到!",false),
SYSTEM_CLASS_CAST(50007, "系统错误,类型强制转换错误!",false),
SYSTEM_PARSER_ERROR(50008, "系统错误,解析出错!",false),
SYSTEM_DATE_PARSER_ERROR(50009, "系统错误,日期解析出错!",false),
// TODO 51xxx 未知系统错误
SYSTEM_ERROR(51000, "系统繁忙,请稍后再试!",false),
SYSTEM_OPERATION_ERROR(51001, "操作失败,请重试或联系管理员",false),
SYSTEM_ERROR_ZUUL(51002, "请求系统过于繁忙,请稍后再试!",false);
// 响应码
private Integer code;
// 响应消息
private String message;
// 是否成功
private Boolean success;
MSG(Integer code,String message, Boolean success) {
this.code = code;
this.message = message;
this.success = success;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Boolean getSuccess() {
return success;
}
public void setSuccess(Boolean success) {
this.success = success;
}
}
返回对象
返回 json 的基础结构对象
public class Result {
// 响应编码
private Integer code;
// 响应消息
private String message;
// 是否成功
private Boolean success;
// 数据
private Object data;
public Result() {}
public Result(Integer code, String message, Boolean success, Object data) {
this.code = code;
this.message = message;
this.success = success;
this.data = data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Boolean getSuccess() {
return success;
}
public void setSuccess(Boolean success) {
this.success = success;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("Result{");
sb.append("code=").append(code);
sb.append(", message='").append(message).append('\'');
sb.append(", success=").append(success);
sb.append(", data=").append(data);
sb.append('}');
return sb.toString();
}
}
返回数据方法
在不同的业务场景我们可用不同的方法进行消息返回
public class ResultData extends Result {
// 默认成功方法
public static Result success() {
return new Result(MSG.SUCCESS.getCode(), MSG.SUCCESS.getMessage(), MSG.SUCCESS.getSuccess(),"");
}
// 自定义成功数据方法
public static Result success(Object data) {
return new Result(MSG.SUCCESS.getCode(), MSG.SUCCESS.getMessage(), MSG.SUCCESS.getSuccess(),data);
}
// 自定义成功数据、消息方法
public static Result success(String message, Object data) {
return new Result(MSG.SUCCESS.getCode(), message, MSG.SUCCESS.getSuccess(),data);
}
// 默认失败方法
public static Result error() {
return new Result(MSG.ERROR.getCode(), MSG.ERROR.getMessage(), MSG.ERROR.getSuccess(),"");
}
// 自定义枚举消息方法
public static Result error(MSG msg) {
return new Result(msg.getCode(), msg.getMessage(), msg.getSuccess(),"");
}
// 自定义失败消息方法
public static Result error(String message) {
return new Result(MSG.ERROR.getCode(), message, MSG.ERROR.getSuccess(),"");
}
// 自定义失败消息、数据方法
public static Result error(String message, Object data) {
return new Result(MSG.ERROR.getCode(), message, MSG.ERROR.getSuccess(),data);
}
// 自定义失败消息(枚举)、数据方法
public static Result error(MSG msg, Object data) {
return new Result(msg.getCode(), msg.getMessage(), msg.getSuccess(),data);
}
}
测试一下
写一个controller 返回类型用我们刚写的Result对象
@RestController
@RequestMapping("/hello")
public class HelloController {
@GetMapping("/index")
public Result index() {
Map<String,String> data = new HashMap<>();
data.put("name","张三");
data.put("sex","男");
return ResultData.success(data);
}
}
到这里基本就差不多好了,接下来我们做全局异常捕捉
全局异常处理
这里我定义了一些常见的异常和自定的异常,如需要其他可以自行扩展
@ControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(CustomException.class)
@ResponseBody
public Result returnCustomException(CustomException e) {
e.printStackTrace();
return ResultData.error(e.getResponseEnum());
}
@ExceptionHandler(ParamsException.class)
@ResponseBody
public Result returnCustomException(ParamsException e) {
return ResultData.error(e.getResponseEnum(),e.getData());
}
// 除零异常
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public Result arithmeticException(ArithmeticException e) {
e.printStackTrace();
return ResultData.error(MSG.SYSTEM_ARITHMETIC_BY_ZERO);
}
// 空指针
@ExceptionHandler(NullPointerException.class)
@ResponseBody
public Result nullException(NullPointerException e) {
e.printStackTrace();
return ResultData.error(MSG.SYSTEM_NULL_POINTER);
}
// 数组越界
@ExceptionHandler(ArrayIndexOutOfBoundsException.class)
@ResponseBody
public Result arrayIndexException(ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
return ResultData.error(MSG.SYSTEM_INDEX_OUT_OF_BOUNDS);
}
// 未知异常
@ExceptionHandler(Exception.class)
@ResponseBody
public Result unknown(Exception e) {
e.printStackTrace();
return ResultData.error(MSG.SYSTEM_OPERATION_ERROR);
}
}
测试异常处理
这里用了一个除零异常测试一下
// 除零异常
@GetMapping("/server")
public Result serverError() {
int i = 1 / 0;
return ResultData.success();
}
可以看到我们捕获到异常后会自动返回我们定义好的消息处理
自定义异常
public class CustomException extends RuntimeException{
private MSG responseEnum;
public CustomException(MSG responseEnum) {
super("具体异常信息为:" + responseEnum.getMessage());
this.responseEnum = responseEnum;
}
public MSG getResponseEnum() {
return responseEnum;
}
public void setResponseEnum(MSG responseEnum) {
this.responseEnum = responseEnum;
}
}
这里再来个非法参数异常,用来等下做参数验证时使用
public class ParamsException extends RuntimeException{
private MSG responseEnum;
private Object data;
public ParamsException(MSG responseEnum,Object data) {
this.responseEnum = responseEnum;
this.data = data;
}
public MSG getResponseEnum() {
return responseEnum;
}
public Object getData() {
return data;
}
}
我们先写一个异常类,在写一个优雅的触发类
public class MyException {
public static void display(MSG responseEnum) {
throw new CustomException(responseEnum);
}
public static void display(MSG responseEnum, Object data) {
throw new ParamsException(responseEnum, data);
}
}
如果不想每次抛异常都需要写 throw new 那我们可用定义一个统一调用类,这样使用异常时只需要使用 MyException 的 display 方法即可。
测试一下
// 测试统一异常
@GetMapping("/error")
public Result error() {
MyException.display(MSG.SYSTEM_ERROR);
return ResultData.success();
}
接下来我们来实现对象参数验证 和自定义的注解开发
对象参数验证
这里我就简单的写几个个注解分别是 @Length(效验长度)、@NotNull(判断是否为空)、@Phone(手机号验证),我们还需要要一个触发注解 @Valid
创建注解
- @Target 注解范围声明
CONSTRUCTOR:用于描述构造器
FIELD:用于描述域(对象属性)
LOCAL_VARIABLE:用于描述局部变量
METHOD:用于描述方法
PACKAGE:用于描述包
PARAMETER:用于描述参数
TYPE:用于描述类、接口(包括注解类型) 或enum声明
- @Retention 生命周期
RetentionPolicy.SOURCE 在源码阶段生效
RetentionPolicy.CLASS 在编译后生效
RetentionPolicy.RUNTIME 在运行时生效
长度验证注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Length {
String value() default "长度不合法";
int length();
}
非空验证注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNull {
String value() default "参数不能为空";
}
手机号验证注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Phone {
String value() default "手机号不合法";
}
触发注解
这个就是为了触发我们的验证需要用的注解所以不需要任何属性和方法,直接写个空注解即可
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Valid {
}
注解处理器
import com.example.annotations.Length;
import com.example.annotations.NotNull;
import com.example.annotations.Phone;
import java.lang.annotation.Annotation;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class VerifyHandler {
private static final Pattern CHINA_PATTERN = Pattern.compile("^((13[0-9])|(14[0,1,4-9])|(15[0-3,5-9])|(16[2,5,6,7])|(17[0-8])|(18[0-9])|(19[0-3,5-9]))\\d{8}$");
public static String verify(Annotation annotation,Object data) {
if (annotation.annotationType() == NotNull.class) {
return notNullBack(annotation, data);
}else if (annotation.annotationType() == Length.class) {
return lengthBack(annotation,data);
}else if (annotation.annotationType() == Phone.class){
return phoneBack(annotation,data);
}
return "未找到匹配器";
}
// 这里返回的是不合法消息,空则合法
public static String notNullBack(Annotation annotation, Object data) {
NotNull notNull = (NotNull) annotation;
if (data == null || "".equals(data.toString().trim())) {
return notNull.value();
}
return null;
}
public static String lengthBack(Annotation annotation, Object data) {
Length length = (Length) annotation;
if (data == null || length.length() > data.toString().trim().length()) {
return length.value();
}
return null;
}
public static String phoneBack(Annotation annotation, Object data) {
Matcher m = CHINA_PATTERN.matcher(Objects.toString(data));
Phone phone = (Phone) annotation;
if (!m.matches()) {
return phone.value();
}
return null;
}
}
测试对象
import com.example.annotations.Length;
import com.example.annotations.NotNull;
import com.example.annotations.Phone;
public class Student {
// 姓名
@NotNull
private String name;
// 手机号
@Phone
private String phone;
// 密码
@Length(value = "密码至少6位",length = 6)
private String password;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("Student{");
sb.append("name='").append(name).append('\'');
sb.append(", phone='").append(phone).append('\'');
sb.append(", password='").append(password).append('\'');
sb.append('}');
return sb.toString();
}
}
AOP 横切拦截验证
controller 方法拦截
@Aspect
@Component
public class MyAnnotationsHandler {
@Pointcut("execution(* com.example.controller.*.*(..))")
public void pointCut() {
}
// 增强方法
@Around("pointCut()")
public Object verifyParams(ProceedingJoinPoint joinPoint) throws Throwable{
Object[] args = joinPoint.getArgs();
Object result = joinPoint.proceed(args);
// 方法签名
Signature signature = joinPoint.getSignature();
// 获取的是代理类的method对象
Method method = ( (MethodSignature)signature ).getMethod();
// 这个方法才是目标对象上有注解的方法
Method realMethod = joinPoint.getTarget().getClass().getDeclaredMethod(signature.getName(), method.getParameterTypes());
// 参数下标
int paramIndex = -1;
boolean isVerify = false;
Annotation[][] parameterAnnotations = realMethod.getParameterAnnotations();
A: for (Annotation[] annotations : parameterAnnotations) {
for (Annotation annotation : annotations) {
paramIndex += 1;
// 获取注解名
if (annotation.annotationType().getName().equalsIgnoreCase(Valid.class.getName())) {
isVerify = true;
break A;
}
}
}
if (isVerify) {
ParamsVerifyHandler.verify(args[paramIndex]);
}
return result;
}
}
参数处理器
public class ParamsVerifyHandler {
public static List<Class> verifyClass = new ArrayList<>();
static {
verifyClass.add(Length.class);
verifyClass.add(NotNull.class);
verifyClass.add(Phone.class);
}
//
public static void verify(Object obj) {
// 获取到对象
Class clz = obj.getClass();
// 属性列表
List<Field> allFields = new ArrayList<>();
// 获取当前类属性
allFields.addAll(Arrays.asList(clz.getDeclaredFields()));
// 如果有父类获取父类属性
if (clz.getSuperclass() != null) {
allFields.addAll(Arrays.asList(clz.getSuperclass().getDeclaredFields()));
}
Map<String,String> verifyMessage = new HashMap<>();
for (Field field : allFields) {
List<Annotation> fieldAnnotations = Arrays.asList(field.getAnnotations());
for (Annotation fieldAnnotation : fieldAnnotations) {
if (verifyClass.contains(fieldAnnotation.annotationType())) {
// 对应的注解事件
try {
field.setAccessible(true);
String message = VerifyHandler.verify(fieldAnnotation,field.get(obj));
if (message != null) {
verifyMessage.put(field.getName(),message);
}
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
if (verifyMessage.size() > 0) {
MyException.display(MSG.PARAMS_ERROR,verifyMessage);
}
}
}
测试一下参数
只要在方法里面加入我们自定义的 @Valid 注解就可以通过 aop 拦截验证
@PostMapping("/params")
public Result verifyParams(@Valid @RequestBody Student student) {
return ResultData.success(student);
}