四、注解详解

四、注解

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讲义 第五版》

原文地址:https://www.cnblogs.com/myfaith-feng/p/13786109.html