反射机制的定义和应用

废话看不懂系列:

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法

Java反射机制可以无视类方法、变量访问权限修饰符,可以调用任何类的任意方法、访问并修改成员变量值。也就是说只要发现一处Java反射调用漏洞几乎就可以为所欲为了。当然前提可能需要你能控制反射的类名、方法名和参数

反射的应用:

ide 自动提示功能,对象(提示属性,方法)–通过反射机制知道的
servlet ->程序员定义->配置文件中的配置->tomcat创建->配置文件信息->类的全路径->tomcat可以根据类的完整名称创建对象

反射可以根据一个字符串(类的全路径) 创建一个类的对象
如在程序中,使用反射技术,程序可以从配置文件中读取配置(字符串:类的全路径)然后动态创建该对象,只需修改配置文件,即可更改创建的对象,而不用写死在代码中

人话能看懂系列:

这里有个类 Car

public class Car{
    public void run(){
        return NULL;
    }
}

正常创建对象:

Car car = new Car()

反射创建对象:

新对象 = 反射("Car")

Java里有个自带的功能能把字符串当类名,创建个新类,还能知道并调用类中的方法,这就是反射,不然在ide里敲.号的时候编辑器咋知道你那类里都有啥方法的

反射的基石

字节码文件对象

java反射基于字节码文件对象

字节码文件对象->Class类的对象(字节码文件即java源文件编译后生成的.class文件) Class是java中的一个类型
jvm将.class文件加载到内存执行时,会将.class文件当作字节码文件对象

区分: class , class为java中的关键字,用来定义类

获得字节码文件对象

1. Object 类的getClass方法

已经获得目标类对象实例,就是已经new出来了一个对象

可以在对象后面使用getClass()方法

public class Preson{
}
Person p = new Person();
Class class1 = p.getClass();

2. 类型的.class属性

已获得目标类名

引用类型和基本数据类型都可以

public class Preson{
}
Class class2 = Person.class;

3. Class.forName(“类的全路径”) //最常用

目标类名在编译器不确定,在运行期确定

Class class3 = Class.forName("com.xxx.xxx.xxx.Person");

就是forName()中传入的参数,不是代码中写死的,是从某个配置文件中获取的,或者外部传入的

x = 某个配置文件中指定的类的路径
Class class3 = Class.forName(x);

类装载器也可以用来加载类

ClassLoader loader = Thread.currentThread().getContextClassLoader(); Class clazz = loader.loadClass("com.xxx.xxx.Person");
ClassLoader.getSystemClassLoader().loadClass("java.lang.Runtime")

class.forName()除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块
而classLoader只干一件事情,就是将.class文件加载到jvm中,只有在newInstance才会去执行static块

static块是随着类的加载而执行,只执行一次,并优先于主函数,类调用时,先执行静态代码块,然后才执行主函数
静态代码块其实就是给类初始化的(jvm加载类时执行,仅执行一次),而构造代码块是给对象初始化的,每一次创建对象时执行
执行顺序优先级:静态块, main(),函数,构造块,构造方法

使用字节码文件对象

一个字节码文件对象对应一个类,类中包含构造方法,成员变量,成员方法

类       字节码文件对象(类在内存中的体现)
构造方法  构造方法对象 类型:Constructor
成员变量  成员变量对象 类型:Filed
成员方法  成员方法对象 类型:Method

使用字节码文件对象来构建一个类的对象

正常创建对象

Person p = new Person();

Person()为类的构造方法,对象是通过构造方法创建的

使用反射创建目标类对象实例并调用其中的方法

Object newINstance():通过调用默认构造器创建一个对象实例

Class clzz =Class.forName("xxx.xxx.xx.Person"); //获取class对象
Constructor[] constructors = clzz.getContructors(); // 获取构造器
Constructor c = constructors[0]; //使用构造器
Person p = (Person) c.newInstance();调用默认构造器实例化对象 返回目标类的实例
Method eat = p.getMethod("eat",String.class); //获取指定方法 参数为方法名和方法的形参类型
eat.invoke(p."meal"); // 使用Method的invoke方法,传入类的实例和参数

使用反射创建新的类有两种方式

Class.newInstance() // 只能调用默认的无参构造函数而且必须是public类型
Constructor.newInstance() // 可以根据传入的参数调用任意构造函数,特定情况下可调用私有构造函数

上文即用的第二种方式,使用了构造器,也可以不用

Class clzz =Class.forName("xxx.xxx.xx.Person");
Person p = (Person) clzz.newInstance();

获得构造器:

  • Constructor[] getConstructors():获得所有public构造器;
  • Constructor[] getDeclaredConstructors():获得所有访问权限的构造器
  • Constructor getConstructor(Class[] params):根据指定参数(也可无参数)获得对应的public构造器;
  • Constructor getDeclaredConstructor(Class[] params):根据指定参数(也可无参数)获得对应构造器;

如果被调用的类的构造函数为默认的构造函数,采用Class.newInstance()则是比较好的选择;如果需要调用类的带参构造函数、私有构造函数, 就需要采用Constractor.newInstance()

使用私有构造器的时候需要使用Constructor.setAccessible(true)方法

Constructor con = clzz.getDeclaredConstructor()
con.setAccessible(true)
targetClass obj = (targetClass)con.newInstance();

获得方法:

  • Method[] getMethods():获得所有public方法;

  • Method[] getDeclaredMethods():获得所有访问权限的方法;

  • Method getMethod(String name, Class[] params):根据方法签名获取类自身对应public方法,或者从基类继承和接口实现的对应public方法

  • Method getDeclaredMethod(String name, Class[] params):根据方法签名获得对应的类自身声明方法访问权限不限

获得变量

  • Field[] getFields():获得类中所有public变量

  • Field[] getDeclaredFields():获得类中所有访问权限变量

  • Field getField(String name):根据变量名得到对应的public变量

  • Field getDeclaredField(String name):根据变量名获得对应的变量,访问权限不限

反射创建对象并调用其中的方法

一个简单的类

class Test{
  public void print(){
  System.out.println("hello!");
     }
 }

通过反射调用Test类的print方法:

  1. 匿名对象直接调用成员方法:

    Test.class.newInstance().print();
  2. 通过invoke:

    
    Object test = Test.class.newInstance();
    Method print = Test.class.getMethod("print");
    print.invoke(test);

    或者

    Test.class.getMethod("print").invoke(Test.class.newInstance(),null);
  3. 有名对象

     Test test = (Test)Test.class.newInstance();
     test.print();

    但是如果这样写:

     Object test = Test.class.newInstance();
    
     test.print();

    程序就会报错找不到print方法,为什么?java中一切皆对象,对象也是个对象,就是Object,那Object就是所有对象的爹,是个顶级父类,如果用Object去接收netInstance(),相当于做了一次向上转型,子类转换成父类,子类方法丢失,所以会找不到方法

一行代码即可实现反射调用Runtime执行本地命令:

Runtime.class.getMethod("exec", String.class).invoke(Runtime.class.getMethod("getRuntime").invoke(null), "whoami")

分析:
正常执行命令:

Runtime.getRuntime.exec("calc");

使用了静态方法getRuntime,无需实例化类即可使用
查看Runtime类中的定义
exec 有多个重载,以最简单的为例。定义如下
传入参数作为命令执行

public Process exec(String command) throws IOException {
  return exec(command, null, null);
}

那么为什么不能直接Runtime.exec()而需要多加一个Runtime.getRuntime

在java中当对象方法仅需使用一次的时候,可以通过调用类的构造函数创建匿名对象
但是Runtime类的构造函数是一个私有函数,无法被实例化

/** Don't let anyone else instantiate this class(不要让任何人实例化这个类) */
private Runtime() {}

所以只能使用getRuntime这个静态方法来取得Runtime类的实例如Runtime runtime = Runtime.getRuntime()
getRuntime()在调用之后返回一个Runtime类的实例currentRuntime

private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
  return currentRuntime;
}

在反射调用的时候,通过Runtime.class.getMethod("exec",String.class)获取Runtime类的exec方法,获取方法后可直接调用invoke方法传入参数
Runtime.class.getMethod("exec",String.class).invoke(),invoke()方法传入的参数为目标类的实例和要执行的参数,获取Runtime类的实例又可以通过Rutime.class.getMethod("getRuntime").invoke(null)来获取,所以需要将Rutime.class.getMethod("getRuntime").invoke(null)和执行的命令作为参数传递给exec的invoke方法

Runtime.class.getMethod("exec", String.class).invoke(Runtime.class.getMethod("getRuntime").invoke(null), "whoami")

invoke静态方法
invoke静态方法时,可以不传入目标对象的obj,也可以执行成功

//main函数
Class.forName("a").getMethod("print").invoke(null);
//内部类:
class a{
    public static void print(){
        System.out.println(1);
    }
}

静态和非静态的区别:
1.全局唯一,任何一次的修改都是全局性的影响
2.只加载一次,优先于非静态
3.使用方式上不依赖于实例对象,通过类名.即可调用。
4.生命周期属于类级别,从JVM 加载开始到JVM卸载结束。

⬆︎TOP