第14章 注解和反射

一、注解:

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

1、Annotation 的定义

From: 元动力
1
2
3
4
5
6
7
8
9
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

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

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

From: 元动力
1
2
3
4
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}

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

image-20210912144431542
image-20210912144431542

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

From: 元动力
1
2
3
4
5
6
7
8
9
10
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}

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

From: 元动力
1
2
3
4
5
6
@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() 即可。

From: 元动力
1
2
3
4
public @interface MyAnnotation {
String name() default "jerry";
int value();
}
image-20210912145304383
image-20210912145304383

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

2、Annotation 组成部分

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

From: 元动力
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
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

From: 元动力
1
2
3
4
5
6
7
8
9
10
11
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 的类型。大白话就是,说明了我的注解将来要放在哪里。

From: 元动力
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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读入。
From: 元动力
1
2
3
4
5
6
7
8
9
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 的定义如下:加有该注解的注解会被子类继承,注意,仅针对类,成员属性、方法并不受此注释的影响。

From: 元动力
1
2
3
4
5
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

@Deprecated

@Deprecated 的定义如下:

From: 元动力
1
2
3
4
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Deprecated {
}

说明:

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

image-20210912151947844
image-20210912151947844

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

image-20210912152418045
image-20210912152418045

@SuppressWarnings

@SuppressWarnings 的定义如下:

From: 元动力
1
2
3
4
5
@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这个类,初步简单的分析一下。我们发现这个类并没有什么成员变量,仅仅存在许多的方法,还有不少是本地方法。通过这些方法的名字我们大致能猜出,这个类能帮我们获取方法、构造器、属性、注解等。

From: 元动力
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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)获取方式

From: 元动力
1
2
3
4
5
6
7
8
9
1、使用类
Class clazz = Dog.class;

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

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

(2)对类对象操作

From: 元动力
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//获取类名字
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();

2、对成员变量的操作

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

(1)获取成员变量

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

From: 元动力
1
2
3
4
5
6
7
8
//获取字段,只能获取公共的字段(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)获取对象的属性

From: 元动力
1
2
3
4
5
Dog dog = new Dog();
dog.setColor("red");
Class clazz = Dog.class;
Field color = clazz.getDeclaredField("color");
System.out.println(color.get(dog));

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

From: 元动力
1
2
3
4
5
6
Int i = age.getInt(dog);
xxx.getDouble(dog);
xxx.getFloat(dog);
xxx.getBoolean(dog);
xxx.getChar(dog);
//每一种基本类型都有对应方法

(3)设置对象的属性

From: 元动力
1
2
3
4
5
6
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());

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

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

3、对方法的操作

(1)获取方法

From: 元动力
1
2
3
4
5
6
//根据名字和参数类型获取一个方法
Method method = clazz.getMethod("eat",String.class);
Method[] methods = clazz.getMethods();

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

(2)对方法的操作

From: 元动力
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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,"热狗");
From: 元动力
1
2
3
4
5
6
7
8
9
10
11
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)获取并构建对象

From: 元动力
1
2
3
4
5
6
Constructor[] constructors = clazz.getConstructors();
Constructor constructor = clazz.getConstructor();
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
Constructor declaredConstructor = clazz.getDeclaredConstructor();

Object obj = constructor.newInstance();

5、对注解的操作

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

From: 元动力
1
2
3
4
5
6
7
8
9
10
11
12
//元注解 要加上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文件的方法:

From: 元动力
1
2
URL resource = Thread.currentThread().getContextClassLoader().getResource("");
String file = resource.getFile();

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

From: 元动力
1
2
3
4
5
6
7
8
9
10
11
12
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);
}
}
From: 元动力
1
2
3
4
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Singleton {
}
From: 元动力
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
35
36
37
38
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);
}
}
From: 元动力
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
35
36
37
38
39
40
41
42
43
44
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());
}
}
}
}
From: 元动力
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
}

}

本站由 钟意 使用 Stellar 1.28.1 主题创建。
又拍云 提供CDN加速/云存储服务
vercel 提供托管服务
湘ICP备2023019799号-1
总访问 次 | 本页访问