跳至主要內容

注解和反射

javase注解反射javase大约 12 分钟

一、注解

Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种机制。Java 语言中的类、方法、变量、参数和包等都可以被标注。

1、Annotation 的定义

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

我们仿照 jdk 自带注解的方式,自己定义一个注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}

结果发现这个注解确实可以使用了,同时我们看到了这几个注解@Retention@Target 这两个注解专门给注解加注解,我们称之为元注解。

image-20210912144431542
image-20210912144431542

再来分析,我们不妨看看那几个元注解的源码:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

我们发现注解中可以有方法,我们在注解中可以这样定义方法:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String name() default "jerry";
    int age();
}

我们在使用的时候就可以这样使用了:

image-20210912144933048
image-20210912144933048

关于注解方法,有以下几点注意的:

1、定义的格式是:String name();

2、可以有默认值,也可以没有,如果没有默认值在使用的时候必须填写对应的值。默认值使用 default 添加。

3、如果想在使用的时候不指定具体的名字,方法名字定义为 value() 即可。

public @interface MyAnnotation {
    String name() default "jerry";
    int value();
}
image-20210912145304383
image-20210912145304383

看到这里,有人可能会问,注解到底有什么用?如果我们没有学习反射,对我们而言,注解确实没什么用,哈哈哈,后边我们结合反射会有例子讲解。

2、Annotation 组成部分

我们使用 javap 查看生成的注解类:

PS D:\code\test\out\production\test\com\ydlclass\chat> javap -v .\MyAnnotation.class
Classfile /D:/code/test/out/production/test/com/ydlclass/chat/MyAnnotation.class
  Last modified 2021-9-12; size 482 bytes
  MD5 checksum 5b096a2faeef11535277c9cdbe5703d0
  Compiled from "MyAnnotation.java"
  //我们发现字节码中注解其实也是一个接口统一继承自java.lang.annotation.Annotation
public interface com.ydlclass.chat.MyAnnotation extends java.lang.annotation.Annotation
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
Constant pool:
{
    // 生成了两个抽象方法
  public abstract java.lang.String name();
    descriptor: ()Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_ABSTRACT
    AnnotationDefault:
      default_value: s#7
  public abstract int value();
    descriptor: ()I
    flags: ACC_PUBLIC, ACC_ABSTRACT
}
SourceFile: "MyAnnotation.java"
RuntimeVisibleAnnotations:
  0: #13(#8=[e#14.#15])
  1: #16(#8=e#17.#18)
PS D:\code\test\out\production\test\com\ydlclass\chat>

java Annotation 的组成中,有 3 个非常重要的主干类。它们分别是:

(1)Annotation.java

package java.lang.annotation;
public interface Annotation {

    boolean equals(Object obj);

    int hashCode();

    String toString();

    Class<? extends Annotation> annotationType();
}

(2)ElementType.java

ElementType 是 Enum 枚举类型,它用来指定 Annotation 的类型。大白话就是,说明了我的注解将来要放在哪里。

package java.lang.annotation;

public enum ElementType {
    // 类、接口(包括注释类型)或枚举声明
    TYPE,
    //  字段声明(包括枚举常量
    FIELD,
    //  方法声明
    METHOD,
    //  参数声明
    PARAMETER,
    //  构造方法声明
    CONSTRUCTOR,
    //  局部变量声明
    LOCAL_VARIABLE,
    //   注释类型声明
    ANNOTATION_TYPE,
    //  包声明
    PACKAGE
}

(3)RetentionPolicy.java

RetentionPolicy 是 Enum 枚举类型,它用来指定 Annotation 的策略。通俗点说,就是不同 RetentionPolicy 类型的 Annotation 的作用域不同。

  1. 若 Annotation 的类型为 SOURCE,则意味着:Annotation 仅存在于编译器处理期间,编译器处理完之后,该 Annotation 就没用了。 例如," @Override" 标志就是一个 Annotation。当它修饰一个方法的时候,就意味着该方法覆盖父类的方法;并且在编译期间会进行语法检查!编译器处理完后,"@Override" 就没有任何作用了。
  2. 若 Annotation 的类型为 CLASS,则意味着:编译器将 Annotation 存储于类对应的 .class 文件中,它是 Annotation 的默认行为。
  3. 若 Annotation 的类型为 RUNTIME,则意味着:编译器将 Annotation 存储于 class 文件中,并且可由 JVM 读入。
package java.lang.annotation;
public enum RetentionPolicy {
    //Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了
    SOURCE,
    //编译器将Annotation存储于类对应的.class文件中。但不会加载到JVM中。默认行为
    CLASS,
    // 编译器将Annotation存储于class文件中,并且可由JVM读入,因此运行时我们可以获取。
    RUNTIME
}

3、Java 自带的 Annotation

理解了上面的 3 个类的作用之后,我们接下来可以讲解 Annotation 实现类的语法定义了。

(1)内置的注解

Java 定义了一套注解,共有 10 个,6 个在 java.lang 中,剩下 4 个在 java.lang.annotation 中。

(1)作用在代码的注解是

  • @Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
  • @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
  • @SuppressWarnings - 指示编译器去忽略注解中声明的警告。
  • @SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
  • @FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
  • @Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。

(2)作用在其他注解的注解(或者说 元注解)是:

  • @Retention - 标识这个注解怎么保存,是只在代码中,还是编入 class 文件中,或者是在运行时可以通过反射访问。
  • @Documented - 标记这些注解是否包含在用户文档中。
  • @Target - 标记这个注解可以修饰哪些 Java 成员。
  • @Inherited - 如果一个类用上了@Inherited 修饰的注解,那么其子类也会继承这个注解

(2)常用注解

通过上面的示例,我们能理解:@interface 用来声明 Annotation,@Documented 用来表示该 Annotation 是否会出现在 javadoc 中, @Target 用来指定 Annotation 的类型,@Retention 用来指定 Annotation 的策略。

@Documented 标记这些注解是否包含在用户文档中。

@Inherited

@Inherited 的定义如下:加有该注解的注解会被子类继承,注意,仅针对类,成员属性、方法并不受此注释的影响。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

@Deprecated

@Deprecated 的定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Deprecated {
}

说明:

@Deprecated 所标注内容,不再被建议使用。

image-20210912151947844
image-20210912151947844

加上这个注解在使用或者重写时会有警告:

image-20210912152418045
image-20210912152418045

@SuppressWarnings

@SuppressWarnings 的定义如下:

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

说明:

SuppressWarnings 的作用是,让编译器对"它所标注的内容"的某些警告保持静默,用于抑制编译器产生警告信息。。例如,"@SuppressWarnings(value={"deprecation", "unchecked"})" 表示对"它所标注的内容"中的 "SuppressWarnings 不再建议使用警告"和"未检查的转换时的警告"保持沉默。

image-20210912152848064
image-20210912152848064
image-20210912152804165
image-20210912152804165

不用记,谁记谁傻 X。

关键字用途
all抑制所有警告
boxing抑制装箱、拆箱操作时候的警告
fallthrough抑制在 switch 中缺失 breaks 的警告
finally抑制 finally 模块没有返回的警告
rawtypes使用 generics 时忽略没有指定相应的类型
serial忽略在 serializable 类中没有声明 serialVersionUID 变量
unchecked抑制没有进行类型检查操作的警告
unused抑制没被使用过的代码的警告

4、Annotation 的作用

(1)Annotation 具有"让编译器进行编译检查的作用“,这个讲了很多了。

(2)利用反射,和反射配合使用能产生奇妙的化学反应。

二、反射

我们都知道光是可以反射的,我们无法直接接触方法区中一个类的方法、属性、注解等,那就可以通过一面镜子观察它的全貌,这个镜子就是 JDK 给我们提供的 Class 类。

image-20210913095552369
image-20210913095552369

首先我们看一下 Class 这个类,初步简单的分析一下。我们发现这个类并没有什么成员变量,仅仅存在许多的方法,还有不少是本地方法。通过这些方法的名字我们大致能猜出,这个类能帮我们获取方法、构造器、属性、注解等。

public final class Class<T>  {

    // 获得他实现的接口
    public Class<?>[] getInterfaces() {
        ReflectionData<T> rd = reflectionData();
        if (rd == null) {
            // no cloning required
            return getInterfaces0();
        } else {
            Class<?>[] interfaces = rd.interfaces;
            if (interfaces == null) {
                interfaces = getInterfaces0();
                rd.interfaces = interfaces;
            }
            // defensively copy before handing over to user code
            return interfaces.clone();
        }
    }

    private native Class<?>[] getInterfaces0();

    // 获得方法
    @CallerSensitive
    public Method[] getMethods() throws SecurityException {
        checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
        return copyMethods(privateGetPublicMethods());
    }

    // 获得他的构造器
    @CallerSensitive
    public Constructor<?>[] getConstructors() throws SecurityException {
        checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
        return copyConstructors(privateGetDeclaredConstructors(true));
    }

    // 获得他的属性
    @CallerSensitive
    public Field getField(String name)
        throws NoSuchFieldException, SecurityException {
        checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
        Field field = getField0(name);
        if (field == null) {
            throw new NoSuchFieldException(name);
        }
        return field;
    }

}

我们已经学过了类的加载过程,这里我们要介绍的是,每一个类加载完成后会在方法区生成一个 Class 类型的对象,辅助我们访问这个的方法、构造器、字段等。这个对象是 Class 的子类,每个类【有且仅有】一个 Class 类,也叫类对象。

image-20210912142513858
image-20210912142513858

1、获取类对象的方法

(1)获取方式

1、使用类
Class clazz = Dog.class;

2、使用全类名
Class aClass = Class.forName("com.ydl.Dog");

3、使用对象
Dog dog = new Dog();
Class clazz = dog.getClass();

(2)对类对象操作

//获取类名字
String name = clazz.getName();
//获取类加载器
ClassLoader classLoader = clazz.getClassLoader();
//获取资源
URL resource = clazz.getResource("");
//得到父类
Class superclass = clazz.getSuperclass();
//判断一个类是不是接口,数组等等
boolean array = clazz.isArray();
boolean anInterface = clazz.isInterface();

//重点,使用class对象实例化一个对象
Object instance = clazz.newInstance();

注意

java1.9 版本中,newInstance()已经被弃用,取而代之的是 class.getDeclaredConstructor().newInstance()getDeclaredConstructor()方法会根据他的参数对该类的构造函数进行搜索并返回对应的构造函数,没有参数就返回该类的无参构造函数,然后再通过 newInstance 进行实例化。

来个实例:

public class Test {
    public Test() {
        System.out.println("HelloTest");
    }
    public static void main(String[] args) throws Exception {
        C c = C.class.getDeclaredConstructor(int.class).newInstance(5);
    }
}

class C {
    public C() {}
    private C(int i) {
     System.out.println("HelloC" + i);
    }

}

class.getDeclaredConstructor().newInstance() 更明确化,明确调用的是哪一个构造器,而不是直接采用默认的无参构造器了。

2、对成员变量的操作

在 java 中万物皆对象成员变量也是对象,他拥有操作一个对象的成员变量的能力。

(1)获取成员变量

getFields 只能获取被 public 修饰的成员变量,当然反射很牛,我们依然可以使用 getDeclaredFields 方法获取所有的成员变量。

//获取字段,只能获取公共的字段(public)
Field name = clazz.getField("type");
Field[] fields = clazz.getFields();
//能获取所有的字段包括private
Field color = clazz.getDeclaredField("color");
Field[] fields = clazz.getDeclaredFields();

System.out.println(color.getType());

(2)获取对象的属性

Dog dog = new Dog();
dog.setColor("red");
Class clazz = Dog.class;
Field color = clazz.getDeclaredField("color");
System.out.println(color.get(dog));

当然你要是明确类型你还能用以下方法:

Int i = age.getInt(dog);
xxx.getDouble(dog);
xxx.getFloat(dog);
xxx.getBoolean(dog);
xxx.getChar(dog);
//每一种基本类型都有对应方法

(3)设置对象的属性

Dog dog = new Dog();
dog.setColor("red");
Class clazz = Dog.class;
Field color = clazz.getDeclaredField("color");
color.set(dog,"blue");
System.out.println(dog.getColor());

当然如果你知道对应的类型,我们可以这样:

xxx.setBoolean(dog,true);
xxx.getDouble(dog,1.2);
xxx.getFloat(dog,1.2F);
xxx.getChar(dog,'A');
//每一种基本类型包装类都有对应方法
Field color = dogClass.getDeclaredField("color");
//暴力注入
color.setAccessible(true);
color.set(dog,"red");

3、对方法的操作

(1)获取方法

//根据名字和参数类型获取一个方法
Method method = clazz.getMethod("eat",String.class);
Method[] methods = clazz.getMethods();

Method eat = clazz.getDeclaredMethod("eat", String.class);
Method[] declaredMethods = clazz.getDeclaredMethods();

(2)对方法的操作

Dog dog = new Dog();
dog.setColor("red");
Class clazz = Dog.class;
//获取某个方法,名字,后边是参数类型
Method method = clazz.getMethod("eat",String.class);
//拿到参数的个数
int parameterCount = method.getParameterCount();
//拿到方法的名字
String name = method.getName();
//拿到参数的类型数组
Class<?>[] parameterTypes = method.getParameterTypes();
//拿到返回值类型
Class<?> returnType = method.getReturnType();
//重点。反射调用方法,传一个实例,和参数
method.invoke(dog,"热狗");
Class dogClass = Class.forName("com.xinzhi.Dog");
Object dog = dogClass.newInstance();

Method eat = dogClass.getMethod("eat");
eat.invoke(dog);

Method eat2 = dogClass.getMethod("eat",String.class);
eat2.invoke(dog,"meat");

Method eat3 = dogClass.getMethod("eat",String.class,int.class);
eat3.invoke(dog,"meat",12);

4、对构造器的操作

(1)获取并构建对象

Constructor[] constructors = clazz.getConstructors();
Constructor constructor = clazz.getConstructor();
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
Constructor declaredConstructor = clazz.getDeclaredConstructor();

Object obj = constructor.newInstance();

5、对注解的操作

(1)从方法、字段、类上获取注解

//元注解 要加上runtime
//类上
Annotation annotation = clazz.getAnnotation(Bean.class);
Annotation[] annotations = clazz.getAnnotations();

//字段上
Annotation annotation = field.getAnnotation(Bean.class);
Annotation[] annotations = field.getAnnotations();

//方法上
Annotation annotation = method.getAnnotation(Bean.class);
Annotation[] annotations = method.getAnnotations();

三、写一个小案例

要求:讲 src 源文件中加了@Singleton注解的类都在程序启动时以【单例】的形式加载到内存。

提示:

1、获取 classpath 文件的方法:

URL resource = Thread.currentThread().getContextClassLoader().getResource("");
String file = resource.getFile();

2、所有的单例放在一个 ConcurrentHashMap 当中:

public class  ApplicationContext {
    private final ConcurrentHashMap<Class<?>,Object> context = new ConcurrentHashMap<>();

    public void registerSingleton(Class<?> clazz,Object t){
        context.put(clazz, t);
    }

    @SuppressWarnings("unchecked")
    public <T> T getSingleton(Class<T> clazz){
        return  (T)context.get(clazz);
    }
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Singleton {
}
public class SingletonHandler {

    public static void handler(List<String> classNames){
        for (String className : classNames) {
            Class<?> clazz = null;
            try {
                clazz = Class.forName(className);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            // 获取注解
            Singleton annotation = clazz.getAnnotation(Singleton.class);
            if(annotation != null){
                Object instance = null;
                try {
                    instance = clazz.newInstance();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                ApplicationContext.addSingleton(clazz,instance);
            }
        }
    }
}public class ApplicationContext {
    // 维护一个上下文环境
    private final static Map<Class<?>,Object> CONTEXT = new ConcurrentHashMap<>(8);

    public static void addSingleton(Class<?> clazz,Object entity){
        ApplicationContext.CONTEXT.put(clazz,entity);
    }

    // 把实例对象从容器拿出来
    public static  <T> T getSingleton(Class<T> clazz){
        return (T)ApplicationContext.CONTEXT.get(clazz);
    }
}
public class FileUtils {

    public static List<String> getAllClassName(File file) {
        // 1、自己定义一个集合
        List<String> classPaths = new ArrayList<>();

        // 2、获取所有的class文件的路径
        findAll(file.getAbsolutePath(),classPaths);
        // D:\code\javase\out\production\annotationAndReflect\com\ydlclass\Dog.class
        // com.ydlclass.Dog   Class.forName()
        // 遍历绝对路径,变成全限定名
        return classPaths.stream().map(path -> {
            String fileName = file.getAbsolutePath();
            // D:\code\javase\out\production\annotationAndReflect\com\ydlclass\Dog.class
            // com\ydlclass\Dog.class
            return path.replace(fileName + "\\", "")
                    .replaceAll("\\\\",".")
                    .replace(".class","");
        }).collect(Collectors.toList());
    }



    private static void findAll(String path,List<String> classPathList) {
        // 1、尝试列出当前文件夹的文件
        File file = new File(path);
        // 2、过滤文件  文件夹和png
        File[] list = file.listFiles((f, n) -> new File(f, n).isDirectory() || n.contains(".class"));

        if (list == null || list.length == 0) {
            return;
        }
        for (File parent : list) {
            // 看看是不是一个文件夹,如果是
            if (parent.isDirectory()) {
                // 递归
                findAll(parent.getAbsolutePath(),classPathList);
            } else {
                // 如果不是
                classPathList.add(parent.getAbsolutePath());
            }
        }
    }
}
public class Bootstrap {
    // 类加载之后就会处理
    static {
        // 获取classpath根路径
        final URL resource = Thread.currentThread().getContextClassLoader().getResource("");
        // 一句话获取权限名称
        List<String> classNames = FileUtils.getAllClassName(new File(resource.getFile()));
        // 处理对应的全限定名称
        SingletonHandler.handler(classNames);
    }

    public static void main(String[] args) {
        Dog singleton = ApplicationContext.getSingleton(Dog.class);
        System.out.println(singleton);
    }

}