📖 小记:毕设快开题了,本来和清华的老师商量着做 Android 安全的课题,一直在看相关论文。最近又说和 Swinburn 那边做,不过做好了可以去华为交流,也挺好。在看 Android 安全的论文时,曾想到一个idea,利用Java的注解和反射机制,在程序运行时加一些感兴趣的信息。注解和反射我之前也没有写过博客,特此记录。
在讲反射前,我们先谈谈静态语言和动态语言。众所周知,Java是一门静态语言,变量的数据类型在声明时即确定。JavaScript是一种常见的动态语言,其变量的数据类型在运行时才能确定且不唯一,会随着程序而改变。
动态语言 | 静态语言 |
---|---|
JavaScript、Python | Java、C++、C# |
但在反射机制的帮助下,Java可以视为一种“准动态语言”,通过反射实现类似动态语言的效果,使编程更加灵活。
同时,Java又是面向对象的语言,所有功能的实现依靠一个个对象。
正常的对象调用首先需要 import 对应的包(package),然后通过 new 实例化一个对象,最后调用对象的方法或属性。
反射则是反其道而行之,通过对象获取对应类的信息,主要有以下方式:
// 通过forName获取类的class对象,括号内为完整包名+类名
Class cls1 = Class.forName("com.bean.User");
Class cls2 = Class.forName("com.bean.User");
System.out.println(cls1.hashCode()+" "+cls2.hashCode()); // 同一个class
// 通过对象获得
Class cls3 = usr1.getClass();
// 通过.class获得
Class cls4 = User.class;
// 获得父类类型
Class cls5 = cls1.getSuperclass();
System.out.println(cls5);
Class 是从 JDK 1.0 起就实现的一个类,可以理解为“管理类的一个类”。通过反射得到 Class 后,就可以调用 Class 的一些方法,其中很重要的就有如 getAnnotations、getFields、getMethods 等,分别得到类的注解、属性、方法等信息。
反射搭配注解,大大提高了 Java 语言的灵活性。
注解在 SpringBoot 中有广泛的应用,也是 Java 高级编程的一部分。在详细讲解前,可以将注解认为是标签,它允许开发者在不改变代码逻辑的情况下,为源代码加入一些补充信息。
一个例子 MyAnnotation
@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
// 注解的参数格式 参数类型 + 参数名()
String name() default "";
int age() default 0;
int id() default -1; // 若默认值为-1,表示不存在
}
可以看出,注解本质上是一个 @interface 类型,但和普通类型不同的是,下面声明的是参数名而不是方法名。声明完参数后还可以通过 default 关键字写默认值。在 @interface 前还有两个注解,@Target 和 @Retention,它们被称为元注解,即修饰注解的注解。
Java 中定义了四类元注解,它们在声明自定义注解时使用,分别是 Target、Retention、Documented 和 Inherited。
该注解用于标识自定义注解适用的代码上下文,可选参数由 java.lang.annotation.ElementType 中的枚举常量决定。
public enum ElementType {
/** 以下从 JDK5 开始支持 */
TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR,
LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE,
/** 以下两个从 JDK8 开始支持 */
TYPE_PARAMETER, TYPE_USE
}
@MyAnnotation 中声明的适用类型为 TYPE 和 MTTHOD,说明该注解可以用于方法或者类。
Retention 翻译过来是保留,即注解信息会在什么情况下保留。有三种保留策略,在 java.lang.annotation.RetentionPolicy 中声明。
public enum RetentionPolicy {
/** 源码级 注解会被编译器丢弃 */
SOURCE,
/** 类文件级 这是默认级别
* 注解会被记录在编译器编译完的 .class 文件中,但不会被JVM在运行时保留 */
CLASS,
/** 运行时级 注解不仅保留在 .class 文件中,还被JVM在运行时保留
* 可通过反射机制被读到 */
RUNTIME
}
从源码可以看出,保留程度 RUNTIME > CLASS > SOURCE。如果要和反射机制配合使用,必须声明为 RUNTIME。
Java还有三类内置注解,Override、Deprecated 和 SuppressWarnings。
Override 是最常见的,表示重写父类或接口的方法。Deprecated 表示该方法或类已被弃用,可能影响功能实现。SuppressWarnings 表示被该注解修饰的类、方法、属性等报出的警告,会被编译器抑制。