资讯专栏INFORMATION COLUMN

Java高级程序员必备:反射、动态代理

church / 3385人阅读

摘要:相比硬编码,反射要复杂的多,但其给我们带来了更大的灵活性。实际上构造函数也是类的静态方法,因此使用关键字创建类的新对象也会被当做对类的静态引用,从而触发类加载器对类的加载。基础基础主要是为反射提供通用特性的接口或基类。

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 animals = createAnimals();
    for (Animal animal : animals){
        System.out.println(animal.getType());
    }

}

/**
 * RTTI假定我们在编译时已经知道了所有的类型
 * @return
 */
private static List createAnimals() {
    List 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);当我们从List中取出元素时,这时容器(容器内部所有的元素都被当做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 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 createAnimals() {
    List 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 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 getAnnotation(Class annotationClass) 返回程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null
Annotation[] getAnnotations() 返回该程序元素上存在的所有注解
boolean is AnnotationPresent(Class 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 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 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 implements Callable {

    @Override
    public T call() throws Exception {
        return null;
    }
}
public final class BaseClassInfo extends Base implements Runnable, Serializable {

    @Override
    public void run() {

    }


    public static void main(String... args){
        Class 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> 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 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, 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代理
* 实现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 用于生成代理对象。

其核心方法为:

/**
* 获取代理类
* loader : 类加载器 * interfaces: 类实现的接口 * */ Class getProxyClass(ClassLoader loader, Class... interfaces); /* * 生成代理对象
* loader : 类加载器 * interfaces : 类实现的接口 * h : 动态代理回调 */ Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h); /* * 判断是否为代理类
* * cl : 待判断类 */ public static boolean isProxyClass(Class cl); /* * 获取代理对象的InvocationHandler
* * 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接口,将方法调用分发给目标对象。

/**
 * 分发处理器
* 将请求挨个转发给目标对象 */ public class DispatcherInvocationHandler implements InvocationHandler { // 目标对象集合 private final List targets; public DispatcherInvocationHandler(List 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 serviceLoader = ServiceLoader.load(Handler.class);
        List handlers = new ArrayList<>();
        Iterator 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的高级应用,大量用于各大框架中。对其的掌握有助于加深对框架的理解。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/76818.html

相关文章

  • MyBatis初步

    摘要:本章主要介绍的是的基础应用和源码涉及的相关等,主要包含的内容有的简介反射动态代理包含代理和代理使用和代码生成器等。组件生命周期,如图测试代码生成器代码生成器,又称逆向工程。 本章主要介绍的是MyBatis的基础应用和源码涉及的相关等,主要包含的内容有MyBatis的简介、反射、动态代理(包含JDK代理和cglib代理)、MyBatis使用和代码生成器等。 1.1 MyBatis简介 M...

    MASAILA 评论0 收藏0
  • Java 高级面试知识点汇总!

    摘要:适配器模式将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。 1、常用设计模式 单例模式:懒汉式、饿汉式、双重校验锁、静态加载,内部类加载、枚举类加载。保证一个类仅有一个实例,并提供一个访问它的全局访问点。 代理模式:动态代理和静态代理,什么时候使用...

    since1986 评论0 收藏0
  • Java动态编程初探

    摘要:动态编程使用场景通过配置生成代码,减少重复编码,降低维护成本。动态生成字节码操作字节码的工具有,其中有两个比较流行的,一个是,一个是。 作者简介 传恒,一个喜欢摄影和旅游的软件工程师,先后从事饿了么物流蜂鸟自配送和蜂鸟众包的开发,现在转战 Java,目前负责物流策略组分流相关业务的开发。 什么是动态编程 动态编程是相对于静态编程而言的,平时我们讨论比较多的静态编程语言例如Java, 与动态...

    赵连江 评论0 收藏0

发表评论

0条评论

church

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<