zoukankan      html  css  js  c++  java
  • 自定义注解

    自定义注解

    原文:https://zhuanlan.zhihu.com/p/60730622

    之前也看过一些自定义注解的内容,但是因为没有实际使用过,时隔3个月,现在来重新学习一下。

    由于本人是个初学者,几乎全文摘抄,加深一下映像。

    注解简介

    注解(Annontation),Java5引入的新特性,位于java.lang.annotation包中。提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。是一种说明、配置、描述性的信息,与具体业务无关,也不会影响正常的业务逻辑。但我们可以用反射机制来进行校验、赋值等操作。

    常见的注解:@Override,@author,@param,@Deprecated,@SuppressWarnings。

    注解的常见用途

    • 生成文档的注解,如@author@param
    • 跟踪代码依赖性,实现替代配置文件功能,如spring mvc的注解。
    • 编译时进行格式检查,如@override
    • 编译时进行代码生成补全,如lombok插件的@Data

    注解的定义

    • 注解的定义通过@interface表示,所有的注解会自动继承java.lang.Annotation接口,且不能再继承别的类或是接口。
    • 注解的成员参数只能用public或默认(default) 访问权修饰来进行修饰。
    • 成员参数只能使用八种基本类型(byte、short、char、int、long、float、double、boolean)和String、Enum、Class、annotations等数据类型,及其数组。
    • 获取类方法和字段的注解信息,只能通过Java的反射技术来获取 Annotation 对象。
    • 注解可以没有定义成员,只做标识。

    元注解

    元注解是专门用来注解其他注解的注解,听起来有些绕口,实际上就是专门为自定义注解提供的注解。java.lang.annotation提供了四种元注解:

    • @Documented – 注解是否将包含在JavaDoc中
    • @Retention – 什么时候使用该注解
    • @Target – 注解用于什么地方
    • @Inherited – 是否允许子类继承该注解,在使用在类上面时,子类会自动继承此注解
    • @Repeatable - 是否可重复注解,jdk1.8引入

    注解的生命周期

    通过@Retention定义注解的生命周期,格式如下:

    @Retention(RetentionPolicy.SOURCE)
    

    其中RetentionPolicy的不同策略对应的生命周期如下:

    • RetentionPolicy.SOURCE : 仅存在于源代码中,编译阶段会被丢弃,不会包含于class字节码文件中。@Override, @SuppressWarnings都属于这类注解。
    • RetentionPolicy.CLASS : 默认策略,在class字节码文件中存在,在类加载的时被丢弃,运行时无法获取到。
    • RetentionPolicy.RUNTIME : 始终不会丢弃,可以使用反射获得该注解的信息。自定义的注解最常用的使用方式。

    注解的作用目标

    通过@Target定义注解作用的目标,比如作用于类、属性、或方法等,默认可用于任何地方。格式如下:

    @Target(ElementType.TYPE)
    

    对应ElementType参数值适用范围如下:

    • ElementType.TYPE: 类、接口、注解、enum
    • ElementType.CONSTRUCTOR: 构造函数
    • ElementType.FIELD: 成员变量、对象、属性、枚举的常量
    • ElementType.LOCAL_VARIABLE: 局部变量
    • ElementType.METHOD: 方法
    • ElementType.PACKAGE: 包
    • ElementType.PARAMETER: 参数
    • ElementType.ANNOTATION_TYPE: 注解
    • ElementType.TYPE_PARAMETER:类型参数,表示这个注解可以用在 Type的声明式前,jdk1.8引入。
    • ElementType.TYPE_USE:类型的注解,表示这个注解可以用在所有使用Type的地方(如:泛型,类型转换等),jdk1.8引入。

    实例

    下面通过一个实例来演示注解的使用:通过注解进行赋值和通过注解进行校验。

    自定义注解

    这里定义两个注解,一个用来赋值,一个用来校验。

    /**
     * 性别赋值
     */
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.FIELD,ElementType.METHOD})
    @Inherited
    public @interface InitSex {
        /**
         * sex enum
         */
        enum SEX_TYPE {MAN, WOMAN}
        SEX_TYPE sex() default SEX_TYPE.MAN;
    }
    
    /**
     * 年龄校验
     */
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.FIELD,ElementType.METHOD})
    @Inherited
    public @interface ValidateAge {
        /**
         * 最小值
         */
        int min() default 18;
        /**
         * 最大值
         */
        int max() default 99;
        /**
         * 默认值
         */
        int value() default 20;
    }
    

    定义数据模型

    这里用User类来表示具体待处理的数据对象。

    /**
     * user
     */
    public class User {
        private String username;
        @ValidateAge(min = 20, max = 35, value = 22)
        private int age;
        @InitSex(sex = InitSex.SEX_TYPE.MAN)
        private String sex;
        // 省略getter/setter方法
    }
    

    测试调用

    具体测试调用的过程,参考代码中的注解,其中initUser方法来演示通过反射给属性赋值,checkUser方法通过反射拿到当前属性的值进行对比校验。

    import java.lang.reflect.Field;
    
    public class TestInitParam {
        public static void main(String[] args) throws IllegalAccessException {
            User user = new User();
            initUser(user);
            // 年龄为0,校验为通过情况
            boolean checkResult = checkUser(user);
            printResult(checkResult);
            // 重新设置年龄,校验通过情况
            user.setAge(22);
            checkResult = checkUser(user);
            printResult(checkResult);
        }
        static void initUser(User user) throws IllegalAccessException {
            // 获取User类中所有的属性(getFields无法获得private属性)
            Field[] fields = User.class.getDeclaredFields();
            // 遍历所有属性
            for (Field field : fields) {
                // 如果属性上有此注解,则进行赋值操作
                if (field.isAnnotationPresent(InitSex.class)) {
                    InitSex init = field.getAnnotation(InitSex.class);
                    field.setAccessible(true);
                    // 设置属性的性别值
                    field.set(user, init.sex().toString());
                    System.out.println("完成属性值的修改,修改值为:" + init.sex().toString());
                }
            }
        }
        static boolean checkUser(User user) throws IllegalAccessException {
            // 获取User类中所有的属性(getFields无法获得private属性)
            Field[] fields = User.class.getDeclaredFields();
            boolean result = true;
            // 遍历所有属性
            for (Field field : fields) {
                // 如果属性上有此注解,则进行赋值操作
                if (field.isAnnotationPresent(ValidateAge.class)) {
                    ValidateAge validateAge = field.getAnnotation(ValidateAge.class);
                    field.setAccessible(true);
                    int age = (int) field.get(user);
                    if (age < validateAge.min() || age > validateAge.max()) {
                        result = false;
                        System.out.println("年龄值不符合条件");
                    }
                }
            }
            return result;
        }
        static void printResult(boolean checkResult) {
            if (checkResult) {
                System.out.println("校验通过");
            } else {
                System.out.println("校验未通过");
            }
        }
    }
    

    打印日志

    完成属性值的修改,修改值为:MAN
    年龄值不符合条件
    校验未通过
    校验通过
    

    spring boot AOP添加自定义注解

    例子

    作用:该注解用于添加中Controller的方法上面,用于取消该接口的使用,并返回填入的value值

    package ins.claim.common.util;
    
    import java.lang.annotation.*;
    
    /**
     * 用于屏蔽接口(该接口的返回值类型中有ResponseHeadDTO类)
     * value中的值表示'appMessage'的返回值信息
     *
     * @author :shentao
     * @create 2021/01/05
     * */
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    public @interface CancelApiAnnotation {
        public String value();
    }
    
    package ins.claim.common.util.impl;
    
    import ins.claim.common.dto.ResponseHeadDTO;
    import ins.claim.common.util.CancelApiAnnotation;
    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;
    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    @Aspect
    @Component
    public class CancelApiAnnotationImpl {
        @Pointcut(value = "execution(public * ins.claim.out.*.api.*Api.*(..)) && @annotation(ins.claim.common.util.CancelApiAnnotation)")
        public void addAdvice() {
        }
    
        @Around("addAdvice()")
        public Object Interceptor(ProceedingJoinPoint pjp) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
            Class clazz = pjp.getTarget().getClass();
            Method[] methods = clazz.getDeclaredMethods();
            Object o1 = null;
            for (Method method : methods) {
                if (method.isAnnotationPresent(CancelApiAnnotation.class)) {
                    //获取注解中的value值
                    CancelApiAnnotation cancelApiAnnotation = method.getAnnotation(CancelApiAnnotation.class);
                    String value = cancelApiAnnotation.value();
                    ResponseHeadDTO responseHeadDTO = new ResponseHeadDTO();
                    responseHeadDTO.setAppMessage(value);
                    String className = method.getReturnType().getName();
                    Class theClass = Class.forName(className);
                    o1 = theClass.newInstance();
                    Method setMethod = o1.getClass().getMethod("setResponseHead", ResponseHeadDTO.class);
                    setMethod.invoke(o1, responseHeadDTO);
                }
            }
            return o1;
        }
    }
    
  • 相关阅读:
    利用Bootstrap快速搭建个人响应式主页(附演示+源码)
    Socket实现仿QQ聊天(可部署于广域网)附源码(2)-服务器搭建
    Socket实现仿QQ聊天(可部署于广域网)附源码(1)-简介
    开拓思维,如何用编程思想进行三维建模(2)
    开拓思维,如何用编程思想进行三维建模(1)
    浅谈Winform事件的实现以及模拟其事件的实现(附实现源码)
    Winform实现Shp-栅格图形文件的读取与显示(外加shp转WKB格式存入oracle)附源码
    本地MVC项目发布到IIS服务器
    Asp.net mvc4 安装报错处理方法(x80070005 拒绝访)
    MySql 主从复制及主主复制
  • 原文地址:https://www.cnblogs.com/siten0616/p/14134672.html
Copyright © 2011-2022 走看看