第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
这两个注解专门给注解加注解,我们称之为元注解。
再来分析,我们不妨看看那几个元注解的源码:
From: 元动力1 2 3 4 5 6 7 8 9 10
| @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention {
RetentionPolicy value(); }
|
我们发现注解中可以有方法,我们在注解中可以这样定义方法:
From: 元动力1 2 3 4 5 6
| @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { String name() default "jerry"; int age(); }
|
我们在使用的时候就可以这样使用了:
关于注解方法,有以下几点注意的:
1、定义的格式是:String name();
2、可以有默认值,也可以没有,如果没有默认值在使用的时候必须填写对应的值。默认值使用default添加。
3、如果想在使用的时候不指定具体的名字,方法名字定义为value() 即可。
From: 元动力1 2 3 4
| public @interface MyAnnotation { String name() default "jerry"; int value(); }
|
看到这里,有人可能会问,注解到底有什么用?如果我们没有学习反射,对我们而言,注解确实没什么用,哈哈哈,后边我们结合反射会有例子讲解。
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" 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 的作用域不同。
- 若 Annotation 的类型为 SOURCE,则意味着:Annotation 仅存在于编译器处理期间,编译器处理完之后,该 Annotation 就没用了。 例如," @Override" 标志就是一个 Annotation。当它修饰一个方法的时候,就意味着该方法覆盖父类的方法;并且在编译期间会进行语法检查!编译器处理完后,"@Override" 就没有任何作用了。
- 若 Annotation 的类型为 CLASS,则意味着:编译器将 Annotation 存储于类对应的 .class 文件中,它是 Annotation 的默认行为。
- 若 Annotation 的类型为 RUNTIME,则意味着:编译器将 Annotation 存储于 class 文件中,并且可由JVM读入。
From: 元动力1 2 3 4 5 6 7 8 9
| package java.lang.annotation; public enum RetentionPolicy { SOURCE, CLASS, 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 所标注内容,不再被建议使用。
加上这个注解在使用或者重写时会有警告:
@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 不再建议使用警告"和"未检查的转换时的警告"保持沉默。
不用记,谁记谁傻X。
关键字 | 用途 |
---|
all | 抑制所有警告 |
boxing | 抑制装箱、拆箱操作时候的警告 |
fallthrough | 抑制在switch中缺失breaks的警告 |
finally | 抑制finally模块没有返回的警告 |
rawtypes | 使用generics时忽略没有指定相应的类型 |
serial | 忽略在serializable类中没有声明serialVersionUID变量 |
unchecked | 抑制没有进行类型检查操作的警告 |
unused | 抑制没被使用过的代码的警告 |
4、Annotation 的作用
(1)Annotation 具有"让编译器进行编译检查的作用“,这个讲了很多了。
(2)利用反射,和反射配合使用能产生奇妙的化学反应。
二、反射
我们都知道光是可以反射的,我们无法直接接触方法区中一个类的方法、属性、注解等,那就可以通过一面镜子观察它的全貌,这个镜子就是JDK给我们提供的Class类。
首先我们看一下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) { return getInterfaces0(); } else { Class<?>[] interfaces = rd.interfaces; if (interfaces == null) { interfaces = getInterfaces0(); rd.interfaces = interfaces; } 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类,也叫类对象。
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();
Object instance = clazz.newInstance();
|
2、对成员变量的操作
在java中万物皆对象成员变量也是对象,他拥有操作一个对象的成员变量的能力。
(1)获取成员变量
getFields只能获取被public修饰的成员变量,当然反射很牛,我们依然可以使用getDeclaredFields方法获取所有的成员变量。
From: 元动力1 2 3 4 5 6 7 8
| Field name = clazz.getField("type"); Field[] fields = clazz.getFields();
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
|
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 { 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); }
}
|