• Java 注解


    注解是什么

    注解是JDK1.5引入的新特性,用于对代码进行说明。注解是一种元数据,用于对代码进行说明,可以对包、类、接口、字段、方法、参数、局部变量等进行注解,可以理解为一种特殊的注释。这个注释是用来给程序看的。

    注释是写给人看的,注解是写给程序看的。

    为什么要使用注解

    在没有注解之前,各种框架中几乎所有的配置以XML方式进行配置,因为XML方式可以降低配置和代码的耦合度,但是随着项目越来越庞大,XML的内容越来越复杂,维护成本变高。所有就有人提出了一种标记式的高耦合的配置方式,这是方式就是注解

    注解:与源代码紧绑定,耦合度高,但它便捷,易于维护修改。
    XML:与源代码无绑定,耦合度低,更容易扩展,但XML内容复杂,不易维护。

    各有优劣!!!

    注解作用

    生成文档:通过代码里的标识的元数据生成javadoc文档。
    编译检查:通过代码里的标识的元数据让编译器在编译期间进行检查验证。
    编译动态处理:编译时通过代码里的标识的元数据动态处理,比如说用作动态代码生成。
    运行动态处理:运行时通过代码里标识的元数据动态处理,比如说使用反射注入实例。

    注解分类

    Java提供了3种注解。
    元注解:用于定义注解的注解,指定某个注解的生命周期以及作用目标等信息。

    1. @Target 标明注解使用的范围(作用的目标),有以下取值范围
    public enum ElementType {
        /*类、接口(包括注解类型)或枚举声明*/
        TYPE,
        /*字段,包括枚举常量*/
        FIELD,
        /*方法*/
        METHOD,
        /*形式参数*/
        PARAMETER,
        /*构造函数*/
        CONSTRUCTOR,
        /*局部变量*/
        LOCAL_VARIABLE,
        /*注解*/
        ANNOTATION_TYPE,
        /*包*/
        PACKAGE,
        /**
         * 表示该注解能写在类型变量的声明语句中(如:泛型声明)
         *
         * @since 1.8
         */
        TYPE_PARAMETER,
        /**
         * 表示该注解能写在使用类型的任何语句中
         *
         * @since 1.8
         */
        TYPE_USE
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    1. @Retention 表明注解被保留的阶段,有以下取值范围
    public enum RetentionPolicy {
    	//源码(只能再编译期可加,编译后会被丢弃)
        /*注解将会被编译器丢弃*/
        SOURCE,
    
    	//字节码(会被编译器编译进class文件中,类加载时会被丢弃)-----默认值
        /*注解由编译器记录在类文件中,但不需要在运行时由JVM保留*/
        CLASS,
    	
    	//运行期(永久保存)
        /*注解由编译器记录在类文件中,运行时由JVM保留,可以通过反射来读取到它们*/
        RUNTIME
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    1. @Documented 被标识的注解,在执行javadoc文档打包时会被保存进doc文档。
    2. @Inherited 标明注解可继承,也就是说我们的注解修饰了一个类,该类的子类将自动继承父类的该注解。

    Java自带标准注解:
    @Override 标明重写某个方法,编译器在对Java文件进行编译成字节码的过程中,一旦检测到某个方法上被修饰了该注解,就会去匹配对父类中是否具有一个同样方法签名的函数,如果没有,则不能通过编译。
    @Deprecated 标明某个类或方法过时
    @SuppressWarnings 标明要忽略的警告

    **自定义注解:**可以根据自己的需求定义注解

    注解实现

    实现方式有基于Spring框架不基于Spring框架两种方式。

    不管采用哪种方式,都是按照以下步骤实现和使用注解的

    1. 声明注解
    2. 实现注解处理器
    借助反射,获取注解对象(通常是Class对象,Method对象,Field对象,Constructor对象,Annotation对象),读取注解的属性值,然后根据注解及其属性的值做相应处理。
    在这一步骤中最重要的是获取指定包路径下的所有的Class对象,只要获取到Class对象,其他对象都可以获取到,就可以任意操作。
    3. 使用注解

    先声明一个注解

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target({ElementType.TYPE, ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnnotion {
    
        String value() default "";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    不基于Spring框架获取Class对象:

    1.借助Spring提供的工具包
    首先引入4个jar包:
    在这里插入图片描述
    代码如下:

    /**
      * 借助Sping的工具类,扫描指定包,根据的bean定义获取包下的所有的Class对象
      * @param packName  包路径
      * @return
      * @throws ClassNotFoundException
      */
     public static Class[] getAllClass1(String packName) throws ClassNotFoundException {
         /*
         * ClassPathScanningCandidateComponentProvider是Spring提供的工具,可以按照自定义的类型,查找ClassPath下符合要求的class文件
         * useDefaultFilters:
         * true表示使用默认的typeFileter,会查出来很多自己不想要的class
         * false表示不使用默认的typeFileter
         */
         ClassPathScanningCandidateComponentProvider classPathScanningCandidateComponentProvider =
                 new ClassPathScanningCandidateComponentProvider(false);
         //设置自定义的typeFilter
         classPathScanningCandidateComponentProvider.addIncludeFilter(new AnnotationTypeFilter(MyAnnotion.class));
         //根据自定义的typeFilter,在指定的包下获取满足条件的bean的定义
         Set<BeanDefinition> beanDefinitions =  classPathScanningCandidateComponentProvider.findCandidateComponents(packName);
         if (beanDefinitions.isEmpty()) {
             return null;
         }
         Class[] clazzes = new Class[beanDefinitions.size()];
         //根据bean的定义获取bean的名称,然后通过反射获取bean的class对象,将class对象放入数组中
         int i = 0;
         for (BeanDefinition beanDefinition : beanDefinitions) {
             Class clazz = Class.forName(beanDefinition.getBeanClassName());
             if (Objects.isNull(clazz)) {
                 continue;
             }
             clazzes[i++] = clazz;
         }
         return clazzes;
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    2.借助reflections反射工具包
    首先引入3个jar包:
    在这里插入图片描述
    代码如下:

    /**
      * 借助reflections反射工具包,扫描指定包,获取包下所有的Class对象
      * @param packName  包路径
      * @return
      */
     public static Class[] getAllClass2(String packName) {
         Reflections reflections = new Reflections(PATH);
         Set<Class<?>> typesAnnotatedWith = reflections.getTypesAnnotatedWith(MyAnnotion.class);
         Class[] classes = new Class[typesAnnotatedWith.size()];
         int i = 0;
         for (Class c : typesAnnotatedWith) {
             classes[i++] = c;
         }
         return classes;
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3.自己实现(采用JDK的API)
    只需依赖JDK 1.8即可
    代码如下:

    /**
      * 自己实现:获取指定包路径下的所有类的Class对象
      * @param packName   包路径
      * @return
      * @throws IOException
      * @throws ClassNotFoundException
      */
     public static Class[] getAllClass3(String packName) throws IOException, ClassNotFoundException {
         URL resource = Thread.currentThread().getContextClassLoader().getResource(packName.replace('.', '/'));
         String[] files = new File(resource.getFile()).list();
         Class[] clazzs = new Class[files.length];
         for (int i = 0; i < files.length; i++) {
             clazzs[i] = Class.forName(packName + "." + files[i].replace(".class", ""));
         }
         return clazzs;
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    不基于Spring框架获取Class对象:

    待补充

    注解本质(实现原理)

    首先定义一个注解的关键字为"@interface",格式如下:

    public @interface 注解名称{
    	属性列表;
    }
    
    • 1
    • 2
    • 3

    首先我们先按照注解的定义创建一个注解MyAnnotation.java,编译之后得到MyAnnotation.class文件
    在这里插入图片描述
    然后再用javap -c MyAnnotation.class命令进行反编译,结果如下

    在这里插入图片描述
    可以看出反编译之后的注解其实就是一个接口,它继承了Annotation接口。

    没错,注解的本质就是一个继承了Annotation接口的接口。

    解析一个类或者方法的注解有两种方式,一种是编译器的直接扫描,一种是运行期反射

    编译器的扫描指的是编译器在对Java代码编译成字节码的过程中检测到某个类或方法被一下注解修饰,这时会对这些注解进行处理。

    运行期反射将会在下面进行详细说明

    对于一个类或者接口来说,Class类对象中提供了以下方法用于反射注解。

    • getAnnotation:返回指定的注解
    • isAnnotationPresent:判断当前元素是否被指定注解修饰
    • getAnnotations:返回所有的注解
    • getDeclaredAnnotation:返回本元素的指定注解
    • getDeclaredAnnotations:返回本元素的所有注解,不包含父类继承而来的。

    首先

  • 相关阅读:
    linux入门6—日志分析与故障排除
    tensorflow中的slim函数集合
    什么是人工智能(AI)数据平台?
    项目管理(PMO的作用)
    营销互动类小游戏策划与开发
    数据结构与算法-希尔排序
    系列七、栈 & 堆
    Ubuntu GDB 的基本使用-传参调用
    字节跳动后端面经十
    [自学记录08*]LDR、HDR与ToneMapping
  • 原文地址:https://blog.csdn.net/weixin_43175427/article/details/125702646