Java 反射机制 学习笔记

前言

上次学了Java RMI相关的知识后,感觉收获挺大的,今天跟着Epicccal师傅继续学习Java 反射机制,这篇文章算是读完师傅文章做的一些记录吧

1.什么是 Java Reflection

可以用一句话来说明什么是 Java 反射机制

Java 反射机制允许运行中的Java程序获取自身的信息, 操作类和对象的内部属性.

当然, 这样说太简明了, 很多没了解过 Java 的同学( 比如我 )可能会一头雾水 . 往细一点说 , 可以给出如下定义 :

Java 反射机制是指在程序运行时 , 对于任何一个类 , 都能知道这个类的所有属性和方法 , 对于任何一个实例对象 , 都能调用该对象的任何一个属性和方法 .

Java中这种 " 动态获取信息 " 和 " 动态调用属性方法 " 的机制被称为 Java 反射机制.

实例对象可以通过反射机制获取它的类 , 类可以通过反射机制获取它的所有方法和属性 . 获取的属性可以设值 , 获取的方法可以调用 .

Java 反射机制的功能可分为如下几点 :

在程序运行时查找一个对象所属的类 .
在程序运行时查找任意一个类的成员变量和方法 .
在程序运行时构造任意一个类的对象 .
在程序运行时调用任意一个对象的方法 .

2.反射的主要用途

反射最重要的用途就是开发各种通用框架。很多框架(比如 Spring)都是配置化的(比如通过 XML 文件配置 Bean),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射,运行时动态加载需要加载的对象。

举一个例子,在运用 Struts 2 框架的开发中我们一般会在 struts.xml 里去配置 Action,比如:

<action name="login"
               class="org.ScZyhSoft.test.action.SimpleLoginAction"
               method="execute">
           <result>/shop/shop-index.jsp</result>
           <result name="error">login.jsp</result>
       </action>

配置文件与 Action 建立了一种映射关系,当 View 层发出请求时,请求会被 StrutsPrepareAndExecuteFilter 拦截,然后 StrutsPrepareAndExecuteFilter 会去动态地创建 Action 实例。比如我们请求 login.action,那么 StrutsPrepareAndExecuteFilter就会去解析struts.xml文件,检索action中name为login的Action,并根据class属性创建SimpleLoginAction实例,并用invoke方法来调用execute方法,这个过程离不开反射。

对与框架开发人员来说,反射虽小但作用非常大,它是各种容器实现的核心。而对于一般的开发者来说,不深入框架开发则用反射用的就会少一点,不过了解一下框架的底层机制有助于丰富自己的编程思想,也是很有益的。

3.反射的基本运用

1.查找一个对象所属的类

如何获取一个类( java.lang.Class )呢? 总的而言有三种方法 .

obj.getClass()
Class.forName(className)
className.class

具体的使用方法如下所示
在这里插入图片描述
这里再强调一下 forName 这个方法,下面是H0t-A1r-B4llo0n师傅文章的原话
在这里插入图片描述
这里提到了这个函数可以选择是否会进行类的初始化,这里我们需要注意一点,执行了类的初始化也就会相应的执行一些类的静态代码块中的内容
在这里插入图片描述
通过上图可以看到,类的初始化时执行了静态代码块中的内容,构造函数的内容并没有执行,构造函数的内容是在类对象的实例化时才执行的,这得分清

那也就是说 , 如果我们能控制一个类 , 那么就可以通过在类中添加包含恶意代码的静态代码块 . 当类初始化时 , 默认会自动执行恶意代码. 如下所示

设有test类

public class test {
    public test(String name) throws ReflectiveOperationException
    {
        Class.forName(name);
    }

    public static void main(String[] args) throws ReflectiveOperationException
    {
        test class1 =new test("lmonstergg");
        //System.out.println("通过obj.getClass()获取类名"+class1.getClass());  //当我们知道实例对象名称时
        //System.out.println("通过Class.forName(className)获取类名"+Class.forName("test")); //当我们知道了某个类的名称时
        //System.out.println("通过className.class获取类名"+test.class); //当我们已经加载了某个类时
    }
}

以及构造恶意的 lmonstergg 类

import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;

public class lmonstergg {
    static
    {
        try {
            Process p = Runtime.getRuntime().exec("whoami"); //执行系统命令
            InputStream is=p.getInputStream();  //获取进程p的标准输出流作为输入字节流
            InputStreamReader isr=new InputStreamReader(is);  //将字节流转换为字符流
            BufferedReader br=new BufferedReader(isr);  //为字符流提供缓冲区,便于读取整块数据
            String line=null;
            while((line=br.readLine())!=null)
            {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在该类静态代码块中 , 通过 java.lang.Runtime.getRuntime().exec() 执行系统命令 , 并将返回字节流转换为字符流 , 存入缓冲区后逐行读取并输出 .

当调用 test 类时 , 会自动执行恶意代码 .
在这里插入图片描述

2.查找一个类的方法

如何获取某一个类的所有方法呢? 总的来说有三种方法 .

className.getMethod(functionName , [parameterTypes.class])
className.getMethods()
className.getDeclaredMethods()

具体使用方法如下图所示 , 这里参考了 sczyh30 师傅的 深入解析Java反射(1) , 在原有代码的基础上进行了修改

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class test {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Class<?> c = methodClass.class;
        Object object = c.newInstance();
        Method[] methods = c.getMethods();
        Method[] declaredMethods = c.getDeclaredMethods();
        //获取methodClass类的add方法
        Method method = c.getMethod("add", int.class, int.class);
        //getMethods()方法获取的所有方法
        System.out.println("getMethods获取的方法:");
        for(Method m:methods)
            System.out.println(m);
        //getDeclaredMethods()方法获取的所有方法
        System.out.println("getDeclaredMethods获取的方法:");
        for(Method m:declaredMethods)
            System.out.println(m);
    }
}
class methodClass {
    public final int fuck = 3;
    public int add(int a,int b) {
        return a+b;
    }
    public int sub(int a,int b) {
        return a+b;
    }
}

在这里插入图片描述

getMethod() : 返回类中一个特定的方法 . 其中第一个参数为方法名称 , 后面的参数为方法的参数对应 Class 的对象 .

getMethods() : 返回某个类的所有公用(public)方法 , 包括其继承类的公用方法 .

getDeclaredMethods() : 返回某个类或接口声明的所有方法 , 包括公共、保护、默认(包)访问和私有方法 , 但不包括其继承类的方法 .

补充一些内容

Class<?> : 定义了一个泛型类 , 其中 <?> 代表不确定类的类型 , 具体细节可以参考 程序鱼师傅 JAVA泛型通配符T,E,K,V区别,T以及Class\<T>,Class\<?> 的区别 一文

for(Method m:methods) 循环获取methods集合中的内容 , 把每一项赋值给变量m .

输出信息中的美元符号( $ )代表内部类 .

3.构造任意一个类的对象

上文提到了可以通过三种方式来获取类 , 那么如果获取一个实例对象呢 ?

通过 className.newInstance() 来构建一个实例对象.

我们都知道在类实例化时会调用构造函数 , 而构造函数又分为 " 有参构造函数 " 和 " 无参构造函数 " . 然而 className.newInstance() 没有参数 , 只能调用无参构造函数 . 如果我们想要调用有参构造函数 , 就必须依赖于 Class 类的 getConstructor() 方法 .

通过 Class 类的 getConstructor() 方法 , 可以获取 Constructor 类的一个实例 , Constructor 类也存在一个 newInstance() 方法 , 不过该方法可以携带参数 . 用该方法来创建实例对象可以调用有参构造函数 .

例子如下

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class test {

    public test()
    {
        System.out.println("调用无参构造函数");
    }public test(String name)
    {
        System.out.println("调用有参构造函数"+name);
    }
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Class<?> cls=Class.forName("test");
        test obj1= (test) cls.newInstance();
        test obj2=(test) cls.getConstructor(String.class).newInstance("调用有参构造函数");
    }
}

在这里插入图片描述
因此 , 我们可以通过 newInstance() 方法来构造任何一个类的对象 . 并且可以选择是调用其无参构造方法 , 还是有参的构造方法 ,总结用法如下

className.newInstance()
className.getConstructor( parameterType ).newInstance( parameterName )

4.调用任意一个实例对象的方法

一般来说 , 可以通过 objectName.functionName() 这种格式来调用实例方法

但是在很多情况下 , 你并不知道类名, 也就无法 new 出实例对象 , 更别提调用实例对象的方法了 . 当遇到这种情况时 , 就需要使用 Java 反射来调用实例对象的方法了 .

仔细看过上文的师傅应该有一些思路了 .

不知道类怎么办 ?
我们可以通过 obj.getClass() , Class.forName(className) , className.class 来获取类.

不知道类有哪些方法怎么办 ?
我们可以通过 className.getMethod(functionName , [parameterTypes.class]) , className.getMethods() , className.getDeclaredMethods() 来获取类的方法.

不能 new 出实例对象怎么办 ?
我们可以通过 className.newInstance() , className.getConstructor().newInstance() 来构造实例对象 .

那如何调用实例对象的方法呢 ?
通过 invoke() 方法来调用任何一个实例对象的方法 !

在这里插入图片描述
这是invoke() 函数的定义

下面来个例子试一试
在这里插入图片描述

Method.invoke(obj , args[])

如上文所说的 , 通过Java反射机制来获取类 , 获取类的方法 , 构造实力对象 , 最终调用实例方法 .

这里再额外说一点

如果要调用的方法是静态的 , 则忽略 obj 参数 .
这个点其实比较好理解 , 我们知道Java中调用静态方法是无需创建实例对象的** , 所以这里可以省略 obj 参数 .

如果要调用的方法的形参个数为 " 0 " , 那么 args[] 数组的长度可以为 " 0 " 或者 " null " .
这个点其实也没啥说的 , args[] 数组本就是要调用方法的参数 , 既然目标方法没有参数 , 这里自然也就不用写 .

参考文章
https://www.guildhab.top/2020/04/java-rmi-%e5%88%a9%e7%94%a82-java-%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96%e6%bc%8f%e6%b4%9e/

https://www.sczyh30.com/posts/Java/java-reflection-1/#4%E3%80%81%E8%8E%B7%E5%8F%96%E6%96%B9%E6%B3%95

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值