Java反射机制(详解易懂)

目录

 

Java反射原理

(一)   *.class文件是什么?

(二) 反射的原理

反射的应用场景

Class的三种获取方法: 


        我们还是从原理到代码,来详细理解java的反射机制。

 

Java反射原理

 

(一)   *.class文件是什么?

        众所周知, Java 是一种平台无关的语言。实现这一步的原理就是:通过javac编译器*.java代码编译成字节码(*.class) 然后通过JVM来加载

561c550765494154a62c90719bb46ecb.png

除了java 像是JRuby等语言,也是通过自己语言的编译器,将其转化成.class文件。然后通过jvm加载运行

那么大家可能会想到我们的C语言。从开始的.c文件到最后的.out文件

其实。.class文件是二进制的字节码。由JVM识别 分析 执行。

            .out 文件是二进制的机器指令。由操作系统加载运行。

所以我们只要有了JVM,无论我们是windows  unix 或者mac系统,都可以运行咱们的程序。

 

(二) 反射的原理

        咱们说了那么多字节码.class文件。跟咱们反射有什么关系呢?

        我们反射的第一步就是要获取到咱们想要的Class类。这跟咱们的.class文件就有很大关系了。

我们获取Class类一般有三种方法:

1. Class.forName(className)

2. 类名.class

3. this.getClass()

其实这三种方法还是有些区别的。Class.forName会触发类的静态初始化块的执行。其他两种不会。

反射的应用场景

使用场景一:编程工具 IDEA 或 Eclipse 等,在写代码时会有代码(属性或方法名)提示,就是因为使

用了反射;

使用场景二:很多知名的框架,如 Spring、MyBatis 等,为了让程序更优雅更简洁,也会使用到反射。

例如,Spring 可以通过配置来加载不同的类,调用不同的方法,代码如下所示:

"'"java

<bean id="person" class="com.spring.beans.Person" init-method="initPerson">

</bean>

"'"

例如,MyBatis 在 Mapper 使用外部类的 Sql 构建查询时,代码如下所示:

"'"java

@SelectProvider(type = PersonSql.class, method = "getListSql")
List<Person> getList();
class PersonSql {
public String getListSql() {
String sql = new SQL() {{
SELECT("*");
FROM("person");
}}.toString();
return sql;
}
}

"'"

使用场景三:数据库连接池,也会使用反射调用不同类型的数据库驱动,代码如下所示:

"'"java

String url = "jdbc:mysql://127.0.0.1:3306/mydb";
String username = "root";
String password = "root";
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(url, username, password);
"'"

but...我主要还是想说反射跟反序列化漏洞的关系。我们先说完反射再说。(这里就cue一下我的另一个文章)。

Class的三种获取方法:

public class Apple {

    private int price;

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public static void main(String[] args) throws Exception{
        Apple apple = new Apple();

        Class clz = Class.forName("com.text.reflect.Apple");
        Class clz1 = Apple.class;
        Class clz2 = apple.getClass();
    }
}

这里注意第三种创建Class的方法是需要一个实例的。

好啦,我们获得了我们心心念念的Class类。

通过这个Class类。我们可以获得其对应的:

Field类(表示类的属性)

Method类(表示类的方法)

Constructor类(表示类的构造)

 

怎么获得这三个类呢?

通过Class实例的 .getField(String name)   .getFields()

                             .getDeclaredField(String name)  .getDeclaredFields()方法来获得

 

        这里添加了Declared的是可以获取包括私有在内所有属性(方法,构造),而没有Declared的方法是只能获取公有的。在使用Declared的方法后面要加一行.setAccessible(true);来设置权限。

比如咱们Apple类中有一个属性是 private int price;

我们就可以通过          Field ff = clz.getDeclaredField("price");

                                            ff.setAccessible(true);   来获取这个私有属性。

 

把Field改成Method和Constructor 就是其他两类的获取方法。值得一提的是:这两类

getMethod(String name,Class...<?>parameterTypes)

getConstructor(Class...<?>parameterTypes) 

传入的参数有些不同,不过我们通过下面的这个例子能很好理解。

public class Apple {

    private int price;

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public static void main(String[] args) throws Exception{
        //正常调用
        Apple apple = new Apple();
        apple.setPrice(5);
        System.out.println("Apple Price:" + apple.getPrice());
 
        //使用反射调用
        Class clz = Class.forName("com.text.reflect.Apple");
        //Class clz1 = Apple.class;
        //Class clz2 = apple.getClass();
        
        //获取名叫setPrice的方法,并且传入的参数类型为int
        Method setPriceMethod = clz.getMethod("setPrice", int.class);

        //获取无参的构造方法
        Constructor appleConstructor = clz.getConstructor();
        //通过这个构造方法创建一个appleObj对象
        Object appleObj = appleConstructor.newInstance();

        //通过一个invoke传入 对象和参数 给这个方法   
        setPriceMethod.invoke(appleObj, 14);
        Method getPriceMethod = clz.getMethod("getPrice");
        System.out.println("Apple Price:" + getPriceMethod.invoke(appleObj));
    }
}

咱们看完这个代码之后。我来梳理一下。

 

1.我们正常创建一个Apple对象。就是Apple apple = new Apple();

而我们通过反射获取一个对象。我们可以通过:

        Apple apple = (Apple)clz.getConstructor().newInstance();

 或者Apple apple = (Apple)clz.newInstance();

我们一般还是用Constructor的newInstance方法来构造,毕竟Constructor就是为了构造嘛。Class 对象则只能使用默认的无参数构造方法。而Constructor有参无参都可以,而且从JDK9开始就弃用Class直接构造了。因为不是很灵活。

值得一提的是,newInstance的效率不如new,也很好理解,比较多经过一层反射嘛,需要运行时动态地获取和调用方法或构造函数。

 

2.我们正常调用 apple.setprice(5);

而我们反射就需要 先通过

 Method setPriceMethod = clz.getMethod("setPrice", int.class); 获取名叫setPrice的方法

setPriceMethod.invoke(appleObj, 14);

再通过Invoke将我们需要调用setPrice方法的appleObj对象,以及setPrice所需的参数传入进去。

这里Invoke就是Method中的方法 专门实现方法功能的。

 

那么,咱们绕那么大圈,意义是什么呢?

除了实现依赖注入(DI)和控制反转(IoC)。许多依赖注入框架(如 Spring)使用反射来动态地将依赖注入到对象中。实现调试工具和测试框架等许多功能以外。

其实我们发现,我们正向调用这些方法,其实我们是知道我们这个Apple类有什么方法。

而我们通过反射,只需要一个class字节码的名字,就可以知道里面所有的属性和方法。

 // 获取所有声明的方法
        Method[] declaredMethods = clz.getDeclaredMethods();
        for (Method method : declaredMethods) {
            System.out.println("Declared Method: " + method.getName());
        }

在我们反序列化漏洞中。我们可以通过反射定制需要的对象

通过invoke调用除了同名函数以外的函数 (invoke可以通过字符串来调用 灵活)
通过Class类创建对象,引入不能序列化的类(Class类可以序列化,可以通过这个来操作不能实例化的类)
eg:Runtime 不能实例化
Runtime.class 中的 getruntime可以作为一个参数传入一个类中。 

还有比如动态代理InvocationHandler中的invoke,有函数调用的时候自动执行。就类似于readObject,在反序列化的时候自动执行。

这篇文字主要还是告诉大家反射的基础知识。有错误希望大家指正。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值