Java程序语言的后门-反射机制


// 通过反射机制,通知力宏做事情
method.invoke(object, args);
// 通过反射,将h作为参数,实例化代理类,返回代理类实例。
return cons.newInstance(new Object[]{h});
而且在

// 将接口类对象数组clone一份。
final Class<?>[] intfs = interfaces.clone();
也提到一个类对象数组的概念,如果你不知道反射,不知道类对象,那么你在阅读者两篇文章的时候,很可能就会雨里雾里,不知所然。通过这篇文章你就能很轻松的掌握类对象和反射。

反射是Java语言中一个很基础,很简单的知识,不仅仅是在工作中会用到,而且也会经常出现在面试中。如果你认真阅读本文,对你在技术层面来说又是一个提升。

一,前言

  反射是什么呢?其实是java程序语言的一种机制,我理解为是java的后门。在其他文章中给出的定义和解释都比较晦涩难懂,不如先看一下具体的代码,再去理解,这样就容易很多。

  为了更好的理解反射机制,不得不提到类对象的概念,为了不与类和对象的概念搞混,我们就首先看一下类和对象的概念。相信你已经非常熟悉类和对象的概念了,那么我就简单的描述一下类与对象的概念:

    类:一个或一组事物的抽象描述,例如狗是对狗狗这一组事物的抽象描述。

    对象:具体的某一个事物,例如哈士奇等,也可以说是类的一个实例。

  那么类对象是什么呢?

    在java中一切皆对象。既然一切皆对象,当然类也是一种对象,那么类的对象类型是什么呢?是java.lang.Class,你也许在其他的地方见到过。

  那么类对象是从哪里来的,怎么创建的呢?

    我们都知道,想要得到一个类的对象,最基本的方法就是通过new关键字,去实例化一个对象。但类对象是一个特殊的对象,自然不能使用new关键字,翻看Class类的源码就可以证明:

/*
 * 私有化构造方法,只有java 虚拟机才能创建类对象
 * Private constructor. Only the Java Virtual Machine creates Class objects.
 * This constructor is not used and prevents the default constructor being
 * generated.
 */
private Class(ClassLoader loader) {
    // Initialize final field for classLoader.  The initialization value of non-null
    // prevents future JIT optimizations from assuming this final field is null.
    classLoader = loader;
}

  Class类中只有这一个私有的构造方法。其实类对象是java虚拟机(JVM)在加载class文件的时候自动在虚拟机内存中给我们创建的。

    这里就涉及到了另外一个机制:类加载机制。

    简单描述一下类加载机制:就是虚拟机将class文件加载到虚拟机内存中,最终形成可以被虚拟机直接使用的java类型。虚拟机会将class文件中的信息按照所需的存储格式放在方法区中,同时会在内存中(HotSpot是在堆内存中)实例化一个java.lang.Class类对象。

  类加载机制是一个很复杂的过程,不是本篇文章的重点,就不展开来说。至少到这里我们已经知道了,类对象是由虚拟机创建的而且HotSpot虚拟机将类对象存放在堆内存中。

  那么怎么通过类对象来实现反射呢?为了理解方便,来举一个有趣的例子

二,一个有趣的例子

  有一天一个同事养了一只狗狗哈士奇,在作者面前大肆炫耀,说他的狗狗多么萌,多么威武,多么听话......,作者听完心生向往,提出去看一看。但是这个同事高傲的抬起头说了三个字:想的美。

  竟然不给我看!!!作者一气之下,就走了java程序的后门-反射。你不让我看,我偏偏要看。

  哈士奇类的定义:

package com.zcz.reflecttest;

public class HaShiQi implements Dog {
public String color = "黑色";
private String name = "富贵";

//私有化构造
private HaShiQi() {};
@Override
public void eat() {
    // TODO Auto-generated method stub
    System.out.println(name + " 去吃狗粮");
}

@Override
public void run() {
    // TODO Auto-generated method stub
    System.out.println(name + " 去跑着玩儿");
}

private void dance() {
    System.out.println(name + " 来跳一支舞");
}

@Override
public String toString() {
    // TODO Auto-generated method stub
    return "名字:"+name+";颜色:"+color;
}

}

  可以看到同时的哈士奇实现了Dog接口:

package com.zcz.reflecttest;

public interface Dog {
public void eat();
public void run();
}

  从代码中可以看到,我的同事不仅仅没告诉作者他的哈士奇竟然会跳舞,甚至连哈士奇的名字都不想让作者知道,而且连构造器都私有化了。

  那么接下来,走后门开始。在上提到类对象,正好我想通过反射看狗狗,也需要用到类对象。那么接下来第一步就先获取HaShiQi的类对象。

三,类对象的获取

  超级简单:

package com.zcz.reflecttest;

/**

  • 通过反射查看同事狗狗的信息
  • @author zhangchengzi
  • */
    public class LookLook {

    public static void main(String[] args) {
    // TODO Auto-generated method stub
    //获取类对象,除了这份方法外还有另外两种方法
    Class clazz = HaShiQi.class;

    }

}

  从这里开始我们的反射的使用的开始了,先看看HaShiQi类中有哪些属性吧?

四,获取属性

  代码:

public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// TODO Auto-generated method stub
//获取类对象,除了这份方法外还有另外两种方法
Class clazz = HaShiQi.class;

    System.out.println("——————— 获取所有公有的属性  —————————");
    Field[] fields = clazz.getFields();
    for(Field field : fields) {
        field.setAccessible(true);
        System.out.println(field);
    }    
}

  打印结果:

——————— 获取所有公有的属性 —————————
public java.lang.String com.zcz.reflecttest.HaShiQi.color
  可以看到只有一个color属性,使用getFields方法是获取不到私有属性了,想要获取私有属性就要使用下方的方法:

public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// TODO Auto-generated method stub
//获取类对象,除了这份方法外还有另外两种方法
Class clazz = HaShiQi.class;

    System.out.println("——————— 获取所有公有的属性  —————————");
    Field[] fields = clazz.getFields();
    for(Field field : fields) {
        field.setAccessible(true);
        System.out.println(field);
    }

    System.out.println("——————— 获取所有的属性  —————————");
    fields = clazz.getDeclaredFields();
    for(Field field : fields) {
        field.setAccessible(true);
        System.out.println(field);
    }
}

  打印结果:

——————— 获取所有公有的属性 —————————
public java.lang.String com.zcz.reflecttest.HaShiQi.color
——————— 获取所有的属性 —————————
public java.lang.String com.zcz.reflecttest.HaShiQi.color
private java.lang.String com.zcz.reflecttest.HaShiQi.name
  可以看到,HaShiQi类中私有(private修饰的)的name属性打印出来了。

五,获取方法

  代码:

public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// TODO Auto-generated method stub
//获取类对象,除了这份方法外还有另外两种方法
Class clazz = HaShiQi.class;

    System.out.println("——————— 获取所有公有的属性  —————————");
    Field[] fields = clazz.getFields();
    for(Field field : fields) {
        field.setAccessible(true);
        System.out.println(field);
    }

    System.out.println("——————— 获取所有的属性  —————————");
    fields = clazz.getDeclaredFields();
    for(Field field : fields) {
        field.setAccessible(true);
        System.out.println(field);
    }
    System.out.println("——————— 获取所有公有的方法  —————————");
    Method[] methods = clazz.getMethods();
    for(Method method : methods) {
        System.out.println(method);
    }
}

  打印结果:

——————— 获取所有公有的属性 —————————
public java.lang.String com.zcz.reflecttest.HaShiQi.color
——————— 获取所有的属性 —————————
public java.lang.String com.zcz.reflecttest.HaShiQi.color
private java.lang.String com.zcz.reflecttest.HaShiQi.name
——————— 获取所有公有的方法 —————————
public void com.zcz.reflecttest.HaShiQi.run()
//重写的toString 方法
public java.lang.String com.zcz.reflecttest.HaShiQi.toString()
public void com.zcz.reflecttest.HaShiQi.eat()
//Object类中的方法
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()

  从结果中可以发现,getMethod方法也是只能获取到公有的方法,而且连Object中的方法也获取到了。跟获取属性也是一样的,也有方法可以获取到HaShiQi类中所有的方法:

public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// TODO Auto-generated method stub
//获取类对象,除了这份方法外还有另外两种方法
Class clazz = HaShiQi.class;

    System.out.println("——————— 获取所有公有的属性  —————————");
    Field[] fields = clazz.getFields();
    for(Field field : fields) {
        field.setAccessible(true);
        System.out.println(field);
    }

    System.out.println("——————— 获取所有的属性  —————————");
    fields = clazz.getDeclaredFields();
    for(Field field : fields) {
        field.setAccessible(true);
        System.out.println(field);
    }
    System.out.println("——————— 获取所有公有的方法  —————————");
    Method[] methods = clazz.getMethods();
    for(Method method : methods) {
        System.out.println(method);
    }
    System.out.println("——————— 获取所有类中声明的方法  —————————");
    methods = clazz.getDeclaredMethods();
    for(Method method : methods) {
        System.out.println(method);
    }
}

  打印结果:

——————— 获取所有公有的属性 —————————
public java.lang.String com.zcz.reflecttest.HaShiQi.color
——————— 获取所有的属性 —————————
public java.lang.String com.zcz.reflecttest.HaShiQi.color
private java.lang.String com.zcz.reflecttest.HaShiQi.name
——————— 获取所有公有的方法 —————————
public void com.zcz.reflecttest.HaShiQi.run()
public java.lang.String com.zcz.reflecttest.HaShiQi.toString()
public void com.zcz.reflecttest.HaShiQi.eat()
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()
——————— 获取所有类中声明的方法 —————————
public void com.zcz.reflecttest.HaShiQi.run()
public java.lang.String com.zcz.reflecttest.HaShiQi.toString()
private void com.zcz.reflecttest.HaShiQi.dance()
public void com.zcz.reflecttest.HaShiQi.eat()

  这样就可以同时获取到HaShiQi类中的私有方法(private修饰的)dance了。

六,获取构造器

  代码:

public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// TODO Auto-generated method stub
//获取类对象,除了这份方法外还有另外两种方法
Class clazz = HaShiQi.class;

    System.out.println("——————— 获取所有公有的属性  —————————");
    Field[] fields = clazz.getFields();
    for(Field field : fields) {
        field.setAccessible(true);
        System.out.println(field);
    }

    System.out.println("——————— 获取所有的属性  —————————");
    fields = clazz.getDeclaredFields();
    for(Field field : fields) {
        field.setAccessible(true);
        System.out.println(field);
    }
    System.out.println("——————— 获取所有公有的方法  —————————");
    Method[] methods = clazz.getMethods();
    for(Method method : methods) {
        System.out.println(method);
    }
    System.out.println("——————— 获取所有类中声明的方法  —————————");
    methods = clazz.getDeclaredMethods();
    for(Method method : methods) {
        System.out.println(method);
    }
    System.out.println("——————— 获取所有公有的构造器  —————————");
    Constructor[] constructors = clazz.getConstructors();
    for(Constructor constructor : constructors) {
        System.out.println(constructor);
    }

}

  打印结果:

——————— 获取所有公有的属性 —————————
public java.lang.String com.zcz.reflecttest.HaShiQi.color
——————— 获取所有的属性 —————————
public java.lang.String com.zcz.reflecttest.HaShiQi.color
private java.lang.String com.zcz.reflecttest.HaShiQi.name
——————— 获取所有公有的方法 —————————
public void com.zcz.reflecttest.HaShiQi.run()
public java.lang.String com.zcz.reflecttest.HaShiQi.toString()
public void com.zcz.reflecttest.HaShiQi.eat()
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()
——————— 获取所有类中声明的方法 —————————
public void com.zcz.reflecttest.HaShiQi.run()
public java.lang.String com.zcz.reflecttest.HaShiQi.toString()
private void com.zcz.reflecttest.HaShiQi.dance()
public void com.zcz.reflecttest.HaShiQi.eat()
——————— 获取所有公有的构造器 —————————

  因为HaShiQi类中没有公有的构造器,所以这里什么都没有打印出来。自然也有获取到私有构造器的方法:

public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// TODO Auto-generated method stub
//获取类对象,除了这份方法外还有另外两种方法
Class clazz = HaShiQi.class;

    System.out.println("——————— 获取所有公有的属性  —————————");
    Field[] fields = clazz.getFields();
    for(Field field : fields) {
        field.setAccessible(true);
        System.out.println(field);
    }

    System.out.println("——————— 获取所有的属性  —————————");
    fields = clazz.getDeclaredFields();
    for(Field field : fields) {
        field.setAccessible(true);
        System.out.println(field);
    }
    System.out.println("——————— 获取所有公有的方法  —————————");
    Method[] methods = clazz.getMethods();
    for(Method method : methods) {
        System.out.println(method);
    }
    System.out.println("——————— 获取所有类中声明的方法  —————————");
    methods = clazz.getDeclaredMethods();
    for(Method method : methods) {
        System.out.println(method);
    }
    System.out.println("——————— 获取所有公有的构造器  —————————");
    Constructor[] constructors = clazz.getConstructors();
    for(Constructor constructor : constructors) {
        System.out.println(constructor);
    }
    System.out.println("——————— 获取所有的构造器  —————————");
    constructors = clazz.getDeclaredConstructors();
    for(Constructor constructor : constructors) {
        System.out.println(constructor);
    }
}

  打印结果:

——————— 获取所有公有的属性 —————————
public java.lang.String com.zcz.reflecttest.HaShiQi.color
——————— 获取所有的属性 —————————
public java.lang.String com.zcz.reflecttest.HaShiQi.color
private java.lang.String com.zcz.reflecttest.HaShiQi.name
——————— 获取所有公有的方法 —————————
public void com.zcz.reflecttest.HaShiQi.run()
public java.lang.String com.zcz.reflecttest.HaShiQi.toString()
public void com.zcz.reflecttest.HaShiQi.eat()
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()
——————— 获取所有类中声明的方法 —————————
public void com.zcz.reflecttest.HaShiQi.run()
public java.lang.String com.zcz.reflecttest.HaShiQi.toString()
private void com.zcz.reflecttest.HaShiQi.dance()
public void com.zcz.reflecttest.HaShiQi.eat()
——————— 获取所有公有的构造器 —————————
——————— 获取所有的构造器 —————————
private com.zcz.reflecttest.HaShiQi()

七,实例化对象

  HaSHiQi类中的信息,包括属性,方法,构造器,我们都已经通过反射浏览了一遍,那么接下来就要再使用反射实例化HaShiQi类的对象了,因为只有实例对象才能调用属性和方法。

  因为HaShiQi类中只显示声明了一个空参构造器,所以我们只能使用这个构造器来实例化对象。

  正常情况下获取指定参数的构造器,需要使用方法clazz.getConstructor(parameterTypes(参数类对象数组))。但是HaShiQi的构造方法是私有的,所以使用这个方法去获取构造器会报错:

Constructor cons = clazz.getConstructor();
Exception in thread "main" java.lang.NoSuchMethodException: com.zcz.reflecttest.HaShiQi.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getConstructor(Class.java:1825)
at com.zcz.reflecttest.LookLook.main(LookLook.java:61)
  所以我们使用另外一个方法解决这个问题:

    // 实例化对象
    // 获取构造器
    Constructor con = clazz.getDeclaredConstructor();
    // 强制设置为可以访问
    con.setAccessible(true);
    HaShiQi haShiQi = (HaShiQi)con.newInstance();
    System.out.println("没有做任何修改前:"+haShiQi.toString());

  打印结果:  

没有做任何修改前:名字:富贵;颜色:黑色
  哈哈,机智如我,怎么能被这种问题打到,从代码中可以看到我们使用了构造器的newInstance方法实例化了一个HaShiQi对象。

    代码:con.newInstance();

    是不是很熟悉?,在文章JAVA设计模式-动态代理(Proxy)源码分析中,实例化代理类对象的时候,也使用到了:

// 通过反射,将h作为参数,实例化代理类,返回代理类实例。
return cons.newInstance(new Object[]{h});
  接下来就是访问对象的属性和方法了如果我们把所有的属性和方法都访问到,不就把同事的狗狗看了一遍了吗?。

八,修改属性

    // 修改狗狗的颜色
    // 获取狗狗的color属性
    Field filed = clazz.getField("color");
    filed.set(haShiQi, "红色");
    System.out.println("修改狗狗的颜色:"+haShiQi.toString());

  打印结果: 

没有做任何修改前:名字:富贵;颜色:黑色
修改狗狗的颜色:名字:富贵;颜色:红色
  修改成功,接着修改狗狗的名字:

// 修改狗狗的颜色
// 获取狗狗的color属性
Field filed = clazz.getField("color");
filed.set(haShiQi, "红色");
System.out.println("修改狗狗的颜色:"+haShiQi.toString());
// 修改狗狗的名字
filed = clazz.getDeclaredField("name");
// 强制设置为可以访问
filed.setAccessible(true);
filed.set(haShiQi, "惊喜");
System.out.println("修改狗狗的名字:"+haShiQi.toString());

  打印结果:

没有做任何修改前:名字:富贵;颜色:黑色
修改狗狗的颜色:名字:富贵;颜色:红色
修改狗狗的名字:名字:惊喜;颜色:红色
  修改成功,但是千万要注意是的是的getDeclaredField方法,而不是getField方法,使用getField方法会抛出异常:Exception in thread "main" java.lang.NoSuchFieldException: name;

  同时filed.setAccessible(true);也是必不可少了,否则将抛出异常:Exception in thread "main" java.lang.IllegalAccessException: Class com.zcz.reflecttest.LookLook can not access a member of class com.zcz.reflecttest.HaShiQi with modifiers "private"

  属性修改完,我们就开始调用方法吧。

九,调用方法

    //调用run方法
    Method method = clazz.getMethod("run");
    method.invoke(haShiQi, null);

  打印结果:

惊喜 去跑着玩儿
  调用成功;

    代码:method.invoke(haShiQi, null);

  是不是觉得这句代码也是很熟悉的?在文章JAVA设计模式-动态代理(Proxy)示例及说明中也有相同的用法:

// 通过反射机制,通知力宏做事情
method.invoke(object, args);
  我们继续,eat方法和run方法一样,都是公有的方法,这里就不再演示了,接下来演示一下私有的dance方法:

    //调用run方法
    Method method = clazz.getMethod("run");
    method.invoke(haShiQi, null);
    //调用dance方法
    method = clazz.getDeclaredMethod("dance");
    method.setAccessible(true);
    method.invoke(haShiQi, null);

  打印结果:

惊喜 去跑着玩儿
惊喜 来跳一支舞
  也同样成功了,但是也要注意getDeclaredMethod方法和method.setAccessible(true);

  到这里,同事不让我看的狗狗,我通过java的反射机制从里到外的全部都看了一遍,不仅仅是看了一遍,我还给他的狗狗改了颜色和名字。调用了所有的方法。是不是很神奇,很有成就感?

十,总结

  反射的基本用法已经都在上面的实例中演示到了,但是反射还是有其他的方法在这里并没有提到,有机会的话继续补充。

  在上面的代码过程中,你也许也发现了,不仅仅是有类对象,而且还有属性,方法和构造器都是对象,属性是java.lang.reflect.Field类的对象,方法是java.lang.reflect.Method类的对象,构造器是java.lang.reflect.Constructor类的对象。看来在java中确实是一切皆对象。

linuxboy的RSS地址:https://www.linuxboy.net/rssFeed.aspx

本文永久更新链接地址:https://www.linuxboy.net/Linux/2018-10/154635.htm

相关内容