【Java】自定义注解详解及使用案例

1、基础

注解的出现是在jdk1.5 但是在jdk1.5版本使用注解必须是继承类的方法的重写,不能用于实现的接口中的方法实现,在jdk1.6环境下对于继承和实现都适用。@interface

1.1 标准的注解

  • jdk1.5版本内置了三种标准的注解:

    1. @Override ,表示当前的方法定义将覆盖超类中的方法。
    2. @Deprecated ,使用了注解为它的元素编译器将发出警告,因为注解是不赞成使用的代码,被弃用的代码。
    3. @SuppressWarnings,关闭不当编辑器警告信息。
  • Java还提供了4种注解,专门负责新注解的创建

@Target:表示该注解可以用于什么地方,可能的ElementType参数有:

ElementType含义
CONSTRUCTOR构造器的声明
FIELD域声明(包括enum实例)
LOCAL_VARIABLE局部变量声明
METHOD方法声明
PACKAGE包声明
PARAMETER参数声明
TYPE类、接口(包括注解类型)或enum声明

@Retention: 表示需要在什么级别保存该注解信息。可选的RetentionPolicy参数包括

RetentionPolicy含义
SOURCE注解将被编译器丢弃
CLASS注解在class文件中可用,但会被VM丢弃
RUNTIMEVM将在运行期间保留注解,因此可以通过反射机制读取注解的信息

@Document :将注解包含在Javadoc中

@Inherited :允许子类继承父类中的注解

2、自定义注解使用

2.1 方法入参校验

参考博文

类文件信息

  1. ParamCheck.java(自定义的注解类,可以校验是否为空,和最大长度)
  2. NfzdException.java(自定义的异常类)
  3. Student.java(测试用的实体类,入参呀)
  4. NfzdTest.java(测试类)
  5. Result.java(公共返回类)
  6. 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注解。

类文件信息

  1. CheckAge.java(自定义的注解类,校验年龄)
  2. CheckAgeValidator.java(校验实现类)
  3. Person.java(测试用的实体类,入参呀)
  4. 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对接口进行鉴权的功能。这次写个简单的

例子,利用自定义注解来控制接口是否关闭。

类文件信息

  1. InterfaceSwitch.java(自定义的注解类,控制接口的开关)
  2. InterfaceSwitchAop.java(aop)
  3. 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、接口未关闭
请求:
请求
响应:
响应

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值