1、基础
注解的出现是在jdk1.5 但是在jdk1.5版本使用注解必须是继承类的方法的重写,不能用于实现的接口中的方法实现,在jdk1.6环境下对于继承和实现都适用。@interface
1.1 标准的注解
-
jdk1.5版本内置了三种标准的注解:
- @Override ,表示当前的方法定义将覆盖超类中的方法。
- @Deprecated ,使用了注解为它的元素编译器将发出警告,因为注解是不赞成使用的代码,被弃用的代码。
- @SuppressWarnings,关闭不当编辑器警告信息。
-
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 :允许子类继承父类中的注解
2、自定义注解使用
2.1 方法入参校验
类文件信息
- ParamCheck.java(自定义的注解类,可以校验是否为空,和最大长度)
- NfzdException.java(自定义的异常类)
- Student.java(测试用的实体类,入参呀)
- NfzdTest.java(测试类)
- Result.java(公共返回类)
- CheckUtils.java(检验的工具类)
- ParamCheck.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;
}
- NfzdException.java
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);
}
}
- Student.java
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;
}
- NfzdTest.java
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());
}
}
}
- Result.java
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 +
'}';
}
}
- CheckUtils.java
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);
}
}
}
2,2 Controller入参校验
对Controller层的接口入参进行一些合法性校验。Controller的入参前加 @Valid注解。
类文件信息
- CheckAge.java(自定义的注解类,校验年龄)
- CheckAgeValidator.java(校验实现类)
- Person.java(测试用的实体类,入参呀)
- HelloController.java(测试用的Controller.类)
- CheckAge.java(自定义的注解类,校验年龄)
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 {};
}
- CheckAgeValidator.java(校验实现类)
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;
}
}
- Person.java(测试用的实体类,入参呀)
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;
}
- HelloController.java(测试用的Controller.类)
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
}
2.3 强大的自定义义注解加aop
自定义注解加aop可以实现很多强大的功能,优点是对已有的代码侵入性小。
- 可以用自定注解加aop做接口的熔断限流,
- 接口权限校验,
- 也可以对一些调用外部接口的方法做测试环境挡板,
- 还可以对一些重要的接口做日志落表记录等。
注:接口权限校验,熔断限流这些都有很多优秀的开源框架。在工作中我们可以用自
定义义注解加aop做一些简单的功能实现,投入成本比较少。如果是一些重要项目还
是建议用已有的框架。在工作中自己写了个自定义注解加aop对接口进行鉴权的功能。这次写个简单的
例子,利用自定义注解来控制接口是否关闭。
类文件信息
- InterfaceSwitch.java(自定义的注解类,控制接口的开关)
- InterfaceSwitchAop.java(aop)
- HelloController.java(测试用的Controller)
- InterfaceSwitch.java(自定义的注解类,控制接口的开关)
package com.lh.nfzd.common.annotation;
import java.lang.annotation.*;
@Documented
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InterfaceSwitch {
/**
* 资源编号
*/
String resourceId();
}
- InterfaceSwitchAop.java(aop)
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;
}
}
- HelloController.java(测试用的Controller)
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、接口未关闭
请求:
响应: