• 四、注解详解


    四、注解

    1、注解的概念和作用

    是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用注解,程序开发人员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充的信息。
    

    2、基本注解

    ➢ @Override   
    	它强制一个子类必须覆盖父类的方法,@Override只能修饰方法,不能修饰其他程序元素。
    ➢ @Deprecated  
    	用于表示某个程序元素(类、方法等)已过时,当其他程序使用已过时的类、方法时,编译器将会给出警告。
    	两个属性
    		forRemoval:该boolean类型的属性指定该API在将来是否会被删除。
    		since:该String类型的属性指定该API从哪个版本被标记为过时。
    ➢ @SuppressWarnings
    ➢ @SafeVarargs (Java 7新增)
    ➢ @FunctionalInterface (Java 8新增)
    	从Java 8开始:如果接口中只有一个抽象方法(可以包含多个默认方法或多个static方法),该接口就是函数式接口。@FunctionalInterface就是用来指定某个接口必须是函数式接口
    	@FunInterface只能修饰接口,不能修饰其他程序元素。
    

    3、JDK元注解

    6个Meta注解(元注解),其中5个元注解都用于修饰其他的注解定义。

    3.1、@Retention

    使用@Retention时必须为该value成员变量指定值,value的值如下:

    RetentionPolicy.CLASS:编译器将把注解记录在class文件中。当运行Java程序时,JVM不可获取注解信息。这是默认值。

    RetentionPolicy.RUNTIME:编译器将把注解记录在class文件中。当运行Java程序时,JVM也可获取注解信息,程序可以通过反射获取该注解信息。

    RetentionPolicy.SOURCE:注解只保留在源代码中,编译器直接丢弃这种注解。

    3.2、@Target

    用于指定被修饰的注解能用于修饰哪些程序单元。value值如下:

    ➢ ElementType.ANNOTATION_TYPE:指定该策略的注解只能修饰注解。

    ➢ ElementType.CONSTRUCTOR:指定该策略的注解只能修饰构造器。

    ➢ ElementType.FIELD:指定该策略的注解只能修饰成员变量。

    ➢ ElementType.LOCAL_VARIABLE:指定该策略的注解只能修饰局部变量。

    ➢ ElementType.METHOD:指定该策略的注解只能修饰方法定义。

    ➢ ElementType.PACKAGE:指定该策略的注解只能修饰包定义。

    ➢ ElementType.PARAMETER:指定该策略的注解可以修饰参数。

    ➢ ElementType.TYPE:指定该策略的注解可以修饰类、接口(包括注解类型)或枚举定义。

    3.3、@Documented

    用于指定被该元注解修饰的注解类将被javadoc工具提取成文档。

    3.4、@Inherited

    指定被它修饰的注解将具有继承性——如果某个类使用了@Xxx注解(定义该注解时使用了@Inherited修饰)修饰,则其子类将自动被@Xxx修饰。

    Inheritable.java

    import java.lang.annotation.*;
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    public @interface Inheritable {
    }
    

    InheritableTest.java

    @Inheritable
    class Base{}
    
    public class InheritableTest extends Base {
    
        public static void main(String[] args) {
            System.out.println(InheritableTest.class.
                    isAnnotationPresent(Inheritable.class));
        }
    
    }
    

    4、自定义接口

    使用@interface关键字定义一个新的注解,例如:public @interface Test{}

    ➢ 标记注解:没有定义成员变量的注解类型被称为标记。这种注解仅利用自身的存在与否来提供信息,如前面介绍的@Override、@Test等注解。

    ➢ 元数据注解:包含成员变量的注解,因为它们可以接受更多的元数据,所以也被称为元数据注解。

    4.1、提取注解信息

    java.lang.reflect包下新增了AnnotatedElement(JDK 5),该接口代表程序中可以接受注解的程序元素 ===== 》Class:类定义。 Constructor:构造器定义。Field:类的成员变量定义。Method:类的方法定义。Package:类的包定义。

    java.lang.annotation.Annotation接口来代表程序元素前面的注解,该接口是所有程序元素(注解)的父接口。

    ➢ <A extends Annotation> A getAnnotation(Class<A> annotationClass):返回该程序元素上存在的、指定类型的注解,如果该类型的注解不存在,则返回null。
    
    ➢ <A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass):这是Java 8新增的方法,该方法尝试获取直接修饰该程序元素、指定类型的注解。如果该类型的注解不存在,则返回null。
    
    ➢ Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
    
    ➢ Annotation[] getDeclaredAnnotations():返回(直接,不包括继承的)修饰该程序元素的所有注解。
    
    ➢ boolean isAnnotationPresent(Class< ?extends Annotation> annotationClass):判断该程序元素上是否存在指定类型的注解,如果存在则返回true,否则返回false。
    
    ➢ <A extends Annotation>A[] getAnnotationsByType(Class<A> annotationClass):该方法的功能与前面介绍的getAnnotation()方法基本相似。但由于Java 8增加了重复注解功能,因此需要使用该方法获取修饰该程序元素、指定类型的多个注解。
    
    ➢ <A extends Annotation>A[] getDeclaredAnnotationsByType(Class<A> annotationClass):该方法的功能与前面介绍的getDeclaredAnnotations()方法基本相似。但由于Java 8增加了重复注解功能,因此需要使用该方法获取直接修饰该程序元素、指定类型的多个注解。
    
    getAnnotation与getDeclaredAnnotation主要区别是:
    	getDeclaredAnnotation只能获取直接修饰在程序元素上的注解。
    	getAnnotation能够获取程序元素继承的接口或类上面的修饰的  具有继承性的注解(就是被@Inherited修饰的注解)
    

    注意:能获取到的注解信息必须是这种类型的注解,@Retention(RetentionPolicy.RUNTIME),否则JVM 在加载class的时候拿不到注解中的信息。

    @Inheritable
    class Base{}
    
    @Service("mybatis")
    public class InheritableTest extends Base {
    
        public static void main(String[] args) {
            System.out.println(InheritableTest.class.
                    isAnnotationPresent(Inheritable.class));
    
            InheritableTest test = new InheritableTest();
    
            Annotation[] annotations = test.getClass().getAnnotations();
            for (Annotation a :
                    annotations) {
                System.out.println(a.toString());
            }
    
            Annotation[] annotations1 = test.getClass().getDeclaredAnnotations();
            for (Annotation a :
                    annotations1) {
                System.out.println(a.toString());
                if (a instanceof Service) {
                    System.out.println(((Service) a).value());
                }
            }
        }
    }
    

    4.2、使用注解的示例

    标记注解:程序通过判断该注解存在与否来决定是否运行指定方法

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    /**
     * 用来标记哪些方法是可测试的
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Testable {
    }
    
    public class MyTest {
        @Testable
        public static void m1(){}
        public static void m2(){}
        @Testable
        public static void m3(){
            throw new IllegalArgumentException("参数出错了!!");
        }
        public static void m4(){}
        @Testable
        public static void m5(){}
        public static void m6(){}
        @Testable
        public static void m7(){
            throw new RuntimeException("程序业务出现异常~!!");
        }
        public static void m8(){}
    }
    
    import lombok.extern.slf4j.Slf4j;
    import java.lang.reflect.Method;
    
    @Slf4j
    public class ProcessorTest {
    
        public static void process(String clazz) throws Exception {
            int passed = 0;
            int failed = 0;
            for (Method m:
                    Class.forName(clazz).getMethods()) {
                if (m.isAnnotationPresent(Testable.class)) {
                    try {
                        m.invoke(null);
                        passed++;
                    } catch (Exception e) {
                        log.error("方法{}运行失败,异常:{}", m, e.getCause());
                        failed++;
                    }
                }
            }
            log.info("共运行了{}个方法,其中:
     失败了:{}个,
     成功了:{}个!!!",
                    (passed + failed), failed, passed);
        }
    
        public static void main(String[] args) throws Exception {
            ProcessorTest.process("crazy.java.chapter14.MyTest");
        }
    
    }
    

    元数据注解

    import java.awt.event.ActionListener;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ActionListenerFor {
        Class<? extends ActionListener> listener();
    }
    
    package crazy.java.chapter14;
    
    import javax.swing.*;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    
    public class AnnotationTest {
    
        private JFrame mainWin = new JFrame("使用注解绑定事件监听器");
        @ActionListenerFor(listener = OKListener.class)
        private JButton ok = new JButton("确定");
    
        @ActionListenerFor(listener = CancleListener.class)
        private JButton cancle = new JButton("取消");
    
        public void init(){
            JPanel jp = new JPanel();
            jp.add(ok);
            jp.add(cancle);
            mainWin.add(jp);
            ActionListenerInstaller.processAnnotations(this);
            mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            mainWin.pack();
            mainWin.setVisible(true);
        }
    
        public static void main(String[] args) {
            new AnnotationTest().init();
        }
    }
    
    class OKListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            JOptionPane.showMessageDialog(null, "单击了确定按钮");
        }
    }
    
    class CancleListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            JOptionPane.showMessageDialog(null, "单击了取消按钮");
        }
    }
    
    package crazy.java.chapter14;
    
    import javax.swing.*;
    import java.awt.event.ActionListener;
    import java.lang.reflect.Field;
    
    public class ActionListenerInstaller {
    
        public static void processAnnotations(Object object) {
            try {
                Class c1 = object.getClass();
                for (Field f : c1.getDeclaredFields()) {
                    f.setAccessible(true);
                    ActionListenerFor a = f.getAnnotation(ActionListenerFor.class);
                    Object fObj = f.get(object);
                    if (a != null && fObj != null && fObj instanceof AbstractButton) {
                        Class<? extends ActionListener> listenerClazz = a.listener();
                        ActionListener a1 = listenerClazz.getDeclaredConstructor().newInstance();
                        AbstractButton abstractButton = (AbstractButton) fObj;
                        abstractButton.addActionListener(a1);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    }
    

    4.3、重复注解

    背景:在Java 8以前,同一个程序元素前最多只能使用一个相同类型的注解。

    解决:在同一个元素前使用多个相同类型的注解,则必须使用注解“容器”(JDK8 之前)。Java 8 新增@Repeatable注解开发重复注解

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    /**
     * 容器”注解的保留期必须比它所包含的注解的保留期更长,否则编译器会报错。
     * 这个注解相当于使用重复注解中的容器,
     * 用一个注解中的数组当作容器用来包容多个重复注解
     *
     * @Repeatable(FkTags.class) 等价于
     * @FkTags({@FkTag(age = 5), @FkTag(name = "疯狂Java", age = 9)})
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface FkTags {
        FkTag[] value();
    }
    
    import java.lang.annotation.*;
    
    @Repeatable(FkTags.class)
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface FkTag {
        String name() default "疯狂软件";
        int age();
    }
    
    import lombok.extern.slf4j.Slf4j;
    import java.lang.annotation.Annotation;
    
    @Log
    @Inheritable
    class BaseFKTag{}
    
    @Slf4j
    //@FkTag(age = 5)
    //@FkTag(name = "疯狂Java", age = 9)  
    @FkTags({@FkTag(age = 5), @FkTag(name = "疯狂Java", age = 9)})
    public class FkTagTest extends BaseFKTag {
    
        public static void main(String[] args) {
            Class<FkTagTest> clazz = FkTagTest.class;
            FkTag[] tags = clazz.getDeclaredAnnotationsByType(FkTag.class);
            for (FkTag ta :
                    tags) {
                log.info("{} ---> {}", ta.name(), ta.age());
            }
            FkTags container = clazz.getDeclaredAnnotation(FkTags.class);
            log.info("container:{}", container);
    
            log.info("annotationType()返回注解类型:{}", container.annotationType());
    
            Annotation[] annotations = clazz.getAnnotations();
            for (Annotation a :
                    annotations) {
                log.info("anotations:{}", a);
            }
    
            Annotation[] declaredAnnotations = clazz.getDeclaredAnnotations();
            for (Annotation a :
                    declaredAnnotations) {
                log.info("declaredAnnotations:{}", a);
            }
        }
    }
    
    import java.lang.annotation.*;
    
    @Inherited
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Log {
        String value() default "";
    }
    

    4.4、类型注解

    Java 8为ElementType枚举增加了TYPE_PARAMETER、TYPE_USE两个枚举值,这样就允许定义注解时使用@Target(ElementType.TYPE_USE)修饰,这种注解被称为类型注解(Type Annotation),类型注解可用于修饰在任何地方出现的类型。

    4.5、使用APT工具

    什么是APT ????

    APT即为Annotation Processing Tool,它是javac的一个工具,中文意思为编译时注解处理器。APT可以用来在编译时扫描和处理注解。通过APT可以获取到注解和被注解对象的相关信息,在拿到这些信息后我们可以根据需求来自动的生成一些代码,省去了手动编写。注意,获取注解及生成代码都是在代码编译时候完成的,相比反射在运行时处理注解大大提高了程序性能。APT的核心是AbstractProcessor类
    

    哪里用到了APT ????

    APT技术被广泛的运用在Java框架中,包括Android项以及Java后台项目,除了上面我们提到的ButterKnife之外,像EventBus 、Dagger2以及阿里的ARouter路由框架等都运用到APT技术,因此要想了解以、探究这些第三方框架的实现原理,APT就是我们必须要掌握的。
    

    APT作用是?????

    APT的主要目的是简化开发者的工作量,因为APT可以在编译程序源代码的同时生成一些附属文件(比如源文件、类文件、程序发布描述文件等),这些附属文件的内容也都与源代码相关。
    

    参考:《疯狂Java讲义 第五版》

  • 相关阅读:
    npm 出现npm ERR! ERESOLVE unable to resolve dependency tree的错误
    Linux基础06 软链接, 硬链接, 查看磁盘状态df, 文件编辑vim(视图模式[批量注释]), 对比文件, 特殊符号, 显示ip地址命令ip, ifconfig, hostname, sed替换命令, awk取列命令
    Linux基础08 组的基本管理, 组的增删改, shell的分类, 环境变量的加载顺序, 登录显示动画, 切换用户su , 用户提权sudo, sudo企业案例
    Linux基础07 用户管理, 用户相关命令(创建, 修改, 删除), 用户创建配置文件, 命令提示符问题, 查看用户登录, 设置用户密码(设置随机复杂密码)
    在Linux上安装Python3
    WRI$_ADV_OBJECTS表过大,导致sysaux表空间不足
    优化器统计跟踪(SYS.EXP_HEAD$ SYS.EXP_OBJ$ SYS.EXP_STAT$不)导致表空间 SYSAUX不断增长
    使用 yapitotypescript 生成接口响应数据的 TS 类型声明
    git 忽略文件提交的几种姿势
    android项目和model存在同一个类调用时解决方法 L
  • 原文地址:https://www.cnblogs.com/myfaith-feng/p/13786109.html
Copyright © 2020-2023  润新知