java安全-初识java反射
2022-10-18 15:36:59

0x00、前言

学习java基础知识记录,方便查阅。

0x01、反射基础

一、反射概念

java执行分为编译期运行期
编译期是指把源码交给编译器编译成计算机可以执行的文件的过程。在 Java 中也就是把 Java 代码编成 class 文件的过程。编译期只是做了一些翻译功能,并没有把代码放在内存中运行起来,而只是把代码当成文本进行操作,比如检查错误。

运行期是把编译后的文件交给计算机执行,直到程序运行结束。所谓运行期就把在磁盘中的代码放到内存中执行起来。

Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息。在 Java 中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。

Java 反射机制主要提供了以下功能,这些功能都位于java.lang.reflect包。

  • 在运行时判断任意一个对象所属的类。
  • 在运行时构造任意一个类的对象。
  • 在运行时判断任意一个类所具有的成员变量和方法。
  • 在运行时调用任意一个对象的方法。
  • 生成动态代理。

反射与常用引用类对象区别
正常方式:引入对应的包类名称——>通过new实例化——>获取实例化对象

反射方式:实例化类对象——>Class获取方法——>得到完整的包类名称

Java反射机制的优缺点
优点:

  • 能够运行时动态获取类的实例,大大提高系统的灵活性扩展性
  • 与Java动态编译相结合,可以实现无比强大的功能。
  • 降低代码程序之间的依赖性。
  • 对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。

缺点:

  • 反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;
  • 冗余了很多代码量。
  • 反射调用方法时可以忽略权限检查,获取这个类的私有方法和属性,因此可能会破坏类的封装性而导致安全问题。

二、反射实现

1、总结简述

1、通过Class类获取类。
2、通过newInstance()对类进行实例化。
3、通过Field访问成员变量|通过Method访问成员方法|通过Constructor访问成员构造方法

注:如果实例类存在构造方法,newInstance()实例化必须保证实例类存在无参构造方法,如只有有参构造方法,newInstance()会报错。Java9以后推荐用clazz.getDeclaredConstructor().newInstance()方式即获取构造方法后再实例化,而非直接newInstance()。

反射机制重要的类

含义
java.lang.Class 代表整个字节码。代表一个类型,代表整个类。
java.lang.reflect.Method 代表字节码中的方法字节码。代表类中的方法。
java.lang.reflect.Constructor 代表字节码中的构造方法字节码。代表类中的构造方法(方法名同类名相同且无参的方法)。
java.lang.reflect.Field 代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)。

必须通过Class获取类过后才能获取Method、Constructor、Field
也就是说Class是反射实现的前提。且Class并不是new出来的,而是java内置的。

2、Class访问类

Class获取方式

方式 示例注解
Class.forName(“完整类名带包名”) Class A=Class.forName(“com.java.reflect.people”);
对象.getClass() people peo=new people();Class A=peo.getClass();
任何类型.class Class A = String.class;

反射类可访问的常用方法

代码实现
people类:

package com.javaweb.reflect;

import java.io.Serializable;

public class people implements Serializable{
private String name;
private String phone;
private int age;
}

实现类:

package com.javaweb.reflect;

import java.lang.reflect.*;

public class reflect {
public static void main (String[] args) throws Exception{
//1、使用反射.forName("完整类名")获取类
Class people1=Class.forName("com.javaweb.reflect.people");
System.out.println("使用.forName(\"完整类名\")获取类 的类名"+people1.getName());
//2、使用反射 对象.getClass()获取类
people pl=new people();
Class people2=pl.getClass();
System.out.println("使用对象.getClass()获取类 的类名"+people2.getName());
//3、使用反射 任何类型.class获取类
Class people3=String.class;
System.out.println("使用任何类型.class获取类 的类名"+people3.getName());
}

}

三种获取类的实现截图:

2、Field访问成员变量

通过下列任意一个方法访问成员变量时将返回 Field 类型的对象或数组。
Field声明使用的方法

Field声明时的方法 注解
getFields() 获取所有权限为public的成员变量
getField(String name) 获取变量名为name的成员变量
getDeclaredFields() 获取当前对象的所有成员变量
getDeclaredField(String name) 获取变量名为name的成员变量

注:针对private私有的变量,需要使用setAccessible(true)方法打破封装,访问私有变量

Field常用方法

方法名称 说明
getName() 获得该成员变量的名称
getType() 获取表示该成员变量的 Class 对象
get(Object obj) 获得指定对象 obj 中成员变量的值,返回值为 Object 类型
set(Object obj, Object value) 将指定对象 obj 中成员变量的值设置为 value
getlnt(0bject obj) 获得指定对象 obj 中成员类型为 int 的成员变量的值
setlnt(0bject obj, int i) 将指定对象 obj 中成员变量的值设置为 i
setFloat(Object obj, float f) 将指定对象 obj 中成员变量的值设置为 f
getBoolean(Object obj) 获得指定对象 obj 中成员类型为 boolean 的成员变量的值
setBoolean(Object obj, boolean b) 将指定对象 obj 中成员变量的值设置为 b
getFloat(Object obj) 获得指定对象 obj 中成员类型为 float 的成员变量的值
setAccessible(boolean flag) 此方法可以设置是否忽略权限直接访问 private 等私有权限的成员变量
getModifiers() 获得可以解析出该方法所采用修饰符的整数

代码实现
people类:

package com.javaweb.reflect;

import java.io.Serializable;

public class people implements Serializable{
private String name;
private String phone;
private int age;
}

实现类:

package com.javaweb.reflect;

import java.lang.reflect.*;

public class reflect {
public static void main (String[] args) throws Exception{
//三种获取类的方式
//1、使用反射.forName("完整类名")获取类
Class people1=Class.forName("com.javaweb.reflect.people");
System.out.println("使用.forName(\"完整类名\")获取类 的类名"+people1.getName());
//2、使用反射 对象.getClass()获取类
people pl=new people();
Class people2=pl.getClass();
System.out.println("使用对象.getClass()获取类 的类名"+people2.getName());
//3、使用反射 任何类型.class获取类
Class people3=String.class;
System.out.println("使用任何类型.class获取类 的类名"+people3.getName());

//进行反射类实例化
Object obj=people1.newInstance();
//System.out.println(obj);

//Field访问成员变量
//反射获取类变量
Field A=people1.getDeclaredField("name");
//由于people类属性值为private私有的,需要setAccessible(true)打破封装,访问私有变量。
A.setAccessible(true);
//反射设置类中的变量值
A.set(obj,"张三");
//输出该obj对象中变量的变量值
System.out.println(A.get(obj));
}
}

Field实现截图:

3、Method访问成员方法

要动态获取一个对象方法的信息,首先需要通过下列方法之一创建一个Method类型的对象或者数组。
Method声明使用的方法

Method声明使用的方法 注解
getMethods() 获取所有权限为public的成员方法
getMethods(String name,Class<?> …parameterTypes) 获取方法名为name的成员方法,参数类型在方法名逗号后面,没有形参就不传
getDeclaredMethods() 获取的成员所有的方法
getDeclaredMethods(String name,Class<?>…parameterTypes) 获取方法名为name的成员方法,参数类型在方法名逗号后面,没有形参就不传

注:针对private私有的方法,需要使用setAccessible(true)方法打破封装,访问私有方法

Method常用方法

静态方法名称 说明
getName() 获取该方法的名称
getParameterType() 按照声明顺序以 Class 数组的形式返回该方法各个参数的类型
getReturnType() 以 Class 对象的形式获得该方法的返回值类型
getExceptionTypes() 以 Class 数组的形式获得该方法可能抛出的异常类型
invoke(Object obj,Object…args) 利用 args 参数执行指定对象 obj 中的该方法,返回值为 Object 类型
isVarArgs() 查看该方法是否允许带有可变数量的参数,如果允许返回 true,否则返回 false
getModifiers() 获得可以解析出该方法所采用修饰符的整数

代码实现
people类:

package com.javaweb.reflect;

import java.io.Serializable;

public class people implements Serializable{
private String name;
private String phone;
private int age;

public void setinfo(String name,String phone,int age){
this.name=name;
this.phone=phone;
this.age=age;
}
public String show(){
return "{name:"+name+";age:"+age+";phone:"+phone+"}";
}

}

实现类:

package com.javaweb.reflect;

import java.lang.reflect.*;

public class reflect {
public static void main (String[] args) throws Exception{
//三种获取类的方式
//1、使用反射.forName("完整类名")获取类
Class people1=Class.forName("com.javaweb.reflect.people");
System.out.println("使用.forName(\"完整类名\")获取类 的类名"+people1.getName());
//2、使用反射 对象.getClass()获取类
people pl=new people();
Class people2=pl.getClass();
System.out.println("使用对象.getClass()获取类 的类名"+people2.getName());
//3、使用反射 任何类型.class获取类
Class people3=String.class;
System.out.println("使用任何类型.class获取类 的类名"+people3.getName());

//进行反射类实例化
Object obj=people1.newInstance();
//System.out.println(obj);

//Field访问成员变量
//反射获取类变量
Field A=people1.getDeclaredField("name");
//由于people类属性值为private私有的,需要setAccessible(true)打破封装,访问私有变量。
A.setAccessible(true);
//反射设置类中的变量值
A.set(obj,"张三");
//输出该obj对象中变量的变量值
System.out.println(A.get(obj));

//Method访问成员setinfo()方法并传参设置变量值
Method mt=people1.getDeclaredMethod("setinfo", String.class, String.class, int.class);
//Method调用方法传参
Object mtobj=mt.invoke(obj,"张三","13011111111",18);
//Method访问成员show()方法
Method mt2=people1.getDeclaredMethod("show");
Object mtobj2=mt2.invoke(obj);
System.out.println(mtobj2);
}
}

Method实现截图:

4、Constructor访问成员构造方法

为了能够动态获取对象构造方法的信息,首先需要通过下列方法之一创建一个 Constructor 类型的对象或者数组。
Constructor声明使用的方法

Constructor声明使用的方法 注解
getConstructor(Class<?>…parameterTypes) 获取所有权限为public的构造方法
getDeclaredConstructors() 获取当前对象的所有构造方法
getDeclaredConstructor(Class<?>…parameterTypes) 获取当前对象所有带参数类型的构造方法

Constructor常用的方法

方法名称 说明
public String getName() 返回构造方法名
isVarArgs() 查看该构造方法是否允许带可变数量的参数,如果允许,返回 true,否则返回false
getParameterTypes() 按照声明顺序以 Class 数组的形式获取该构造方法各个参数的类型
getExceptionTypes() 以 Class 数组的形式获取该构造方法可能抛出的异常类型
newInstance(Object … initargs) 通过该构造方法利用指定参数创建一个该类型的对象,如果未设置参数则表示采用默认无参的构造方法
setAccessiable(boolean flag) 如果该构造方法的权限为 private,默认为不允许通过反射利用 netlnstance()方法创建对象。如果先执行该方法,并将入口参数设置为 true,则允许创建对象
getModifiers() 获得可以解析出该构造方法所采用修饰符的整数

代码实现
people类:

package com.javaweb.reflect;

import java.io.Serializable;

public class people implements Serializable{
private String name;
private String phone;
private int age;

public void setinfo(String name,String phone,int age){
this.name=name;
this.phone=phone;
this.age=age;
}
public people(){
System.out.print("调用了无参构造方法:");
}
public people(String name,String phone,int age){
setinfo(name,phone,age);
System.out.print("调用了带参数的构造方法:"+show());
}

public String show(){
return "{name:"+name+";age:"+age+";phone:"+phone+"}";
}

}

实现类:

package com.javaweb.reflect;

import java.lang.reflect.*;

public class reflect {
public static void main (String[] args) throws Exception{
//三种获取类的方式
//1、使用反射.forName("完整类名")获取类
Class people1=Class.forName("com.javaweb.reflect.people");
System.out.println("使用.forName(\"完整类名\")获取类 的类名"+people1.getName());
//2、使用反射 对象.getClass()获取类
people pl=new people();
Class people2=pl.getClass();
System.out.println("使用对象.getClass()获取类 的类名"+people2.getName());
//3、使用反射 任何类型.class获取类
Class people3=String.class;
System.out.println("使用任何类型.class获取类 的类名"+people3.getName());

//进行反射类实例化
Object obj=people1.newInstance();
//System.out.println(obj);

//Field访问成员变量
//反射获取类变量
Field A=people1.getDeclaredField("name");
//由于people类属性值为private私有的,需要setAccessible(true)打破封装,访问私有变量。
A.setAccessible(true);
//反射设置类中的变量值
A.set(obj,"张三");
//输出该obj对象中变量的变量值
System.out.println(A.get(obj));

//Method访问成员setinfo()方法并传参设置变量值
Method mt=people1.getDeclaredMethod("setinfo", String.class, String.class, int.class);
//Method调用方法传参
Object mtobj=mt.invoke(obj,"张三","13011111111",18);
//Method访问成员show()方法
Method mt2=people1.getDeclaredMethod("show");
Object mtobj2=mt2.invoke(obj);
System.out.println(mtobj2);

System.out.println("----------分割线--------");

//Constructor访问成员构造方法
//方法一、通过newInstance()调用无参构造方法
Object obj2=people1.newInstance();
System.out.println(" 方法一:直接通过newInstance()调用无参构造方法");
//方法二、通过getDeclaredConstructor()调用带参构造方法,再调用newIntance()传参构造方法
Constructor ct=people1.getDeclaredConstructor(String.class, String.class, int.class);
Object obj3=ct.newInstance("张四","13022222222",19);
System.out.println(" 方法二:通过getDeclaredConstructor()调用带参构造方法,再调用newIntance()传参构造方法");
//方法三、通过getDeclaredConstructor()调用无参构造方法
Constructor ct2=people1.getDeclaredConstructor();
Object obj4=ct2.newInstance();
System.out.println(" 方法三:通过getDeclaredConstructor()调用无参构造方法");
}
}

Constructor实现截图:

0x02、思考

Java反射是真的累啊看下来,为了实现动态对类的操作,绕了很大一圈,多出来很多代码去实现这个功能,但确实使用反射很大程度降低了代码之间的依赖性,实现动态加载。
java反射核心想法就是:突破常规访问限制,就是为了动态访问类。
导致的安全问题也是因为突破常规访问限制,利用反射去访问对象以及篡改变量值包括一些私有属性的变量。

0x03、参考链接

http://c.biancheng.net/view/6907.html
https://blog.csdn.net/qq_44715943/article/details/120587716