首页 > 编程语言 >javasec(一)java反射

javasec(一)java反射

时间:2023-04-19 18:01:47浏览次数:49  
标签:反射 java name class Class javasec 获取 public String

这篇文章介绍javasec基础知识--java反射。

0x01 反射是什么?

反射是一种机制,利用反射机制动态的实例化对象、读写属性、调用方法、构造函数。

程序运行状态中对于任意一个类或对象,都能够获取到这个类的所有属性和方法(包括私有属性和方法),这种动态获取信息以及动态调用对象方法的功能就称为反射机制。简单来讲,通过反射,类对我们是完全透明的,想要获取任何东西都可以。

通过反射,我们可以在程序运行时动态创建对象,还能获取到类的所有信息,比如它的属性、构造器、方法、注解等(无法获取父类被projected修饰的东西)

0x02 铺垫知识

1.java代码的三个阶段及class对象的由来

  1. Source源代码阶段:.java被编译成*.class字节码文件。
  2. Class类对象阶段:.class字节码文件被类加载器加载进内存,并将其封装成Class对象(用于在内存中描述字节码文件),Class对象将原字节码文件中的成员变量抽取出来封装成数组Field[],将原字节码文件中的构造函数抽取出来封装成数组Construction[],将成员方法封装成数组Method[]。当然Class类内不止这三个,还封装了很多,我们常用的就这三个。(Class对象的由来: 将class文件读入内存,并为之创建一个Class对象)
  3. RunTime运行时阶段:使用new创建对象的过程。
  4. 示意图两张:

img

img

2.获取class对象的3种方法

1)Class.forName(类的全限定名/全路径名) -----常用

通过类的全限定名获取该类的class对象,多用于配置文件,将类名定义在配置文件中,通过读取配置文件加载类。

2)类名.class

通过类的属性class获取class对象。{所有数据类型(包括基本数据类型)都有的一个“静态的”class属性。} 多用于参数的传递

3)对象.getClass()

此方法是定义在Objec类中的方法,因此所有的类都会继承此方法。多用于对象获取字节码的方式

注意:在运行期间,一个类,只有一个Class对象产生!

先写一个Person类用于测试:

package Reflect;
public class Person {
    public String name;
    public float high;
    protected int age;
    char sex;
    private String phoneNum;
    //无参构造方法
    public Person(){
        System.out.println("调用了公有、无参构造方法执行了。。。");
    }

    //有一个参数的构造方法
    public Person(String name){
        this.name = name;
        System.out.println("调用了一个参数的构造方法\n"+"姓名:" + name);
    }

    //有多个参数的构造方法
    public Person(String name ,int age){
        this.name = name;
        this.age = age;
        System.out.println("调用了两个参数的构造方法"+"姓名:"+name+"年龄:"+ age);//这的执行效率有问题,以后解决。
    }

    //受保护的构造方法
    protected Person(boolean n){
        System.out.println("调用受保护的构造方法 n = " + n);
    }

    //私有构造方法
    private Person(int age){
        System.out.println("私有的构造方法   年龄:"+ age);
    }
}
package Reflect;

public class getClass {
    public static void main(String[] args) throws Exception {

        //方式一:Class.forName("全类名");
        Class class1 = Class.forName("Reflect.Person");   //Person自定义实体类
        System.out.println("class1 = " + class1);

        //方式二:类名.class
        Class class2 = Person.class;
        System.out.println("class2 = " + class2);

        //方式三:对象.getClass();
        Person person = new Person();
        Class class3 = person.getClass();
        System.out.println("class3 = " + class3);

        //比较三个对象
        System.out.println(class1 == class2);    //true
        System.out.println(class1 == class3);    //true
    }
}

执行结果:

img

从执行比较结果看,三种方法获取到的class对象都是同一个。

也就是说:****在运行期间,一个类,只有一个Class对象产生

0x03 代码中的利用

1.获取属性/字段 用Field类

获取成员变量并调用:
1.批量的
 		1).Field[] getFields():获取所有的"公有字段"
 		2).Field[] getDeclaredFields():获取所有字段,包括:私有、受保护、默认、公有;
2.获取单个的:
 		1).public Field getField(String fieldName):获取某个"公有的"字段;
 		2).public Field getDeclaredField(String fieldName):获取某个字段(可以是私有的)
获取字段值:Field --> get(对象)
设置字段的值:
 		Field --> public void set(对象,值): 					
参数说明:
1.obj:要设置的字段所在的对象;
2.value:要为字段设置的值;
package Reflect;
import java.lang.reflect.Field;

public class GetZd {
    public static void main(String[] args) throws Exception{
        //获取Person1类的Class对象
        Class Zd = Class.forName("Reflect.Person1");
        //获取public字段name:
        System.out.println("获取单个公有字段并调用"+Zd.getField("name")+"\n");
        //获取cn从父类Person继承的public字段who
        System.out.println("获取单个从父类继承的公有字段并调用"+Zd.getField("who")+"\n");
        //获取private字段phoneNum:
        System.out.println("获取单个私有字段并调用"+Zd.getDeclaredField("phoneNum")+"\n");
        //获取全部公有字段
        System.out.println("获取全部公有字段如下:"+"\n");
        Field[] a = Zd.getFields();
        for(Field b : a){
           System.out.println(b);
        }
        //获取所有字段(包括公有、私有、受保护)
        System.out.println("获取全部字段如下:"+"\n");
        Field[] c = Zd.getDeclaredFields();
        for(Field d : c){
            System.out.println(d);
        }
        //获取值与设置值
        Object xx = Zd.getConstructor().newInstance();
        Person1 x = (Person1)xx; //先通过反射得到的Class类对象Zd得到一个Person1类的对象
        //name.get()中的参数需要该类的对象,而不能是.class
        Field name = Zd.getField("name");
        System.out.println("直接输出name,无法获取值:"+name+"\n");
        System.out.println("用name.get(对象)方法获得值为:"+name.get(x)+"\n");
        //设置值
        //Field --> public void set(Object obj,Object value):
  		//参数说明:1.obj:要设置的字段所在的对象;2.value:要为字段设置的值;
        name.set(x,"uf9n1x3333");
        System.out.println("set后姓名为:"+x.name+"\n");

        //获取并设置私有字段
        Field phonen = Zd.getDeclaredField("phoneNum");
        System.out.println();
        phonen.setAccessible(true);//暴力反射,解除私有限定
        phonen.set(x,"7777777");
        System.out.println("私有Set后phoneNum为:"+x);//因为PhoneNum为私有权限,所以这里不能用x.PhoneNum输出它的值,所以就全输出出来看
    }
}

输出如下:

img

2. 获取方法 用Method类

Class类提供了以下几个方法来获取方法:

 Method getMethod(name, Class...):获取某个public的Method(包括父类)
 Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类)
 Method[] getMethods():获取所有public的Method(包括父类)
 Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)

执行方法使用invoke()

2.1 通过反射来使用substring
package Reflect;
import java.lang.reflect.Method;

public class Test {
    public static void main(String[] args) throws Exception{
        String name = "uf9n1x";
        Method substring1 = String.class.getMethod("substring", int.class);
        //Object a = substring1.invoke(name,3);
        //String aa = (String)a;
        //System.out.println(aa);
        System.out.println(substring1.invoke(name,3));
    }
}

如果调用的方法是静态方法。那么invoke方法传入的第一个参数永远为null

// 获取Integer.parseInt(String)方法,参数为String: 
Method m = Integer.class.getMethod("parseInt", String.class); 
// 调用该静态方法并获取结果: 
Integer n = (Integer) m.invoke(null, "23333"); 
System.out.println(n);
2.2 暴力反射成员方法
m = stuClass.getDeclaredMethod("show4", int.class);//调用制定方法(所有包括私有的),需要传入两个参数,第一个是调用的方法名称,第二个是方法的形参类型,切记是类型。
System.out.println(m);
m.setAccessible(true);//解除私有限定
Object result = m.invoke(obj, 20);//需要两个参数,一个是要调用的对象(获取有反射),一个是实参
System.out.println("返回值:" + result);

3. 获取构造方法 用Constructor类

通过Class实例获取Constructor的方法如下:

Constructor-->getConstructor(Class...):获取某个public的Constructor;
Constructor-->getDeclaredConstructor(Class...):获取某个Constructor;
Constructor-->getConstructors():获取所有public的Constructor;
Constructor-->getDeclaredConstructors():获取所有Constructor。

eg:
//先获取Person的Class对象
Class Gz = Person1.class;
//获取有参的构造函数public Person(String name, Integer age) 参数类型顺序要与构造函数内一致,且参数类型为字节码文件类型
Constructor c2 = Gz.getConstructor(String.class,Integer.class);
//使用获取到的有参构造函数创建对象
Object person2 = constructor2.newInstance("zhangsan", 22);   //获取的是有参的构造方法,就必须要指定参数
System.out.println(person2);

调用非public的Constructor时,必须首先通过setAccessible(true)设置允许访问。setAccessible(true)可能会失败。

3.1 反射执行命令
 Class.forName("java.lang.Runtime").getMethod("exec", String.class).invoke(Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime")),"calc");
拆开来:
Object a = Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime"));
Class.forName("java.lang.Runtime").getMethod("exec", String.class).invoke(a,"calc");

imgexec.invoke(a,"calc");相当于就是a.exec("calc");

而a是什么,a是通过getRuntime()方法获取到的Runtime类的实例对象。

因为Runtime类没有构造器可用,所以不能创建Runtime类的实例。

Runtime类有一个静态方法:getRuntime(),这个方法可以返回一个Runtime类的实例。这也是我们唯一获取Runtime类实例的办法。

Runtime r = Runtime.getRuntime();

imgimg

4.反射main()方法

package fanshe.main;
 
import java.lang.reflect.Method;
 
/**
 * 获取Student类的main方法、不要与当前的main方法搞混了
 */
public class Main {
	
	public static void main(String[] args) {
		try {
			//1、获取Student对象的字节码
			Class clazz = Class.forName("fanshe.main.Student");
			
			//2、获取main方法
			 Method methodMain = clazz.getMethod("main", String[].class);//第一个参数:方法名称,第二个参数:方法形参的类型,
			//3、调用main方法
			// methodMain.invoke(null, new String[]{"a","b","c"});
			 //第一个参数,对象类型,因为方法是static静态的,所以为null可以,第二个参数是String数组,这里要注意在jdk1.4时是数组,jdk1.5之后是可变参数
			 //这里拆的时候将  new String[]{"a","b","c"} 拆成3个对象。。。所以需要将它强转。
			 methodMain.invoke(null, (Object)new String[]{"a","b","c"});//方式一
			// methodMain.invoke(null, new Object[]{new String[]{"a","b","c"}});//方式二
			
		} catch (Exception e) {
			e.printStackTrace();
		}	
	}
}

5.修改被final、static关键字修饰的成员变量

5.1 修改final修饰的非String类型变量(直接反射修改即可)

声明一个final修饰的name如下. 接下来使用反射来对它进行修改. 目的也就是使name指向一个新的StringBuilder对象.

public class Pojo2 {
private final StringBuilder name = new StringBuilder("default");
public void printName() {
System.out.println(name);
}
}

咱们看看反射的威力吧, 它能修改final的字段的指向.也就是让name字段指向一个新的地址.

//获取一个对象
Pojo2 p = new Pojo2();
// 查看被修改之前的值
p.printName();
// 反射获取字段, name成员变量
Field nameField = p.getClass().getDeclaredField("name");
// 由于name成员变量是private, 所以需要进行访问权限设定
nameField.setAccessible(true);
// 使用反射进行赋值
nameField.set(p, new StringBuilder("111"));
// 打印查看被修改后的值
p.printName();

img

5.2 修改final修饰的String类型变量(使其运行后才能得值)

把上面的例子修改一下:

public class Pojo {
private final String name = "defult";
public void printName() {
System.out.println(name);
}
}

还是用上面的方法去修改:

//获取一个对象
Pojo p = new Pojo();
// 查看被修改之前的值
p.printName();
// 反射获取字段, name成员变量
Field nameField = p.getClass().getDeclaredField("name");
// 由于name成员变量是private, 所以需要进行访问权限设定
nameField.setAccessible(true);
// 使用反射进行赋值
nameField.set(p, "111");
// 打印查看被修改后的值
p.printName();

发现修改失败了:

img

因为JVM在编译时期, 就把final类型的String进行了优化, 在编译时期就会把String处理成常量, 所以 Pojo里的printName()方法, 就相当于:

public void printName() {
System.out.println("default");
}

其实name的值是赋值成功了, 只是printName()方法在JVM优化后就被写死了, 所以无论name是否被正确修改为其他的值, printName始终都会打印"default3".

final修饰的String在JVM编译时就被处理为常量, 怎么样防止这种现象呢?

其实只需要使用一些手段让final String类型的name的初始值经过一次运行才能得到, 那么就不会在编译时期就被处理为常量了

public class Pojo {
// 防止JVM编译时就把"default4"作为常量处理
private final String name = (null == null ? "default" : "");

public void printName() {
System.out.println(name);
}
}

此时,再来测试修改:

Pojo p = new Pojo();
p.printName();
Field nameField = p.getClass().getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(p, "111");
p.printName();

img

发现可以修改成功。

5.3 (重点)修改 final + static修饰符的变量
public class Pojo {
private final static String name = "default";
public void printName() {
System.out.println(name);
}
}

此时如果还是用上面的方法去修改:

Pojo p = new Pojo();
p.printName();
Field nameField = p.getClass().getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(p, "111");
p.printName();

执行之后会报出如下异常, 因为反射无法修改同时被static final修饰的变量:

img那该怎么修改呢?

思路是这样的, 先通过反射把name字段的final修饰符去掉看如下代码:

先把name字段通过反射取出来, 这个和之前的步骤都一样, 反射出来的字段类型(Field)命名为'nameField'

Field nameField = p.getClass().getDeclaredField("name");
nameField.setAccessible(true);

接下来再通过反射, 把nameField的final修饰符去掉:

Field modifiers = nameField.getClass().getDeclaredField("modifiers");
modifiers.setAccessible(true);
modifiers.setInt(nameField, nameField.getModifiers() & ~Modifier.FINAL);//去除final修饰符

然后就可以正常对name字段进行值的修改了.

nameField.set(p, new StringBuilder("111"));

最后别忘了再把final修饰符加回来:

modifiers.setInt(nameField, nameField.getModifiers() & ~Modifier.FINAL);

本例子中反射部分完整的代码如下:

Pojo p = new Pojo();
p.printName();
Field nameField = p.getClass().getDeclaredField("name");
nameField.setAccessible(true);
Field modifiers = nameField.getClass().getDeclaredField("modifiers");
modifiers.setAccessible(true);
modifiers.setInt(nameField, nameField.getModifiers() & ~Modifier.FINAL);//去除Final
nameField.set(p, "111");
modifiers.setInt(nameField, nameField.getModifiers() & ~Modifier.FINAL);//补回final
p.printName();

标签:反射,java,name,class,Class,javasec,获取,public,String
From: https://www.cnblogs.com/uf9n1x/p/17334120.html

相关文章

  • javasec(五)URLDNS反序列化分析
    这篇文章介绍URLDNS就是ysoserial中⼀个利⽤链的名字,但准确来说,这个其实不能称作“利⽤链”。因为其参数不是⼀个可以“利⽤”的命令,⽽仅为⼀个URL,其能触发的结果也不是命令执⾏,⽽是⼀次DNS请求。ysoserial打包成jar命令mvncleanpackage-DskipTests,刚刚入门所以用这条链作......
  • javasec(六)RMI
    这篇文章介绍java-RMI远程方法调用机制。RMI全称是RemoteMethodInvocation,远程⽅法调⽤。是让某个Java虚拟机上的对象调⽤另⼀个Java虚拟机中对象上的⽅法,只不过RMI是Java独有的⼀种RPC方法。看这篇之前可以先去看看RPC:https://www.bilibili.com/video/BV1zE41147Zq?from=searc......
  • GE反射内存实时通讯网络解决方案
    时通讯网络是用于需要较高实时性要求的应用领域的专用网络通讯技术,一般采用基于高速网络的共享存储器技术实现。它除了具有严格的传输确定性和可预测性外,还具有速度高、通信协议简单、宿主机负载轻、软硬件平台适应性强、可靠的传输纠错能力、支持中断信号的传输等特点。本方案选......
  • java大数据培训专业课程与教学模式的介绍
    很多人想要报名java大数据培训班,但是却不知道怎么选择java大数据培训班,也不知道学习Java都需要掌握哪些知识,java大数据没有你们想象的那么难,Java大数据培训班的选择技巧:一、java大数据培训班需要有专业课程java大数据学员选择培训班的首要条件就是:java大数据课程。Java大数据课程有......
  • java 小数转百分数字符串
     在Java中,可以使用`java.text.NumberFormat`类将小数转换为百分数字符串。具体步骤如下:1.创建一个`NumberFormat`对象:NumberFormatnf=NumberFormat.getPercentInstance();2.使用`setMaximumFractionDigits()`方法设置小数位数的最大值:nf.setMaximumFract......
  • java 用 Java 将 HashMap 转换为 TreeMap 的程序
    转载自:https://www.moonapi.com/news/24923.html HashMap 是Java1.2以来Java集合的一部分。它提供了以(键、值)对存储数据的JavaMap接口的基本实现。要访问HashMap中的值,必须知道它的键。哈希映射被称为哈希映射,因为它使用哈希技术来存储数据。Java中的树图和抽象......
  • Linux课程(大数据、JavaEE,Python通用版)
    尚硅谷Linux课程(大数据、JavaEE,Python通用版)整理:韩顺平Linux课程笔记第1章LINUX开山篇1.1本套LINUX课程的内容介绍1.2LINUX的学习方向1.2.1Linux运维工程师.1.2.2Linux嵌入式开发工程师.123在linux下做各种程序开发.1.2.4示意图.1.3LINUX的应用领域......
  • java jdk 国内下载镜像地址及安装
      javajdk国内下载镜像地址(1)TUNA镜像 https://mirrors.tuna.tsinghua.edu.cn/AdoptOpenJDK/(2)HUAWEI镜像 https://repo.huaweicloud.com/java/jdk/安装一、手动解压安装包:1、在user目录下新建java文件夹:#cd/usr/#mkdirjava#cdjava2.下载jdk1.8#wgethttp:......
  • java 通过url下载附件并压缩zip
    publicFilezipAttachFile(StringfilePathDir,List<String>urlFileList,StringmemberId)throwsException{filePathDir="/home/file";FilezipFileDir=newFile(filePathDir);if(!zipFileDir.exists()){......
  • java 删除文件和目录
    publicvoiddeleteFileAndDir(StringfilePathDir){Pathpath=Paths.get(filePathDir);try{Files.walkFileTree(path,newSimpleFileVisitor<Path>(){//先去遍历删除文件......