JAVA中的反射

我们这个世界的一个问题是,蠢人信誓旦旦,智人满腹狐疑

一、什么是反射?

在运行状态中,对于任意一个类,都能够获取到这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性(包括私有的方法和属性),这种动态获取的信息以及动态调用对象的方法的功能就称为java语言的反射机制。

通过JAVA的反射机制,程序猿可以更深入地控制程序的运行过程,如在程序运行时对用户输入的信息进行验证,还可逆向控制程序的执行。

想要使用反射机制,就必须要先获取到该类的字节码文件对象(.class),通过字节码文件对象,就能够通过该类中的方法获取到我们想要的所有信息(方法,属性,类名,父类名,实现的所有接口等等),每一个类对应着一个字节码文件也就对应着一个Class类型的对象,也就是字节码文件对象。

获取字节码文件对象有三种方式

        //三种方式
        Class clazz=Person.class;

        Person person=new Person();
        Class clazz=person.getClass();

        clazz=Class.forName("Person");
        System.out.println(clazz);

你可以通过字节码对象得到该类的属性,方法等等

我们先定义一个Person类来方便以后我们做测试

public class Person {
    public String name;
    protected int age;
    String address;
    private char sex;
    public Person(){

    }
    protected Person(String name,int age){
        this.name=name;
        this.age=age;
    }
    Person(String name,int age,String address){
        this.name=name;
        this.age=age;
        this.address=address;
    }
    private Person(String name,char sex){
        this.name=name;
        this.sex=sex;
    }
    public void show(){
        System.out.println("show");
    }
    protected void test(String msg){
        System.out.println(msg);
    }
    int getSum(int a,int b){
        return a+b;
    }
    private String show2(String msg){
        return msg;
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", address='" + address + '\'' +
                ", sex=" + sex +
                '}';
    }
}

二、获取

 private static void showConstructor(Constructor[] constructors) {
        for(Constructor constructor:constructors){
            System.out.println(constructor);
        }

        showLine();
    }

private static void showMethod(Method[] methods) {
        for (Method method:methods){
            System.out.println(method);
        }
        showLine();
    }

获取方法后我们需要知道都获取了哪些,我在这里写了一个打印方法名的方法

 

1.获取所有的公共的构造方法

Constructor[] constructors=clazz.getConstructors();
showConstructor(constructors);

结果: public Person()

因为我在Person类中之定义了一个public构造方法,所以只打印了一个

2.获取所有的构造方法

Constructor[] constructors1=clazz.getDeclaredConstructors();
showConstructor(constructors1);

结果:

private Person(java.lang.String,char)
Person(java.lang.String,int,java.lang.String)
protected Person(java.lang.String,int)

3.获取指定的构造方法对象

Constructor constructor=clazz.getConstructor(null);
Object object=constructor.newInstance(null);
System.out.println(object instanceof Person);

你可以使用getConstructor()方法获取指定参数的构造方法,然后你可以用newInstance创建一块空间并在其中执行该构造方法

我在最后打印输出了之后后的对象的类型判断,判断结果是true,说明执行该构造方法后确实生成了一个Person类

你还可以这样写

object=clazz.newInstance();

不过这种写法只适用于空构造方法,上面那种方法确实万用的

constructor=clazz.getDeclaredConstructor(String.class,char.class);
constructor.setAccessible(true);
object=constructor.newInstance("王五",'男');
System.out.println(object);

结果:

Person{name='王五', age=0, address='null', sex=男}

4.获取成员方法对象

获取本类以及父类中的成员方法对象

Method[] methods=clazz.getMethods();
showMethod(methods);

结果:

public java.lang.String Person.toString()
public void Person.show()
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()

获取子类或者接口的所有成员方法对象

methods=clazz.getDeclaredMethods();
showMethod(methods);

结果:

private java.lang.String Person.show2(java.lang.String)
int Person.getSum(int,int)
public java.lang.String Person.toString()
protected void Person.test(java.lang.String)
public void Person.show()

获取指定的成员方法对象

Method method=clazz.getDeclaredMethod("show2",String.class);
method.setAccessible(true);
object=method.invoke(clazz.newInstance(),"我是结果");
System.out.println(object);

结果:我是结果

这种调用方式很重要,叫做暴力反射,使用getDeclaredMethod()方法,传入方法名和参数得到方法对象,使用setAccessible(true)是方法对象可以被强制调用,不管它是public还是private,最后使用invoke()方法来执行方法,传入执行空间和参数

5.获取字段

返回该类或者接口所有的公共字段

Field[] fields=clazz.getFields();
showField(fields);

结果:

public java.lang.String Person.name

返回该类或者接口中所有的成员变量

fields=clazz.getDeclaredFields();
showField(fields);

结果:

public java.lang.String Person.name
protected int Person.age
java.lang.String Person.address
private char Person.sex

获取指定的字段

object=clazz.newInstance();
Field field=clazz.getDeclaredField("sex");
field.setAccessible(true);
field.set(object,'男');
System.out.println(object);

结果:

Person{name='null', age=0, address='null', sex=男}

三、例子

我们除了用反射调用我们平常无法调用的方法,修改平常无法修改的属性之外还能做什么呢?

我们可以用它来加密我们的类文件,这样可以隐藏内部实现还可以防止他人用反射调用

我们把写好的类编译出class文件来,然后使用字符流把每个字符异或一个数字,这样就实现了简单的加密,随后你想用该类的话可以再次使用字符流将每个字符异或一个同样的数字便实现了解密

举个简单的例子

int a=10;
System.out.println(a=a^89);
System.out.println(a=a^89);
/**
     * 加密一个字节码文件
     * @param classPath
     * @throws IOException
     */
    public static byte[] encryptionClass(String classPath) throws IOException {
        BufferedInputStream bufferedInputStream=new BufferedInputStream(new FileInputStream(classPath));
        int len;//记录每次读取的长度
        byte[] bytes=new byte[1024];
        //循环读写
        //先将整个字节码文件读取到内容中,加密后输出
        ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();//将字节写到字节数组中
        while ((len=bufferedInputStream.read(bytes))!=-1){
            byteArrayOutputStream.write(bytes,0,len);
            byteArrayOutputStream.flush();
        }
        byte[] byteArray=byteArrayOutputStream.toByteArray();
        //将所有的字节都异或一个值,就实现了加密或者解密
        for (int i=0;i<byteArray.length;i++){
            byte value=byteArray[i];
            byteArray[i]=(byte)(value^23);
        }
        bufferedInputStream.close();
        byteArrayOutputStream.close();
        return byteArray;

    }

    public static void test() throws IOException {
        String classPath="C:\\Users\\liuyue\\IdeaProjects\\Reflect\\out\\production\\Reflect\\Person.class";
        byte[] bytes=ClassUtils.encryptionClass(classPath);
        ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(bytes);
        int len;
        byte[] bytes1=new byte[1024];
        BufferedOutputStream bufferedOutputStream=new BufferedOutputStream(new FileOutputStream(classPath));
        while ((len=byteArrayInputStream.read(bytes1))!=-1){
            bufferedOutputStream.write(bytes1,0,len);
            bufferedOutputStream.flush();
        }
    }

通过方式的加密之后你便无法使用

Person person=new Person();
System.out.println(person);

你需要自定义的类加载器才能加载

import java.io.*;

public class MyClassLoader extends ClassLoader{
    public static void main(String[] args) throws IOException {

    }
//得到Class对象
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] bytes=ClassUtils.encryptionClass(name);
            //将一个byte数组转换成Class对象
            /**
             * name  所需要的类的二进制名称,如果不知道此名称,则该参数为null
             * b  组成类数据的字节
             * off   类数据的b中的起始偏移量
             * len   类数据的长度
             */
            return defineClass(null,bytes,0,bytes.length);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return super.findClass(name);
    }
}

你需要定义一个类然后继承ClassLoader类,然后复写findClass方法,在其中实现class文件的解密

 MyClassLoader myClassLoader=new MyClassLoader();
        Class clazz=myClassLoader.loadClass("C:\\Users\\liuyue\\IdeaProjects\\Reflect\\out\\production\\Reflect\\Person.class");
        System.out.println(clazz.newInstance());

随后你才能创建对象并调用方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

幽蓝丶流月

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值