• JAVA 反射机制


    前言:记录的文章全是个人的小理解加上Javasec中的大部分内容,这是做学习笔记

    概念

    反射的作用:实现任何类的成员方法(Methods)、成员变量(Fields)、构造方法(Constructors)等信息,还可以动态创建Java类实例(Instance)、调用任意的类方法、修改任意的类成员变量值等

    比如之前笔记中的Unsafe,来获取这个对象的时候就用了两种方法,就是通过构造方法和获取属性这两种方法来获取该对象的实例

    首先要知道的通过java反射获取到的其实都是对应的类型的Class实例对象,之前也讲到了类的初始化需要ClassLoader对象,这个返回的也是一个对应类型的Class实例对象!

    个人理解:java反射应该就是在ClassLoader中加载实现的吧。。。。

    获取Class对象

    之前讲了一种方法了,就是通过ClassLoader类中的loadClass方法来进行加载,然后返回的一个Class实例对象,比如classLoader.loadClass("com.anbai.sec.classloader.TestHelloWorld");

    还有两种其他的方法:

    类名.class // 如:com.anbai.sec.classloader.TestHelloWorld.class // 触发类中的static静态代码块
    Class.forName("com.anbai.sec.classloader.TestHelloWorld") // 小提醒,之前写到说这个会触发类中的static静态代码块
    

    如果是获取Class数组的话方式就有点不一样,自己也第一次学,这里就是关于Java类型的描述符方式(泛型自己也不太懂,有时候得学习)

    Class<?> doubleArray = Class.forName("[D");//相当于double[].class
    Class<?> cStringArray = Class.forName("[[Ljava.lang.String;");// 相当于String[][].class
    

    那么比如要加载一个Runtime类的实例就有三种方法:

            String className = "java.lang.Runtime";
            Class  runtimeClass1 = Class.forName(className);
            Class  runtimeClass2 = java.lang.Runtime.class;
            Class  runtimeClass3 = ClassLoader.getSystemClassLoader().loadClass(className);
    

    小知识点: 反射调用内部类的时候需要使用$来代替.,如com.anbai.Test类有一个叫做Hello的内部类,那么调用的时候就应该将类名写成:com.anbai.Test$Hello,然后到时候获取的时候就比如Class runtimeClass1 =Class.forName("com.anbai.Test$Hello")

    继续讲如果通过反射来实现Runtime类的exec方法的调用,如下代码即可实现:

    public class Runcmd {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
            String className = "java.lang.Runtime";
            Class  runtimeClass1 = Class.forName(className);
            Class  runtimeClass2 = java.lang.Runtime.class;
            Class  runtimeClass3 = ClassLoader.getSystemClassLoader().loadClass(className);
    
            Constructor runtimeConstructor =  runtimeClass1.getDeclaredConstructor();
            runtimeConstructor.setAccessible(true);
            Runtime runtime = (Runtime) runtimeConstructor.newInstance();
            Method ExecMethod = runtimeClass1.getMethod("exec", String.class);
            String cmd = "whoami";
            Process p = (Process) ExecMethod.invoke(runtime, cmd);
    
            InputStream in =  p.getInputStream();
            ByteArrayOutputStream baos = new ByteArrayOutputStream(); //获取字节输出流
            byte[]                b    = new byte[1024];
            int                   a    = -1;
    
            // 读取命令执行结果
            while ((a = in.read(b)) != -1) { // in读取出来的数据都放到a(这个a是读取到的长度大小,类型为int)中,在写入到baos中
                baos.write(b, 0, a); // write方法,流没有自己去学习过,个人理解应该是用一个输出流来接受,写进去的时候就是写进b数组中,
                // 偏移 0就是从开头开始,每次写的长度就是a
            }
    
            // 输出命令执行结果
            System.out.println(baos.toString());
        }
    }
    

    反射获取一个类中的方法

    这里只把这个挑出来讲,因为其实原理都是类似的,并且只有在学习这个的时候自己也遇到了点坑,所以也把它拿出来

    自己写一个Person类,如下:

    public class Person {
        private static String name;
        public Person(String name){
            this.name = name;
        }
        public String getName() {
            return name;
        }
    
        public void setName(String aName){
            name = aName;
        }
    
        public static void setStaticName(String aName){
            name = aName;
        }
    
        public static String getStatiName(){
            return name;
    
        }
    }
    

    这里要实现获取getName方法,需要注意的是自己定义了1个static name的属性,2个返回name属性的方法,但是一个是静态一个不是静态,还有2个是设置name的方法,这个方法需要传一个参数,也是一个静态一个不是静态

    这里开始反射方法的学习,如下代码显示,可以看到反射了两个方法,一个方法是静态的一个不是静态的,并且调用方法的时候第一个参数一个是Person的实例,一个为null,这里就说了自己的一个坑!

    总结如下:

    1、如果底层方法是静态的,则指定的obj参数将被忽略。 它可能为null

    2、如果底层方法所需的形式参数的数量为0,则提供的args数组的长度为0或为空。

    3、如果底层方法是一个实例方法,它将使用动态方法查找来调用(根据你的参数判断来调用不同的方法)

    4、如果底层方法是静态的,则如果尚未初始化该方法,那么声明该方法的类将被初始化

    public class PeronTest {
    
        public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    
            Class Pclass = Person.class;
    
            Constructor pConstruct = Pclass.getDeclaredConstructor();
    
            Object person = (Person)pConstruct.newInstance();
    
            Method setNameMethod = Pclass.getMethod("setName", String.class);
            setNameMethod.invoke(person, "not static setName");
            System.out.println(Person.name);
            
            Method setStaticNameMethod = Pclass.getMethod("setStaticName", String.class);
            setStaticNameMethod.invoke(null, "static setName");
            System.out.println(Person.name);
        }
    }
    

    来一个javasec的总结:

    Java反射机制是Java动态性中最为重要的体现,利用反射机制我们可以轻松的实现Java类的动态调用。Java的大部分框架都是采用了反射机制来实现的(如:Spring MVC、ORM框架等),Java反射在编写漏洞利用代码、代码审计、绕过RASP方法限制等中起到了至关重要的作用。

  • 相关阅读:
    nginx配置
    day5 业务开发中较有用代码
    day4 Vue基础
    npm vue的一些命令
    day3 ES6基础
    python_矩阵的加法和乘法计算(包括矩阵的动态输入,纯列表实现不引入其他模块)
    python_利用元组实现剪刀石头布
    python_整型与IP地址的转换
    python_判断标识符的合法性
    python_生成随机数与列表排序
  • 原文地址:https://www.cnblogs.com/zpchcbd/p/13368170.html
Copyright © 2020-2023  润新知