项目所有演示代码见:源码
1. Java类型系统
获取Java类型系统,主要有两个方式:一种是传统的RTTI(Run-Time Type Identification),它假定我们在编译时已经知道了所有的类型信息;另一种是反射(Reflect),它允许我们在程序运行时获取并使用类型信息。
假如有一个简单的继承体系,让我们看下在 RTTI 和 Reflect 不同情况下如何获取类型信息。
Animal 为接口,定义 getType 以返回不同动物的类型,Cat、Dog、Elephant 等为具体实现类,均实现 getType 接口。一般情况下,我们会创建一个具体的对象(Cat,Dog,Elephant等),把它向上转型为Animal,并在程序后面直接使用 Animal 引用。
具体样例代码如下:
/**
* 动物
*/
public interface Animal {
/**
* 获取动物类型
* @return
*/
String getType();
}
/**
* 动物具体子类 猫
*/
public class Cat implements Animal{
@Override
public String getType() {
return "猫";
}
}
/**
* 动物具体子类 狗
*/
public class Dog implements Animal{
@Override
public String getType() {
return "狗";
}
}
/**
* 动物具体实现 大象
*/
public class Elephant implements Animal{
@Override
public String getType() {
return "大象";
}
}
让我们看下相同的功能通过 硬编码 与 反射 两个机制如何实现。
1.1. 硬编码
RTTI 假定在编译期,已经知道了所有的类型信息。在编码时,可以直接使用具体的类型信息,这是我们最常见的类型用法。
在编译期,编译器通过容器、泛型保障类型系统的完整性;在运行时,由类型转换操作来确保这一点。
硬编码样例如下:
public static void main(String... args){
List<Animal> animals = createAnimals();
for (Animal animal : animals){
System.out.println(animal.getType());
}
}
/**
* RTTI假定我们在编译时已经知道了所有的类型
* @return
*/
private static List<Animal> createAnimals() {
List<Animal> animals = new ArrayList<>();
animals.add(new Cat()); // 已知类型Cat
animals.add(new Elephant()); // 已知类型Elephant
animals.add(new Dog()); // 已知类型 Dog
return animals;
}
在这个例子中,我们把 Cat、Elephant、Dog 等向上转型为 Animal 并存放于 List<Animal> 中,在转型过程中丢失了具体的类型信息(只保留了接口信息 Animal );当我们从 List<Animal> 中取出元素时,这时容器(容器内部所有的元素都被当做 Object)会自动将结果转型成 Animal。这是 RTTI 最基本的用法,在 Java 中所有的类型转换都是在运行时进行有效性检查。这也是 RTTI 的含义,在运行时,识别一个对象的类型。
1.2. Reflect
Reflect 允许我们在运行时获取并使用类型信息,它主要用于在编译阶段无法获得所有的类型信息的场景,如各类框架。
反射样例如下:
private static final String[] ANIMAL_TYPES = new String[]{
"com.example.reflectdemo.base.Cat",
"com.example.reflectdemo.base.Elephant",
"com.example.reflectdemo.base.Dog"
};
public static void main(String... args){
List<Object> animals = createAnimals();
for (Object animal : animals){
System.out.println(invokeGetType(animal));
}
}
/**
* 利用反射API执行getType方法(等同于animal.getType)
* @param animal
* @return
*/
private static String invokeGetType(Object animal){
try {
Method getTypeMethod = Animal.class.getMethod("getType");
return (String) getTypeMethod.invoke(animal);
}catch (Exception e){
return null;
}
}
/**
* 反射允许我们在运行时获取类型信息
* @return
*/
private static List<Object> createAnimals() {
List<Object> animals = new ArrayList<>();
for (String cls : ANIMAL_TYPES){
animals.add(instanceByReflect(cls));
}
return animals;
}
/**
* 使用反射机制,在运行时动态的实例化对象(等同于new关键字)
* @param clsStr
* @return
*/
private static Object instanceByReflect(String clsStr) {
try {
// 通过反射获取类型信息
Class cls = Class.forName(clsStr);
// 通过Class实例化对象
Object object = cls.newInstance();
return object;
}catch (Exception e){
e.printStackTrace();
return null;
}
}
反射,可以通过一组特殊的 API,在运行时,动态执行所有 Java 硬编码完成的功能(如对象创建、方法调用等)。
相比硬编码,Java 反射 API 要复杂的多,但其给我们带来了更大的灵活性。
2. Class对象
要理解 RTTI 在 Java 中的工作原理,首先需要知道类型信息在 Java 中是如何表示的。这个工作是由称为 Class 对象的特殊对象完成的,它包含了与类相关的所有信息。Java 使用 Class 对象来执行 RTTI。
类是程序的一部分,每个类都会有一个 Class 对象。每当编写并编译一个新类(动态代理、CGLIB、运行时编译都能创建新类),就会产生一个 Class 对象,为了生成这个类的对象,运行这个程序的 JVM 将使用称为 “类加载器” 的子系统。
2.1. Class Loader
类加载器子系统,是 JVM 体系重要的一环,主要完成将 class 二进制文件加载到 JVM 中,并将其转换为 Class 对象的过程。
类加载器子系统实际上是一条类加载器链,但是只有一个原生类加载器,它是 JVM 实现的一部分。原生类加载器加载的是可信类,包括 Java API 类,他们通常是从本地加载。在这条链中,通常不需要添加额外的类加载器,但是如果有特殊需求,可以挂载新的类加载器(比如 Web 容器)。
所有的类都是在第一次使用时,动态加载到 JVM 中的,当程序第一次对类的静态成员引用时,就会加载这个类。实际上构造函数也是类的静态方法,因此使用 new 关键字创建类的新对象也会被当做对类的静态引用,从而触发类加载器对类的加载。
Java 程序在它开始运行之前并非被全部加载,各个部分是在需要时按需加载的。类加载器在加载类之前,首先检查这个类的 Class 是否已经加载,如果尚未加载,加载器会按照类名查找 class 文件,并对字节码进行有效性校验,一旦 Class 对象被载入内存,它就用来创建这个类的所有对象。
static初始化块 在类加载时调用,因此可以用于观察类在什么时候进行加载,样例如下:
static class C1{
static {
System.out.println("C1");
}
}
static class C2{
static {
System.out.println("C2");
}
}
static class C3{
static {
System.out.println("C3");
}
}
public static void main(String... args) throws Exception{
System.out.println("new start");
// 构造函数为类的静态引用,触发类型加载
new C1();
new C1();
System.out.println("new end");
System.out.println();
System.out.println("Class.forName start");
// Class.forName为Class上的静态函数,用于强制加载Class
Class.forName("com.example.reflectdemo.classloader.ClassLoaderTest$C2");
Class.forName("com.example.reflectdemo.classloader.ClassLoaderTest$C2");
System.out.println("Class.forName end");
System.out.println();
System.out.println("C3.class start");
// Class引用,会触发Class加载,但是不会触发初始化
Class c1 = C3.class;
Class c2 = C3.class;
System.out.println("C3.class end");
System.out.println();
System.out.println("c1.newInstance start");
// 调用class上的方法,触发初始化逻辑
c1.newInstance();
System.out.println("c1.newInstance end");
}
输出结果为:
new start
C1
new end
Class.forName start
C2
Class.forName end
C3.class start
C3.class end
c1.newInstance start
C3
c1.newInstance end
看结果,C3.class 的调用不会自动的初始化该 Class 对象(调用 static 块)。为了使用 Class 而做的准备工作主要包括三个步骤:
- 加载,这个是由类加载器执行。该步骤将查找字节码文件,并根据字节码创建一个 Class 对象。
- 链接,在链接阶段将验证类中的字节码,为静态域分配存储空间,如果必要的话,将解析这个类创建的对其他类的引用。
- 初始化,如果该类有超类,则对其进行初始化,执行静态初始化器和静态初始化块。初始化被延时到对静态方法或非常数静态域进行首次访问时才执行。
2.2. Class 实例获取
Class 对象作为 Java 类型体系的入口,如何获取实例成为第一个要解决的问题。
Class 对象的获取主要有以下几种途径:
- ClassName.class,获取 Class 对象最简单最安全的方法,其在编译时会受到编译检测,但上例中已经证实,该方法不会触发初始化逻辑。
- Class.forName,这是反射机制最常用的方法之一,可以在不知具体类型时,通过一个字符串加载所对应的 Class 对象。
- object.getClass,这也是比较常用的方式之一,通过一个对象获取生成该对象的 Class 实例。
对于基本数据类型对应的包装器类,还提供了一个 TYPE 字段,指向对应的基本类型的 Class 对象。
基本类型 | TYPE类型 |
---|---|
boolean.class | Boolean.TYPE |
char.class | Char.TYPE |
byte.class | Byte.TYPE |
short.class | Short.TYPE |
int.class | Integer.TYPE |
long.class | Long.TYPE |
float.class | Float.TYPE |
double.class | Double.TYPE |
void.class | Void.TYPE |
2.3. Class 类型信息
Class 对象存储了一个 class 的所有信息,当获取到 Class 对象后,便能通过 API 获取到这些信息。
在进入 Class 类型信息之前,需要简单的了解下几个反射的基类,以便更好的理解反射实现体系。
2.3.1 Class API 基础
Class API 基础主要是为反射 API 提供通用特性的接口或基类。由于其通用性,现统一介绍,在具体的 API 中将对其进行忽略。
2.3.1.1 AnnotatedElement
AnnotatedElement 为 Java1.5 新增接口,该接口代表程序中可以接受注解的程序元素,并提供统一的 Annotation 访问方式,赋予 API 通过反射获取 Annotation 的能力,当一个 Annotation 类型被定义为运行时后,该注解才能是运行时可见,当 class 文件被装载时被保存在 class 文件中的 Annotation 才会被虚拟机读取。
AnnotatedElement接口是所有注解元素(Class、Method、Field、Package 和 Constructor)的父接口,所以程序通过反射获取了某个类的 AnnotatedElement 对象之后,程序就可以调用该对象的下列方法来访问 Annotation 信息:
方法 | 含义 |
---|---|
<T extends Annotation> T getAnnotation(Class<T> annotationClass) | 返回程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null |
Annotation[] getAnnotations() | 返回该程序元素上存在的所有注解 |
boolean is AnnotationPresent(Class<?extends Annotation> annotationClass) | 判断该程序元素上是否包含指定类型的注解,存在则返回 true,否则返回 false |
Annotation[] getDeclaredAnnotations() | 返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。 |
AnnotatedElement子类涵盖所有可以出现Annotation的地方,其中包括:
- Constructor 构造函数
- Method 方法
- Class 类型
- Field 字段
- Package 包
- Parameter 参数
- AnnotatedParameterizedType 泛型
- AnnotatedTypeVariable 变量
- AnnotatedArrayType 数组类型
- AnnotatedWildcardType
样例如下:
public class AnnotatedElementTest {
public static void main(String... args){
System.out.println("getAnnotations:");
for (Annotation annotation : A.class.getAnnotations()){
System.out.println(annotation);
}
System.out.println();
System.out.println("getAnnotation:" + A.class.getAnnotation(TestAnn1.class));
System.out.println();
System.out.println("isAnnotationPresent:" + A.class.isAnnotationPresent(TestAnn1.class));
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnn1{
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnn2{
}
@TestAnn1
@TestAnn2
public class A{
}
}
输出结果如下:
getAnnotations:
@com.example.reflectdemo.annotatedElement.AnnotatedElementTest$TestAnn1()
@com.example.reflectdemo.annotatedElement.AnnotatedElementTest$TestAnn2()
getAnnotation:@com.example.reflectdemo.annotatedElement.AnnotatedElementTest$TestAnn1()
isAnnotationPresent:true
2.3.1.2. Member
Member用于标记反射中简单元素。
所涉及方法如下:
方法 | 含义 |
---|---|
getDeclaringClass | 元素所在类 |
getName | 元素名称 |
getModifiers | 元素修饰 |
isSynthetic | 是否为 Synthetic,synthetic 是由编译器引入的字段、方法、类或其他结构,主要用于 JVM 内部使用。 |
其子类主要包括:
- Class 类型
- Field 字段
- Method 方法
- Constructor 构造函数
2.3.1.3. AccessibleObject
AccessibleObject 可访问对象,其对元素的可见性进行统一封装。同时实现 AnnotatedElement 接口,提供对 Annotation 元素的访问。
所涉及方法如下:
方法 | 含义 |
---|---|
isAccessible | 是否可访问 |
setAccessible | 重新访问性 |
其中 AccessibleObject 所涉及的子类主要包括:
- Field 字段
- Constructor 构造函数
- Method 方法
AccessibleObject 对可见性提供了强大的支持,使我们能够通过反射扩展访问限制,甚至可以对 private成员进行访问。
样例代码如下:
public class TestBean {
private String id;
public String getId() {
return id;
}
private void setId(String id) {
this.id = id;
}
}
public class AccessibleObjectBase {
public static void main(String... args) throws Exception{
TestBean testBean = new TestBean();
// private方法, 不能直接调用
Method setId = TestBean.class.getDeclaredMethod("setId", String.class);
System.out.println("setId:" + setId.isAccessible());
try {
setId.invoke(testBean, "111");
}catch (Exception e){
System.out.println("private不能直接调用");
}
setId.setAccessible(true);
System.out.println("设置可访问:" + setId.isAccessible());
setId.invoke(testBean, "111");
System.out.println("设置可访问后,可以绕过private限制,进行调用,结果为:" + testBean.getId());
}
}
输出结果如下:
setId:false
private不能直接调用
设置可访问:true
设置可访问后,可以绕过private限制,进行调用,结果为:111
2.3.1.4. Executable
Executable 表示可执行元素的一种封装,可以获取方法签名相关信息。
所涉及方法如下:
方法 | 含义 |
---|---|
getName | 获取名称 |
getModifiers | 获取修饰符 |
getTypeParameters | 获取类型参数(泛型) |
getParameterTypes | 获取参数列表 |
getParameterCount | 获取参数数量 |
getGenericParameterTypes | 获取参数类型 |
getExceptionTypes | 获取异常列表 |
getGenericExceptionTypes | 获取异常列表 |
锁涉及的子类主要有:
- Constructor 构造函数
- Method 方法
样例代码如下:
public class TestBean {
private String id;
public <T, R>TestBean(String id) throws IllegalArgumentException, NotImplementedException {
this.id = id;
}
public String getId() {
return id;
}
private void setId(String id) {
this.id = id;
}
}
public class ExecutableTest {
public static void main(String... args) throws Exception{
for (Constructor constructor : TestBean.class.getConstructors()){
System.out.println("getName: " + constructor.getName());
System.out.println();
System.out.println("getModifiers: " + Modifier.toString(constructor.getModifiers()));
System.out.println();
System.out.println("getTypeParameters:");
for (TypeVariable<Constructor> t : constructor.getTypeParameters()){
System.out.println("type var:" + t.getName());
}
System.out.println();
System.out.println("getParameterCount:" + constructor.getParameterCount());
System.out.println();
System.out.println("getParameterTypes:");
for (Class cls : constructor.getParameterTypes()){
System.out.println(cls.getName());
}
System.out.println();
System.out.println("getExceptionTypes:");
for (Class cls : constructor.getExceptionTypes()){
System.out.println(cls.getName());
}
}
}
}
输出结果为:
getName: com.example.reflectdemo.reflectbase.TestBean
getModifiers: public
getTypeParameters:
type var:T
type var:R
getParameterCount:1
getParameterTypes:
java.lang.String
getExceptionTypes:
java.lang.IllegalArgumentException
sun.reflect.generics.reflectiveObjects.NotImplementedException
2.3.1.5. 方法命名规则
整个反射机制存在着通用的命名规则,了解这些规则,可以大大减少理解方法的阻力。
getXXX 和 getDeclaredXXX, 两者主要区别在于获取元素的可见性不同,一般情况下 getXXX 返回 public 类型的元素,而 getDeclaredXXX 获取所有的元素,其中包括private、protected、public 和 package。
2.3.2. 类型信息
Class 自身信息包括类名、包名、父类以及实现的接口等。
Class类实现 AnnotatedElement 接口,以提供对注解的支持。除此以外,涉及方法如下:
方法 | 含义 |
---|---|
getName | 获取类名 |
getCanonicalName | 得到目标类的全名(包名+类名) |
getSimpleName | 等同于getCanonicalName |
getTypeParameters | 获取类型参数(泛型) |
getSuperclass | 获取父类 |
getPackage | 获取包信息 |
getInterfaces | 获取实现接口 |
getModifiers | 获取修饰符 |
isAnonymousClass | 是否匿名类 |
isLocalClass | 是否局部类 |
isMemberClass | 是否成员类 |
isEnum | 是否枚举 |
isInterface | 是否是接口 |
isArray | 是否是数组 |
getComponentType | 获取数组元素类型 |
isPrimitive | 是否是基本类型 |
isAnnotation | 是否是注解 |
getEnumConstants | 获取枚举所有类型 |
getClasses | 获取定义在该类中的public类型 |
getDeclaredClasses | 获取定义在该类中的类型 |
实例如下:
class Base<T> implements Callable<T> {
@Override
public T call() throws Exception {
return null;
}
}
public final class BaseClassInfo<T, R extends Runnable> extends Base<T> implements Runnable, Serializable {
@Override
public void run() {
}
public static void main(String... args){
Class<BaseClassInfo> cls = BaseClassInfo.class;
System.out.println("getName:" + cls.getName());
System.out.println();
System.out.println("getCanonicalName:" + cls.getCanonicalName());
System.out.println();
System.out.println("getSimpleName:" + cls.getSimpleName());
System.out.println();
System.out.println("getSuperclass:" + cls.getSuperclass());
System.out.println();
System.out.println("getPackage:" + cls.getPackage());
System.out.println();
for (Class c : cls.getInterfaces()){
System.out.println("interface : " + c.getSimpleName());
}
System.out.println();
for (TypeVariable<Class<BaseClassInfo>> typeVariable : cls.getTypeParameters()){
System.out.println("type var : " + typeVariable.getTypeName());
}
System.out.println();
System.out.println("getModifiers:" + Modifier.toString(cls.getModifiers()));
}
}
输出结果为:
getName:com.example.reflectdemo.classdetail.BaseClassInfo
getCanonicalName:com.example.reflectdemo.classdetail.BaseClassInfo
getSimpleName:BaseClassInfo
getSuperclass:class com.example.reflectdemo.classdetail.Base
getPackage:package com.example.reflectdemo.classdetail
interface : Runnable
interface : Serializable
type var : T
type var : R
getModifiers:public final
Class类型判断,实例如下:
public class ClassTypeTest {
public static void main(String... args){
Runnable runnable = new Runnable() {
@Override
public void run() {
printClassType(getClass());
}
};
System.out.println("匿名内部类");
runnable.run();
class M implements Runnable{
@Override
public void run() {
printClassType(getClass());
}
}
System.out.println("方法内部类");
new M().run();
System.out.println("内部类");
new ClassTypeTest().new T().run();
System.out.println("静态内部类");
new S().run();
System.out.println("枚举");
printClassType(EnumTest.class);
System.out.println("接口");
printClassType(Runnable.class);
System.out.println("数组");
printClassType(int[].class);
System.out.println("int");
printClassType(int.class);
System.out.println("注解");
printClassType(AnnTest.class);
}
class T implements Runnable{
@Override
public void run() {
printClassType(getClass());
}
}
static class S implements Runnable{
@Override
public void run() {
printClassType(getClass());
}
}
enum EnumTest{
A, B, C
}
@interface AnnTest{
}
private static void printClassType(Class cls){
System.out.println("Class:" + cls.getName());
System.out.println("isAnonymousClass:" + cls.isAnonymousClass());
System.out.println("isLocalClass:" + cls.isLocalClass());
System.out.println("isMemberClass:" + cls.isMemberClass());
System.out.println("isEnum:" + cls.isEnum());
System.out.println("isInterface:" + cls.isInterface());
System.out.println("isArray:" + cls.isArray());
System.out.println("isPrimitive:" + cls.isPrimitive());
System.out.println("isAnnotation:" + cls.isAnnotation());
if (cls.isEnum()){
System.out.println("getEnumConstants:");
for (Object o : cls.getEnumConstants()){
System.out.println(o);
}
}
if (cls.isArray()){
System.out.println("getComponentType:" + cls.getComponentType());
}
System.out.println();
}
}
输出结果如下:
匿名内部类
Class:com.example.reflectdemo.classdetail.ClassTypeTest$1
isAnonymousClass:true
isLocalClass:false
isMemberClass:false
isEnum:false
isInterface:false
isArray:false
isPrimitive:false
isAnnotation:false
方法内部类
Class:com.example.reflectdemo.classdetail.ClassTypeTest$1M
isAnonymousClass:false
isLocalClass:true
isMemberClass:false
isEnum:false
isInterface:false
isArray:false
isPrimitive:false
isAnnotation:false
内部类
Class:com.example.reflectdemo.classdetail.ClassTypeTest$T
isAnonymousClass:false
isLocalClass:false
isMemberClass:true
isEnum:false
isInterface:false
isArray:false
isPrimitive:false
isAnnotation:false
静态内部类
Class:com.example.reflectdemo.classdetail.ClassTypeTest$S
isAnonymousClass:false
isLocalClass:false
isMemberClass:true
isEnum:false
isInterface:false
isArray:false
isPrimitive:false
isAnnotation:false
枚举
Class:com.example.reflectdemo.classdetail.ClassTypeTest$EnumTest
isAnonymousClass:false
isLocalClass:false
isMemberClass:true
isEnum:true
isInterface:false
isArray:false
isPrimitive:false
isAnnotation:false
getEnumConstants:
A
B
C
接口
Class:java.lang.Runnable
isAnonymousClass:false
isLocalClass:false
isMemberClass:false
isEnum:false
isInterface:true
isArray:false
isPrimitive:false
isAnnotation:false
数组
Class:[I
isAnonymousClass:false
isLocalClass:false
isMemberClass:false
isEnum:false
isInterface:false
isArray:true
isPrimitive:false
isAnnotation:false
getComponentType:int
int
Class:int
isAnonymousClass:false
isLocalClass:false
isMemberClass:false
isEnum:false
isInterface:false
isArray:false
isPrimitive:true
isAnnotation:false
注解
Class:com.example.reflectdemo.classdetail.ClassTypeTest$AnnTest
isAnonymousClass:false
isLocalClass:false
isMemberClass:true
isEnum:false
isInterface:true
isArray:false
isPrimitive:false
isAnnotation:true
内部类型样例如下:
public class InnerClassTest {
public static void main(String... args){
System.out.println("getClasses");
for (Class cls : InnerClassTest.class.getClasses()){
System.out.println(cls.getName());
}
}
public interface I{
}
public class A implements I{
}
public class B implements I{
}
}
输出结果如下:
getClasses
com.example.reflectdemo.classdetail.InnerClassTest$B
com.example.reflectdemo.classdetail.InnerClassTest$A
com.example.reflectdemo.classdetail.InnerClassTest$I
2.3.3. 对象实例化
对象实例化,主要通过 Constructor 实例完成,首先通过相关方法获取 Constructor 对象,然后进行实例化操作。
所涉及的方法如下:
方法 | 含义 |
---|---|
newInstance | 使用默认构造函数实例化对象 |
getConstructors | 获取public构造函数 |
getConstructor(Class<?>... parameterTypes) | 获取特定public构造函数 |
getDeclaredConstructors | 获取所有的构造函数 |
getDeclaredConstructor | 获取特定构造函数 |
实例化涉及的核心类为 Constructor,Constructor 继承自 Executable,拥有 AnnotatedElement、AccessibleObject、Executable 等相关功能,其核心方法如下:
方法 | 含义 |
---|---|
newInstance | 调用构造函数,实例化对象 |
样例如下:
public class TestBean {
private final Integer id;
private final String name;
public <T, R>TestBean(Integer id, String name) throws IllegalArgumentException, NotImplementedException {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "TestBean{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
public class ConstructorTest {
public static void main(String... args) throws Exception{
for (Constructor constructor : TestBean.class.getConstructors()){
TestBean bean = (TestBean) constructor.newInstance(1, "Test");
System.out.println("newInstance:" + bean);
}
}
}
输出结果为:
newInstance:TestBean{id=1, name='Test'}
2.3.4. 属性信息
对象属性是类型中最主要的信息之一,主要通过 Field 表示,首先通过相关方法获取 Field 实例,然后进行属性值操作。
所涉及的方法如下:
方法 | 含义 |
---|---|
getFields | 获取public字段 |
getField(String name) | 获取特定public字段 |
getDeclaredFields | 获取所有的的属性 |
getDeclaredField | 获取特定字段 |
Field 继承自 AccessibleObject 实现 Member 接口,拥有 AccessibleObject、AnnotatedElement、Member 相关功能,其核心方法如下:
方法 | 含义 |
---|---|
isEnumConstant | 是否枚举常量 |
getType | 获取类型 |
get | 获取属性值 |
getBoolean | 获取boolean值 |
getByte | 获取byte值 |
getChar | 获取chat值 |
getShort | 获取short值 |
getInt | 获取int值 |
getLong | 获取long值 |
getFloat | 获取float值 |
getDouble | 获取double值 |
set | 设置属性值 |
setBoolean | 设置boolean值 |
setByte | 设置byte值 |
setChar | 设置char值 |
setShort | 设置short值 |
setInt | 设置int值 |
setLong | 设置long值 |
setFloat | 设置float值 |
setDouble | 设置double值 |
实例如下:
public enum EnumTest {
A
}
public class FieldBean {
private EnumTest aEnum;
private String aString;
private boolean aBoolean;
private byte aByte;
private char aChar;
private short aShort;
private int anInt;
private long aLong;
private float aFloat;
private double aDouble;
}
public class FieldTest {
public static void main(String... args) throws NoSuchFieldException, IllegalAccessException {
FieldBean fieldBean = new FieldBean();
Field aEnum = getByName("aEnum");
Field aString = getByName("aString");
Field aBoolean = getByName("aBoolean");
Field aByte = getByName("aByte");
Field aChar = getByName("aChar");
Field aShort = getByName("aShort");
Field anInt = getByName("anInt");
Field aLong = getByName("aLong");
Field aFloat = getByName("aFloat");
Field aDouble = getByName("aDouble");
aEnum.set(fieldBean, EnumTest.A);
System.out.println("isEnumConstant: " + aEnum.isEnumConstant());
System.out.println("set and get enum : " + aEnum.get(fieldBean));
aString.set(fieldBean, "Test");
System.out.println("set and get String : " + aString.get(fieldBean));
aBoolean.setBoolean(fieldBean, true);
System.out.println("set and get Boolean : " + aBoolean.getBoolean(fieldBean));
aByte.setByte(fieldBean, (byte) 1);
System.out.println("set and get Byte : " + aByte.getByte(fieldBean));
aChar.setChar(fieldBean, 'a');
System.out.println("set and get Char : " + aChar.getChar(fieldBean));
aShort.setShort(fieldBean, (short) 1);
System.out.println("set and get Short : " + aShort.getShort(fieldBean));
anInt.setInt(fieldBean, 1);
System.out.println("set and get Int : " + anInt.getInt(fieldBean));
aLong.setLong(fieldBean, 1L);
System.out.println("set and get Long : " + aLong.getLong(fieldBean));
aFloat.setFloat(fieldBean, 1f);
System.out.println("set and get Float : " + aLong.getFloat(fieldBean));
aDouble.setDouble(fieldBean, 1.1);
System.out.println("set and get Double : " + aLong.getDouble(fieldBean));
}
private static Field getByName(String name) throws NoSuchFieldException {
Field field = FieldBean.class.getDeclaredField(name);
field.setAccessible(true);
return field;
}
}
2.3.5. 方法信息
类型中的方法通过 Method 表示,首先通过相关方法获取 Method 实现,然后通过反射执行方法。
所涉及的方法如下:
方法 | 含义 |
---|---|
getMethods | 获取public方法 |
getMethod(String name, Class<?>... parameterTypes) | 获取特定public方法 |
getDeclaredMethods | 获取所有方法 |
getDeclaredMethod | 获取特定方法 |
Method 继承自 Executable,拥有 AnnotatedElement、AccessibleObject、Executable 等相关功能,其核心方法如下:
方法 | 含义 |
---|---|
getReturnType | 获取方法返回类型 |
invoke | 调用方法 |
isBridge | 是否为桥接方法。桥接方法是 JDK 1.5 引入泛型后,为了使Java的泛型方法生成的字节码和 1.5 版本前的字节码相兼容,由编译器自动生成的方法。我们可以通过Method.isBridge()方法来判断一个方法是否是桥接方法。 |
isDefault | 是否为默认方法 |
实例如下:
public interface SayHi {
String get();
default void hi(){
System.out.println("Hi " + get());
}
}
public class MethodBean implements Function<String, String>, SayHi {
private final String name;
public MethodBean(String name) {
this.name = name;
}
@Override
public String get() {
return "Hi " + name;
}
@Override
public String apply(String s) {
return s + name;
}
}
public class MethodTest {
public static void main(String... args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Method strMethod = MethodBean.class.getDeclaredMethod("apply", String.class);
Method objMethod = MethodBean.class.getDeclaredMethod("apply", Object.class);
Method hiMethod = SayHi.class.getDeclaredMethod("hi");
MethodBean methodBean = new MethodBean("张三");
System.out.println("Return Type:");
System.out.println("getMethod(String):" + strMethod.getReturnType());
System.out.println("getMethod(Object):" + objMethod.getReturnType());
System.out.println("hi():" + hiMethod.getReturnType());
System.out.println();
System.out.println("isBridge:");
System.out.println("getMethod(String):" + strMethod.isBridge());
System.out.println("getMethod(Object):" + objMethod.isBridge());
System.out.println("hi():" + hiMethod.isBridge());
System.out.println();
System.out.println("isDefault:");
System.out.println("getMethod(String):" + strMethod.isDefault());
System.out.println("getMethod(Object):" + objMethod.isDefault());
System.out.println("hi():" + hiMethod.isDefault());
System.out.println();
System.out.println("invoke:");
System.out.println("invoke(String):" + strMethod.invoke(methodBean, "Test"));
System.out.println("invoke(Object):" + objMethod.invoke(methodBean, "Test"));
System.out.println("hi():" + hiMethod.invoke(methodBean));
}
}
输出结果:
Return Type:
getMethod(String):class java.lang.String
getMethod(Object):class java.lang.Object
hi():void
isBridge:
getMethod(String):false
getMethod(Object):true
hi():false
isDefault:
getMethod(String):false
getMethod(Object):false
hi():true
invoke:
invoke(String):Test张三
invoke(Object):Test张三
Hi Hi 张三
hi():null
2.3.6. 其他
除上述核心方法外,Class 对象提供了一些使用方法。
所涉及方法如下:
方法 | 含义 |
---|---|
isInstance | 判断某对象是否是该类的实例 |
isAssignableFrom | 判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口。如果是则返回 true;否则返回 false。 |
getClassLoader | 获取加载当前类的ClassLoader |
getResourceAsStream | 根据该ClassLoader加载资源 |
getResource | 根据该ClassLoader加载资源 |
public class Task implements Runnable{
@Override
public void run() {
}
}
public class OtherTest {
public static void main(String...args){
Task task = new Task();
System.out.println("Runnable isInstance Task:" + Runnable.class.isInstance(task));
System.out.println("Task isInstance Task:" + Task.class.isInstance(task));
System.out.println("Task isAssignableFrom Task:" + Task.class.isAssignableFrom(Task.class));
System.out.println("Runnable isAssignableFrom Task :" + Runnable.class.isAssignableFrom(Task.class));
}
}
输出结果:
Runnable isInstance Task:true
Task isInstance Task:true
Task isAssignableFrom Task:true
Runnable isAssignableFrom Task :true
3. 动态代理
代理是基本的设计模式之一,它是我们为了提供额外的或不同的操作,而插入的用来代替 “实际” 对象的对象。这些操作通常与 “实际” 对象通信,因此代理通常充当中间人的角色。
例如,我们已有一个 Handler 接口,和一个实现类 HandlerImpl,现需要对其进行性能统计,使用代理模式,代码如下:
/**
* handler接口
*/
public interface Handler {
/**
* 数据处理
* @param data
*/
void handle(String data);
}
/**
* Handler 实现
*/
public class HandlerImpl implements Handler{
@Override
public void handle(String data) {
try {
TimeUnit.MILLISECONDS.sleep(100);
System.out.println(data);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* Handler代理<br />
* 实现Handler接口,记录耗时情况,并将请求发送给目标对象
*/
public class HandlerProxy implements Handler{
private final Handler handler;
public HandlerProxy(Handler handler) {
this.handler = handler;
}
@Override
public void handle(String data) {
long start = System.currentTimeMillis();
this.handler.handle(data);
long end = System.currentTimeMillis();
System.out.println("cost " + (end - start) + " ms");
}
}
public static void main(String... args){
Handler handler = new HandlerImpl();
Handler proxy = new HandlerProxy(handler);
proxy.handle("Test");
}
采用代理模式,比较优雅的解决了该问题,但如果 Handler 接口存在多个方法,并且需要对所有方法进行性能监控,那 HandlerProxy 的复杂性将会提高。
Java动态代理比代理更进一步,因为它可以动态的创建代理并动态的处理对所代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器上。
3.1. InvocationHandler
InvocationHandler 是由动态代理处理器实现的接口,对代理对象的方法调用,会路由到该处理器上进行统一处理。
其只有一个核心方法:
/**
* proxy : 代理对象
* method : 调用方法
* args : 调用方法参数
**/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
3.2. Proxy
Proxy 用于生成代理对象。
其核心方法为:
/**
* 获取代理类<br />
* loader : 类加载器
* interfaces: 类实现的接口
*
*/
Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces);
/*
* 生成代理对象<br />
* loader : 类加载器
* interfaces : 类实现的接口
* h : 动态代理回调
*/
Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h);
/*
* 判断是否为代理类<br />
*
* cl : 待判断类
*/
public static boolean isProxyClass(Class<?> cl);
/*
* 获取代理对象的InvocationHandler <br />
*
* proxy : 代理对象
*/
InvocationHandler getInvocationHandler(Object proxy);
3.3. demo
对于之前的性能监控,使用Java动态代理怎么实现?
/**
* 定义代理方法回调处理器
*/
public class CostInvocationHandler implements InvocationHandler {
// 目标对象
private final Object target;
public CostInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("call method " + method + " ,args " + args);
long start = System.currentTimeMillis();
try {
// 将请求转发给目标对象
return method.invoke(this.target, args);
}finally {
long end = System.currentTimeMillis();
System.out.println("cost " + (end - start) + "ms");
}
}
}
public static void main(String... args){
Handler handler = new HandlerImpl();
CostInvocationHandler invocationHandler = new CostInvocationHandler(handler);
Class cls = Proxy.getProxyClass(DHandlerMain.class.getClassLoader(), Handler.class);
Handler proxy = (Handler) Proxy.newProxyInstance(DHandlerMain.class.getClassLoader(),
new Class[]{Handler.class},
invocationHandler);
System.out.println("invoke method");
proxy.handle("Test");
System.out.println("isProxyClass: " + Proxy.isProxyClass(cls));
System.out.println("getInvocationHandler: " + (invocationHandler == Proxy.getInvocationHandler(proxy)));
}
4. 基于 SPI 的 Plugin
SPI 全称为 (Service Provider Interface) ,是 JDK 内置的一种服务提供发现机制。 目前有不少框架用它来做服务的扩展发现,它是一种动态替换发现的机制。
具体用法是在 JAR 包的 "META-INF/services/" 目录下建立一个文件,文件名是接口的全限定名,文件的内容可以有多行,每行都是该接口对应的具体实现类的全限定名。然后使用 ServiceLoader.load(Interface.class) 对插件进行加载。
假定,现有个场景,需要对消息进行处理,但消息处理器的实现需要放开,及可以动态的对处理器进行加载,当有新消息到达时,依次调用处理器对消息进行处理,让我们结合SPI和反射构造一个简单的 Plugin 系统。
首先我们需要一个插件接口和若干个实现类:
/**
* 插件接口
*/
public interface Handler {
void handle(String msg);
}
/**
* 实现1
*/
public class Handler1 implements Handler{
@Override
public void handle(String msg) {
System.out.println("Handler1:" + msg);
}
}
/**
* 实现2
*/
public class Handler2 implements Handler{
@Override
public void handle(String msg) {
System.out.println("Handler2:" + msg);
}
}
然后,我们添加 SPI 配置,及在 META-INF/services/com.example.reflectdemo.plugin.Handler 添加配置信息:
com.example.reflectdemo.plugin.Handler1
com.example.reflectdemo.plugin.Handler2
其次,我们实现 DispatcherInvocationHandler 类继承自 InvocationHandler 接口,将方法调用分发给目标对象。
/**
* 分发处理器<br />
* 将请求挨个转发给目标对象
*/
public class DispatcherInvocationHandler implements InvocationHandler {
// 目标对象集合
private final List<Object> targets;
public DispatcherInvocationHandler(List<Object> targets) {
this.targets = targets;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
for (Object target : targets){
// 将请求转发给目标对象
method.invoke(target, args);
}
return null;
}
}
实现主流程,通过 SPI 加装插件,将插件作为转发对象实例化 DispatcherInvocationHandler,在通过Proxy构建动态代理对象,最后调用 handle 方法进行业务处理。
public static void main(String... args){
// 使用SPI加载插件
ServiceLoader<Handler> serviceLoader = ServiceLoader.load(Handler.class);
List<Object> handlers = new ArrayList<>();
Iterator<Handler> handlerIterator = serviceLoader.iterator();
while (handlerIterator.hasNext()){
Handler handler = handlerIterator.next();
handlers.add(handler);
}
// 将加载的插件组装成InvocationHandler,以进行分发处理
DispatcherInvocationHandler invocationHandler = new DispatcherInvocationHandler(handlers);
// 生成代理对象
Handler proxy = (Handler) Proxy.newProxyInstance(HandlerMain.class.getClassLoader(), new Class[]{Handler.class}, invocationHandler);
// 调用handle方法
proxy.handle("Test");
}
运行结果如下:
Handler1:Test
Handler2:Test
5. 总结
Java 类型系统、反射、动态代理,作为 Java 的高级应用,大量用于各大框架中。对其的掌握有助于加深对框架的理解。