栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java反射--实战篇

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java反射--实战篇

"talk is cheap, show me the code"。接下来将从实际应用的角度,说明如何使用反射。关于反射相关的概念,可以参考Java反射概述博文。 使用反射的基本步骤如下:
(1) 获取类型的Class对象;
(2) 基于Class对象获取类型元数据:Constructor类型、Field类型、Method类型;
(3) 基于Constructor类型、Field类型、Method类型访问成员。

获取Class对象

Class类是由class关键字修饰的一个特殊的类。Class实例可以看成是Object及其子类的元数据引用。一个Java类均对应一个Class实例。该Class实例引用可以从getClass方法获取。使用Class对象可以获取对应类型的元数据(metadata)信息,如构造器、字段、方法等。
获取Class对象的方法有很多种。这里介绍下常用的几种方法:
(1) 使用Class类的forName静态方法;
(2) 使用Ojbect根类的class静态字段;
(3) 使用类型实例的getClass()方法;
(4) 使用ClassLoader实例的loadClass方法。
在介绍获取Class对象的方法前,先预定义测试类,方便后面统一使用。

// package io.github.courage007.reflect;

@Getter
@Setter
public class Apple {
    private String color;
    
    private String size;

    public Apple() {
        this.color = "red";
        this.size = "medium";
    }

    public Apple(String color, String size) {
        this.color = color;
        this.size = size;
    }

    public void changeByTime() {
        this.color = "gray";
        this.size = "small";
    }

    private void changeColor() {
        this.color = "gray";
    }
}
使用Class类的forName静态方法

Class类提供forName静态方法用于获取Class实例。相关源码片段如下:

public final class Class implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {
    // ...
    @CallerSensitive
    public static Class forName(String className)
                throws ClassNotFoundException {
        Class caller = Reflection.getCallerClass();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }

    @CallerSensitive
    public static Class forName(String name, boolean initialize,
                                   ClassLoader loader)
        throws ClassNotFoundException
    {
        Class caller = null;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            // Reflective call to get caller class is only needed if a security manager
            // is present.  Avoid the overhead of making this call otherwise.
            caller = Reflection.getCallerClass();
            if (loader == null) {
                ClassLoader ccl = ClassLoader.getClassLoader(caller);
                if (ccl != null) {
                    sm.checkPermission(
                        SecurityConstants.GET_CLASSLOADER_PERMISSION);
                }
            }
        }
        return forName0(name, initialize, loader, caller);
    }

    // since java 9
    @CallerSensitive
    public static Class forName(Module module, String name) {
        Objects.requireNonNull(module);
        Objects.requireNonNull(name);

        ClassLoader cl;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            Class caller = Reflection.getCallerClass();
            if (caller != null && caller.getModule() != module) {
                // if caller is null, Class.forName is the last java frame on the stack.
                // java.base has all permissions
                sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
            }
            PrivilegedAction pa = module::getClassLoader;
            cl = AccessController.doPrivileged(pa);
        } else {
            cl = module.getClassLoader();
        }

        if (cl != null) {
            return cl.loadClass(module, name);
        } else {
            return BootLoader.loadClass(module, name);
        }
    }

    
    private static native Class forName0(String name, boolean initialize,
                                            ClassLoader loader,
                                            Class caller)
        throws ClassNotFoundException;
}

可以看到,forName静态方法有三个重载方法。其使用示例如下:

public void getClassRefByForName() {
    try {
        Class clazz1 = Class.forName("io.github.courage007.reflect.Apple");
        // Returns the class of the caller of the method calling this method
        // 获取调用当前方法的方法的调用者
        Class caller = Reflection.getCallerClass();
        Class clazz = Class.forName("io.github.courage007.reflect.Apple", false, caller.getClassLoader());
    } catch (ClassNotFoundException ex) {
        throw new RuntimeException("get class failed");
    }
    // java 9新增基于Module获取Class实例,这里不再讨论,有兴趣的同学可以自行学习
}

基于类的全路径名获取 Class 对象的方式,其优势是不需要事先引入依赖,适用于运行时调用的场景。需要说明的是,由于代码里硬编码了类的全路径名,不能很好的适应类的全路径名变化场景,生产上可以这部分数据放置在文件或数据库等存储介质中。

使用Ojbect根类的静态class字段

Ojbect根类提供静态class字段,可以直接获取Class实例。示例代码如下:

public void getClassRefByStaticField() {
    Class clazz = Apple.class;
}

使用这种方法,需要引入类对应的包。该方式主要应用于调用方。

使用类型实例的getClass()方法

Ojbect根类提供getClass方法,用于获取实例的Class实例。关键代码如下:

public class Object {
    // ...

    
    @HotSpotIntrinsicCandidate
    public final native Class getClass();
}

所以,可以使用getClass方法获取Class实例。示例代码如下:

public void getClassRefByGetClassMethod() {
    Apple apple = new Apple();
    Class clazz = apple.getClass();
}

需要说明的是,使用这种方法,需要引入实例所属类的包。这种方式的调用多出现于调用方。对于自身来说,因为已经有了类的实例,无需再通过Class实例去构造实例并访问字段或方法。

使用ClassLoader实例的loadClass方法

ClassLoader实例提供loadClass方法来实现指定类的全路径名来获取Class实例。示例代码如下:

public void getClassRefByClassLoader() {
    try {
        ClassLoader classLoader = this.getClass().getClassLoader();
        Class clazz = classLoader.loadClass("io.github.courage007.reflect.Apple");
        System.out.println(clazz.toString());
    } catch (ClassNotFoundException ex) {
        throw new RuntimeException("get class failed");
    }
}

基于ClassLoader获取Class的方式与基于Class类的forName静态方法获取Class的方式一样,都是基于类的全路径名获取 Class 对象。其优缺点不再赘述。

基于反射构造实例

获取Class对象后,就可基于Class对象实现构造方法、字段、方法的调用。这里介绍如何基于Class对象调用构造方法以实现实例创建。
按照访问类型、参数个数,可将构造函数分为如下四类:公有无参构造函数、公有带参构造函数、私有无参构造函数、私有带参构造函数。

公有无参构造函数

Class类提供newInstance方法,用于创建类的实例。但是,该方法会返回空实例,在Java 9之后已经弃用,推荐先基于Class获取Constructor实例,然后基于Constructor的newInstance方法去创建类型实例。需要说明的是Constructor的newInstance方法也可创建公有带参构造函数,示例代码如下:

public void getInstanceWithoutParam() {
    try {
        Class clazz = Class.forName("io.github.courage007.reflect.Apple");
        Apple apple = (Apple) clazz.getConstructor(null).newInstance(null);
        apple.getColor();
    } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException ex) {
        throw new RuntimeException("get class constructor failed");
    }
}

已弃用的Class类的newInstance方法代码片段如下:

public final class Class implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {
    // ...
    
    @CallerSensitive
    @Deprecated(since="9")
    public T newInstance() throws InstantiationException, IllegalAccessException {
        // ...
        try {
            // 创建无参
            return tmpConstructor.newInstance((Object[])null);
        } catch (InvocationTargetException e) {
            Unsafe.getUnsafe().throwException(e.getTargetException());
            // Not reached
            return null;
        }
    }
}
公有带参构造函数

Constructor的newInstance方法支持创建公有带参构造函数,示例代码如下:

public void getInstanceWithParam() {
    try {
        Class clazz = Class.forName("io.github.courage007.reflect.Apple");
        Apple apple = (Apple) clazz.getConstructor(String.class, String.class).newInstance("green", "small");
        apple.getColor();
    } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException ex) {
        throw new RuntimeException("get class constructor failed");
    }
}
私有构造函数

Class实例的getConstructor方法只能获取包含父类的某个public的构造方法,对于某个非public访问权限的构造方法,则需使用
getDeclaredConstructor方法。需要说明的是,在使用非public的Constructor时,必须先执行setAccessible(true)方法,设置允许访问。
经过对公有构造函数调用可以发现,基于Constructor调用公有无参构造函数和公有待参构造函数,其差异性仅体现在传参。私有无参构造函数和私有带参构造函数有类似处理机制。这里仅以私有带参构造函数为例,私有无参构造函数处理类似。示例代码如下:

public void getInstanceWithPrivateConstructor() {
    try {
        Class clazz = Class.forName("io.github.courage007.reflect.Apple");
        Constructor currentConstructor = clazz.getDeclaredConstructor(String.class);
        currentConstructor.setAccessible(true);
        Apple apple = (Apple)currentConstructor.newInstance("yellow");
        apple.getColor();
    } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException ex) {
        throw new RuntimeException("get class constructor failed");
    }
}
基于反射获取字段

与基于反射构造实例先通过Class对象获取Constructor对象,然后再基于Constructor对象调用构造函数类似,基于反射获取字段先通过Class对象获取Field对象,然后再基于Field对象访问字段。Field提供一系列方法,用于操作字段,常用的方法有:

getName():返回字段名称  
getType():返回字段类型,也是一个Class实例,如String.class  
getModifiers():返回字段的修饰符,它是一个int,不同的bit表示不同的访问权限  
setAccessible():设置变量为public  
set():设置字段值  
get():获取字段值  

这里以访问私有字段为例,介绍下如何基于反射访问字段。示例代码如下:

public void getFieldWithPrivatePermission() {
    try {
        Class clazz = Class.forName("io.github.courage007.reflect.Apple");
        // 获取private字段"grade":
        Field colorField = clazz.getDeclaredField("color");
        colorField.setAccessible(true);
        // 获取字段名
        colorField.getName();
        // 获取字段类型
        colorField.getType();
        // 获取字段的访问权限
        colorField.getModifiers();
        Apple apple = (Apple) clazz.getConstructor(String.class, String.class).newInstance("green", "small");
        apple.getColor();
        // 设置字段
        colorField.set(apple, "red");
        // 获取字段值
        colorField.get(apple);
    } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException |  InstantiationException | InvocationTargetException | NoSuchMethodException ex) {
        throw new RuntimeException("get class field failed");
    }
}
基于反射获取方法

与基于反射构造实例和基于反射获取字段类似,基于反射获取方法先通过Class对象获取Method对象,然后再基于Method对象访问方法。Method提供一系列方法,用于访问方法,常用的方法有:

getName():返回方法名称  
getReturnType():返回方法返回值类型,也是一个Class实例,例如:String.class  
getParameterTypes():返回方法的参数类型,是一个Class数组,例如:{String.class, int.class}  
getModifiers():返回方法的修饰符,它是一个int,不同的bit表示不同的含义  
setAccessible():设置private函数为public属性  
invoke(object,new Object[]{}) 调用执行方法  

这里以访问私有方法为例,介绍下如何基于反射调用方法。示例代码如下:

public void invokeMethodWithPrivatePermission() {
    try {
        Class clazz = Class.forName("io.github.courage007.reflect.Apple");
        Method changeColorMethod = clazz.getDeclaredMethod("changeColor");
        changeColorMethod.setAccessible(true);
        // 获取方法名
        changeColorMethod.getName();
        // 获取参数类型 Class[] 数组
        changeColorMethod.getParameterTypes();
        // 返回方法返回值类型 Class 实例
        changeColorMethod.getReturnType();
        // 获取方法的访问权限
        changeColorMethod.getModifiers();
        Apple apple = (Apple) clazz.getConstructor(String.class, String.class).newInstance("green", "small");
        // 调用方法
        changeColorMethod.invoke(apple, null);
    } catch (ClassNotFoundException | IllegalAccessException |  InstantiationException | InvocationTargetException | NoSuchMethodException ex) {
        throw new RuntimeException("get class method failed");
    }
}
基于反射获取父类成员

通过Class实例的getXxx类型方法可以获取包含父类的某个public的构造方法、字段、方法。对于非public构造方法、字段、方法,可以先通过获取getSuperclass获取父类对应的Class对象,然后通过Class对象访问非public构造方法、字段、方法。这里不再给出示例,有兴趣的同学,可以自行学习。

总结

Java提供Class类型、Constructor类型、Field类型、Method类型,帮助实现运行时访问类型实例上的成员。在获取成员时,根据成员的访问权限、声明位置,需要选用不同的方法,具体可以分为两类:
getXxx 获取包含父类的某个public的构造方法、字段、方法。
getDeclaredXxx 获取当前类的包含private访问权限的所有构造方法、字段、方法。

参考

https://www.anquanke.com/post/id/245458 Java安全之反射

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/755861.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号