赞
踩
注解的出现是在jdk1.5 但是在jdk1.5版本使用注解必须是继承类的方法的重写,不能用于实现的接口中的方法实现,在jdk1.6环境下对于继承和实现都适用。@interface
jdk1.5版本内置了三种标准的注解:
Java还提供了4种注解,专门负责新注解的创建
@Target:表示该注解可以用于什么地方,可能的ElementType参数有:
ElementType | 含义 |
---|---|
CONSTRUCTOR | 构造器的声明 |
FIELD | 域声明(包括enum实例) |
LOCAL_VARIABLE | 局部变量声明 |
METHOD | 方法声明 |
PACKAGE | 包声明 |
PARAMETER | 参数声明 |
TYPE | 类、接口(包括注解类型)或enum声明 |
@Retention: 表示需要在什么级别保存该注解信息。可选的RetentionPolicy参数包括
RetentionPolicy | 含义 |
---|---|
SOURCE | 注解将被编译器丢弃 |
CLASS | 注解在class文件中可用,但会被VM丢弃 |
RUNTIME | VM将在运行期间保留注解,因此可以通过反射机制读取注解的信息 |
@Document :将注解包含在Javadoc中
@Inherited :允许子类继承父类中的注解
类文件信息
- ParamCheck.java(自定义的注解类,可以校验是否为空,和最大长度)
- NfzdException.java(自定义的异常类)
- Student.java(测试用的实体类,入参呀)
- NfzdTest.java(测试类)
- Result.java(公共返回类)
- CheckUtils.java(检验的工具类)
package com.lh.nfzd.common.annotation; import java.lang.annotation.*; /** * 参数检验注解 */ @Documented @Inherited @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface ParamCheck { /** * 字段名称 * * @return */ String name(); /** * 是否可为空 默认可为空 * * @return */ boolean isNull() default true; /** * 长度 * * @return */ int maxLength() default -1; }
package com.lh.nfzd.common.exception; /** * 自定义异常类 */ public class NfzdException extends RuntimeException { private static final long serialVersionUID = 1L; public NfzdException() { } public NfzdException(String str) { super(str); } public NfzdException(Throwable throwable) { super(throwable); } public NfzdException(String str, Throwable throwable) { super(str, throwable); } }
package com.lh.nfzd.pojo; import com.lh.nfzd.common.annotation.ParamCheck; import lombok.Data; import java.util.List; @Data public class Student { private Integer id; @ParamCheck(name = "姓名", isNull = false) private String name; @ParamCheck(name = "备注", maxLength = 5) private String info; @ParamCheck(name = "课程", maxLength = 2) private List<BaseEntity> list; }
package com.lh.nfzd; import com.lh.nfzd.pojo.BaseEntity; import com.lh.nfzd.pojo.Student; import com.lh.nfzd.pojo.common.Result; import com.lh.nfzd.utils.CheckUtils; import lombok.extern.slf4j.Slf4j; import org.junit.Test; import java.util.ArrayList; import java.util.List; @Slf4j public class NfzdTest { @Test public void test01() { Student student = new Student(); student.setId(1); student.setName("孙少安"); student.setInfo("平凡的世界"); List<BaseEntity> list = new ArrayList<>(); list.add(new BaseEntity()); list.add(new BaseEntity()); student.setList(list); Result result = this.printInfo(student); System.out.println(result); } private Result printInfo(Student student) { try { CheckUtils.doValidator(student); log.info("信息 student={}", student); return Result.ok(); } catch (Exception e) { e.printStackTrace(); log.error("异常: e={}", e.getMessage()); return Result.error(e.getMessage()); } } }
package com.lh.nfzd.pojo.common; import com.lh.nfzd.pojo.enums.TransactionCode; public class Result<T> { private String code; private String msg; private T data; private boolean success; public static Result ok(Object data) { return ok(TransactionCode.SUCCESS.getCode(), TransactionCode.SUCCESS.getMsg(), true, data); } public static Result ok() { return new Result<>(TransactionCode.SUCCESS.getCode(), TransactionCode.SUCCESS.getMsg(), true); } public static Result ok(String msg) { return new Result<>(TransactionCode.SUCCESS.getCode(), msg, true); } public static Result ok(String code, String msg, boolean success, Object data) { return new Result<>(code, msg, success, data); } public static Result error() { return new Result<>(TransactionCode.ERROR.getCode(), TransactionCode.ERROR.getMsg(), false); } public static Result error(String msg) { return new Result<>(TransactionCode.ERROR.getCode(), msg, false); } public Result() { } public Result(String code, String msg, boolean success) { this.code = code; this.msg = msg; this.success = success; } public Result(String code, String msg, boolean success, T data) { this.code = code; this.msg = msg; this.data = data; this.success = success; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public T getData() { return data; } public void setData(T data) { this.data = data; } public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } @Override public String toString() { return "Result{" + "code='" + code + '\'' + ", msg='" + msg + '\'' + ", data=" + data + ", success=" + success + '}'; } }
package com.lh.nfzd.utils; import com.lh.nfzd.common.annotation.ParamCheck; import org.apache.commons.lang.StringUtils; import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.List; public class CheckUtils { /** * 通过反射来获取javaBean上的注解信息,判断属性值信息,然后通过注解元数据来返回 */ public static <T> boolean doValidator(T clas) { Class<?> clazz = clas.getClass(); Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { ParamCheck paramCheck = field.getDeclaredAnnotation(ParamCheck.class); if (null != paramCheck) { Object value = getValue(clas, field.getName()); if (!paramCheck.isNull() && !notNull(value)) { throwExcpetion(paramCheck.name() + "不能为空!"); } if (paramCheck.maxLength() > 0 && !checkLength(value, paramCheck.maxLength())) { throwExcpetion(paramCheck.name() + "超长!"); } } } return true; } /** * 获取当前fieldName对应的值 * * @param clazz 对应的bean对象 * @param fieldName bean中对应的属性名称 * @return */ public static <T> Object getValue(T clazz, String fieldName) { Object value = null; try { BeanInfo beanInfo = Introspector.getBeanInfo(clazz.getClass()); PropertyDescriptor[] props = beanInfo.getPropertyDescriptors(); for (PropertyDescriptor property : props) { if (fieldName.equals(property.getName())) { Method method = property.getReadMethod(); value = method.invoke(clazz, new Object[]{}); } } } catch (Exception e) { e.printStackTrace(); } return value; } /** * 非空校验 * * @param value * @return */ public static boolean notNull(Object value) { if (null == value) { return false; } if (value instanceof String && StringUtils.isBlank((String) value)) { return false; } if (value instanceof List && isEmpty((List<?>) value)) { return false; } return null != value; } /** * 检验长度 * * @param value 值 * @param maxLength 最大长度 * @return */ public static boolean checkLength(Object value, int maxLength) { if (null == value) { return true; } if (value instanceof String && ((String) value).length() > maxLength) { return false; } if (value instanceof List && ((List<?>) value).size() > maxLength) { return false; } return true; } public static boolean isEmpty(List<?> list) { return null == list || list.isEmpty(); } private static void throwExcpetion(String msg) { if (null != msg) { throw new NfzdException(msg); } } }
对Controller层的接口入参进行一些合法性校验。Controller的入参前加 @Valid注解。
类文件信息
- CheckAge.java(自定义的注解类,校验年龄)
- CheckAgeValidator.java(校验实现类)
- Person.java(测试用的实体类,入参呀)
- HelloController.java(测试用的Controller.类)
package com.lh.nfzd.common.annotation; import com.lh.nfzd.common.annotation.validation.CheckAgeValidator; import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.*; @Documented @Inherited @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = CheckAgeValidator.class) public @interface CheckAge { // 最小年龄 int min() default 0; // 最大年龄 int max(); String message() default "年龄不符"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
package com.lh.nfzd.common.annotation.validation; import com.lh.nfzd.common.annotation.CheckAge; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; public class CheckAgeValidator implements ConstraintValidator<CheckAge, Integer> { private int min; private int max; @Override public void initialize(CheckAge constraintAnnotation) { this.max = constraintAnnotation.max(); this.min = constraintAnnotation.min(); } @Override public boolean isValid(Integer age, ConstraintValidatorContext constraintValidatorContext) { if (age == null) { return false; } return age >= min && age <= max; } }
package com.lh.nfzd.model;
import com.lh.nfzd.common.annotation.CheckAge;
import lombok.Data;
@Data
public class Person {
private String name;
@CheckAge(min = 5, max = 20)
private Integer age;
}
package com.lh.nfzd.controller; import com.lh.nfzd.model.Person; import com.lh.nfzd.model.common.Result; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; @Slf4j @RestController public class HelloController { @RequestMapping(value = "/sayHi", method = RequestMethod.GET) public String sayHi() { return "hello nfzd!!!"; } /** * 测试注解校验参数 * * @param person * @return */ @RequestMapping(value = "/testAnnotation", method = RequestMethod.POST) public Result<Person> testAnnotation(@Valid @RequestBody Person person) { log.info("测试注解校验参数 入参={}", person); return Result.ok(person); } }
测试详情
1、反例
返回
{ "timestamp": "2019-10-26 12:08:38", "status": 400, "error": "Bad Request", "errors": [ { "codes": [ "CheckAge.person.age", "CheckAge.age", "CheckAge.java.lang.Integer", "CheckAge" ], "arguments": [ { "codes": [ "person.age", "age" ], "arguments": null, "defaultMessage": "age", "code": "age" }, 20, 5 ], "defaultMessage": "年龄不符", "objectName": "person", "field": "age", "rejectedValue": 1, "bindingFailure": false, "code": "CheckAge" } ], "message": "Validation failed for object='person'. Error count: 1", "path": "/testAnnotation" }
2、正例
返回
{
"code": "200",
"msg": "交易成功",
"data": {
"name": "小花",
"age": 15
},
"success": true
}
自定义注解加aop可以实现很多强大的功能,优点是对已有的代码侵入性小。
注:接口权限校验,熔断限流这些都有很多优秀的开源框架。在工作中我们可以用自
定义义注解加aop做一些简单的功能实现,投入成本比较少。如果是一些重要项目还
是建议用已有的框架。在工作中自己写了个自定义注解加aop对接口进行鉴权的功能。这次写个简单的
例子,利用自定义注解来控制接口是否关闭。
类文件信息
- InterfaceSwitch.java(自定义的注解类,控制接口的开关)
- InterfaceSwitchAop.java(aop)
- HelloController.java(测试用的Controller)
package com.lh.nfzd.common.annotation; import java.lang.annotation.*; @Documented @Inherited @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface InterfaceSwitch { /** * 资源编号 */ String resourceId(); }
package com.lh.nfzd.aop; import com.lh.nfzd.common.annotation.InterfaceSwitch; import com.lh.nfzd.model.common.Result; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Slf4j @Aspect @Component public class InterfaceSwitchAop { @Pointcut(value = "@annotation(interfaceSwitch)") public void interfaceSwitchPointcut(InterfaceSwitch interfaceSwitch) { } @Around(value = "interfaceSwitchPointcut(interfaceSwitch)") public Object doAround(ProceedingJoinPoint joinPoint, InterfaceSwitch interfaceSwitch) throws Throwable { log.info("开始校验接口是否关闭!resouceId={}", interfaceSwitch.resourceId()); String resouceId = interfaceSwitch.resourceId(); if (!this.getSwitch(resouceId)) { return Result.error("接口关闭"); } else { return joinPoint.proceed(); } } /** * 这个方法就是判断接口是否关闭的方法,入参是resourceId。 * 我们可以把接口开关的配置信息配置在数据库、或者配置在apollo这样的配置中心。或者配置文件中等。 * 用resourceId作为唯一匹配的key * <p> * 这里我们简单实现 * * @param resouceId * @return */ private boolean getSwitch(String resouceId) { if ("testAnnotationSwitchFalse".equals(resouceId)) { return false; } else if ("testAnnotationSwitchTrue".equals(resouceId)) { return true; } return true; } }
package com.lh.nfzd.controller; import com.lh.nfzd.common.annotation.InterfaceSwitch; import com.lh.nfzd.model.Person; import com.lh.nfzd.model.common.Result; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; @Slf4j @RestController public class HelloController { @RequestMapping(value = "/sayHi", method = RequestMethod.GET) public String sayHi() { return "hello nfzd!!!"; } /** * 测试注解校验参数 * * @param person * @return */ @RequestMapping(value = "/testAnnotation", method = RequestMethod.POST) public Result<Person> testAnnotation(@Valid @RequestBody Person person) { log.info("测试注解校验参数 入参={}", person); return Result.ok(person); } /** * 测试注解加AOP控制接口开关 * * @param person * @return */ @InterfaceSwitch(resourceId = "testAnnotationSwitchFalse") @RequestMapping(value = "/testAnnotationSwitchFalse", method = RequestMethod.POST) public Result<Person> testAnnotationSwitchFalse(@RequestBody Person person) { log.info("测试注解加AOP控制接口开关 入参={}", person); return Result.ok(person); } /** * 测试注解加AOP控制接口开关 * * @param person * @return */ @InterfaceSwitch(resourceId = "testAnnotationSwitchTrue") @RequestMapping(value = "/testAnnotationSwitchTrue", method = RequestMethod.POST) public Result<Person> testAnnotationSwitchTrue(@RequestBody Person person) { log.info("测试注解加AOP控制接口开关 入参={}", person); return Result.ok(person); } }
测试情况
1、接口关闭
请求:
响应:
1、接口未关闭
请求:
响应:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。