JAVA反射机制瞎扯篇
反射机制的定义和应用
废话看不懂系列:
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法
Java反射机制可以无视类方法、变量访问权限修饰符,可以调用任何类的任意方法、访问并修改成员变量值
。也就是说只要发现一处Java反射调用漏洞几乎就可以为所欲为了。当然前提可能需要你能控制反射的类名、方法名和参数
。
反射的应用:
ide 自动提示功能,对象(提示属性,方法)–通过反射机制知道的
servlet ->程序员定义->配置文件中的配置->tomcat创建->配置文件信息->类的全路径->tomcat可以根据类的完整名称创建对象
反射可以根据一个字符串(类的全路径) 创建一个类的对象
如在程序中,使用反射技术,程序可以从配置文件中读取配置(字符串:类的全路径)然后动态创建该对象,只需修改配置文件,即可更改创建的对象,而不用写死在代码中
人话能看懂系列:
这里有个类 Car
1 | public class Car{ |
正常创建对象:
1 | Car car = new Car() |
反射创建对象:
1 | 新对象 = 反射("Car") |
Java里有个自带的功能能把字符串当类名,创建个新类,还能知道并调用类中的方法,这就是反射,不然在ide里敲.
号的时候编辑器咋知道你那类里都有啥方法的
反射的基石
字节码文件对象
java反射基于字节码文件对象
字节码文件对象->Class类的对象(字节码文件即java源文件编译后生成的.class文件) Class是java中的一个类型
jvm将.class文件加载到内存执行时,会将.class文件当作字节码文件对象
区分: class , class为java中的关键字,用来定义类
获得字节码文件对象
1. Object 类的getClass方法
已经获得目标类对象实例,就是已经new出来了一个对象
可以在对象后面使用getClass()
方法
1 | public class Preson{ |
2. 类型的.class属性
已获得目标类名
引用类型和基本数据类型都可以
1 | public class Preson{ |
3. Class.forName(“类的全路径”) //最常用
目标类名在编译器不确定,在运行期确定
1 | Class class3 = Class.forName("com.xxx.xxx.xxx.Person"); |
就是forName()
中传入的参数,不是代码中写死的,是从某个配置文件中获取的,或者外部传入的
1 | x = 某个配置文件中指定的类的路径 |
类装载器也可以用来加载类
1 | ClassLoader loader = Thread.currentThread().getContextClassLoader(); Class clazz = loader.loadClass("com.xxx.xxx.Person"); |
1 | ClassLoader.getSystemClassLoader().loadClass("java.lang.Runtime") |
class.forName()
除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块
而classLoader只干一件事情,就是将.class文件加载到jvm中,只有在newInstance才会去执行static块
1 | static块是随着类的加载而执行,只执行一次,并优先于主函数,类调用时,先执行静态代码块,然后才执行主函数 |
使用字节码文件对象
一个字节码文件对象对应一个类,类中包含构造方法,成员变量,成员方法
1 | 类 字节码文件对象(类在内存中的体现) |
使用字节码文件对象来构建一个类的对象
正常创建对象
1 | Person p = new Person(); |
Person()
为类的构造方法,对象是通过构造方法创建的
使用反射创建目标类对象实例并调用其中的方法
Object newINstance()
:通过调用默认构造器创建一个对象实例
1 | Class clzz =Class.forName("xxx.xxx.xx.Person"); //获取class对象 |
使用反射创建新的类有两种方式
1 | Class.newInstance() // 只能调用默认的无参构造函数而且必须是public类型 |
上文即用的第二种方式,使用了构造器,也可以不用
1 | Class clzz =Class.forName("xxx.xxx.xx.Person"); |
获得构造器:
Constructor[] getConstructors()
:获得所有public构造器;Constructor[] getDeclaredConstructors()
:获得所有访问权限的构造器Constructor getConstructor(Class[] params)
:根据指定参数(也可无参数)获得对应的public构造器;Constructor getDeclaredConstructor(Class[] params)
:根据指定参数(也可无参数)获得对应构造器;
如果被调用的类的构造函数为默认的构造函数,采用Class.newInstance()则是比较好的选择;如果需要调用类的带参构造函数、私有构造函数, 就需要采用Constractor.newInstance()
使用私有构造器的时候需要使用Constructor.setAccessible(true)
方法
1 | Constructor con = clzz.getDeclaredConstructor() |
获得方法:
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)
:根据变量名获得对应的变量,访问权限不限;
反射创建对象并调用其中的方法
一个简单的类
1 | class Test{ |
通过反射调用Test类的print方法:
匿名对象直接调用成员方法:
1
Test.class.newInstance().print();
通过invoke:
1
2
3
4
Object test = Test.class.newInstance();
Method print = Test.class.getMethod("print");
print.invoke(test);或者
1
Test.class.getMethod("print").invoke(Test.class.newInstance(),null);
有名对象
1
2Test test = (Test)Test.class.newInstance();
test.print();但是如果这样写:
1
2
3Object test = Test.class.newInstance();
test.print();程序就会报错找不到print方法,为什么?java中一切皆对象,对象也是个对象,就是Object,那Object就是所有对象的爹,是个顶级父类,如果用Object去接收netInstance(),相当于做了一次向上转型,子类转换成父类,子类方法丢失,所以会找不到方法
一行代码即可实现反射调用Runtime执行本地命令:
1 | Runtime.class.getMethod("exec", String.class).invoke(Runtime.class.getMethod("getRuntime").invoke(null), "whoami") |
分析:
正常执行命令:
1 | Runtime.getRuntime.exec("calc"); |
使用了静态方法getRuntime
,无需实例化类即可使用
查看Runtime类中的定义
exec 有多个重载,以最简单的为例。定义如下
传入参数作为命令执行
1 | public Process exec(String command) throws IOException { |
那么为什么不能直接Runtime.exec()
而需要多加一个Runtime.getRuntime
在java中当对象方法仅需使用一次的时候,可以通过调用类的构造函数创建匿名对象
但是Runtime类的构造函数是一个私有函数,无法被实例化
1 | /** Don't let anyone else instantiate this class(不要让任何人实例化这个类) */ |
所以只能使用getRuntime
这个静态方法来取得Runtime类的实例如Runtime runtime = Runtime.getRuntime()
getRuntime()在调用之后返回一个Runtime类的实例currentRuntime
1 | private static Runtime currentRuntime = new Runtime(); |
在反射调用的时候,通过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方法
1 | Runtime.class.getMethod("exec", String.class).invoke(Runtime.class.getMethod("getRuntime").invoke(null), "whoami") |
invoke静态方法
invoke静态方法时,可以不传入目标对象的obj,也可以执行成功
1 | //main函数 |
静态和非静态的区别:
1.全局唯一,任何一次的修改都是全局性的影响
2.只加载一次,优先于非静态
3.使用方式上不依赖于实例对象,通过类名.即可调用。
4.生命周期属于类级别,从JVM 加载开始到JVM卸载结束。