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而做的准备工作主要包括三个步骤:

  1. 加载,这个是由类加载器执行。该步骤将查找字节码文件,并根据字节码创建一个Class对象。
  2. 链接,在链接阶段将验证类中的字节码,为静态域分配存储空间,如果必要的话,将解析这个类创建的对其他类的引用。
  3. 初始化,如果该类有超类,则对其进行初始化,执行静态初始化器和静态初始化块。初始化被延时到对静态方法或非常数静态域进行首次访问时才执行。

2.2. Class 实例获取

Class对象作为Java类型体系的入口,如何获取实例成为第一个要解决的问题。

Class对象的获取主要有以下几种途径:

  1. ClassName.class,获取Class对象最简单最安全的方法,其在编译时会受到编译检测,但上例中已经证实,该方法不会触发初始化逻辑。
  2. Class.forName,这是反射机制最常用的方法之一,可以在不知具体类型时,通过一个字符串加载所对应的Class对象。
  3. 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 ClassAPI 基础

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的地方,其中包括:

  1. Constructor 构造函数
  2. Method 方法
  3. Class 类型
  4. Field 字段
  5. Package 包
  6. Parameter 参数
  7. AnnotatedParameterizedType 泛型
  8. AnnotatedTypeVariable 变量
  9. AnnotatedArrayType 数组类型
  10. 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内部使用。

其子类主要包括:

  1. Class 类型
  2. Field 字段
  3. Method 方法
  4. Constructor 构造函数

2.3.1.3. AccessibleObject

AccessibleObject可访问对象,其对元素的可见性进行统一封装。同时实现AnnotatedElement接口,提供对Annotation元素的访问。

所涉及方法如下:

方法

含义

isAccessible

是否可访问

setAccessible

重新访问性

其中AccessibleObject所涉及的子类主要包括:

  1. Field 字段
  2. Constructor 构造函数
  3. 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

获取异常列表

锁涉及的子类主要有:

  1. Constructor 构造函数
  2. 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. 方法命名规则

整个反射机制存在着通用的命名规则,了解这些规则,可以大大减少理解方法的阻力。

getXXXgetDeclaredXXX, 两者主要区别在于获取元素的可见性不同,一般情况下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的高级应用,大量用于各大框架中。对其的掌握有助于加深对框架的理解。