Java Reflection(从浅入深理解反射)

本节的代码链接:reflection

1. 反射的由来

反射机制允许程序执行期借助于Reflection API取得任何类的内部信息,如成员变量、构造器、成员方法等,并能操作对象的属性及方法,在设计模式和框架底层都会用到。

1.1 引入需求

编写框架的时候有一个常见的需求:通过外部配置文件,在不修改源码的情况下,来扩展功能。

有这么一个类:

public class Obj {
    private String name = "mimi";

    public Obj() {
    }

    public Obj(String name) {
        this.name = name;
    }

    public void sayHi() {
        System.out.println(this.name + " say: hi!");
    }
}

试着思考:在了解反射之前,你是否可以使用现有技术,通过一个指定了某个类的全限定类名和方法.properties配置文件,实例出对象并且调用其中的方法呢?

1.2 尝试通过I/O解决

我们知道,通过全限定类名确实可以实例化一个对象

Obj obj = new com.chenshu.Obj();

那我就先通过I/O把Obj类的全限定类名和方法拿出来呗,使用Properties类,可以轻松的读写文件:

public class Main {
    public static void main(String[] args) throws IOException {
        //1.读写文件
        Properties properties = new Properties();
        properties.load(new FileInputStream("src/static/re.properties"));
        String classPathName = properties.getProperty("classPath");
        String methodName = properties.getProperty("method");
        System.out.println("全限定类名:" + classPathName);
        System.out.println("方法名:" + methodName);
    }
}

拿到全限定类名和方法名后,我们开始犯难了,拿到了Obj类的全限定类名后,也不可能通过这个 String 创建对象啊,这时就要用到反射机制了。

2. 反射机制快速入门

2.1 加载类

通过一个类名为Class的类来获取到Obj类,可以把下面的clazz对象看成Obj类的图纸:

Class clazz = Class.forName(classPathName);

2.2 获得实例

通过clazz得到 com.chenshu.Obj 的对象实例:

Object o = clazz.newInstance();

2.3 获得方法

java.lang.reflect 包中有一个 Method 类可以从 clazz 中接收方法,也就是把方法视为一个对象,并通过方法.invoke(对象)来实现调用:

Method method = clazz.getMethod(methodName);
method.invoke(o);

3. 反射原理

加载完类之后,在中就产生了一个Class类型的对象 (一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个对象就像一个镜子,透过这个镜子看到类的结构,所以,形象的称之为反射。

为什么能够通过这样一个Class对象来进行创建实例,操作属性,操作方法呢?

以前面提到的Obj类为例子:

Obj.java这样一个java代码,会在编译时期被转换为Obj.class的字节码文件

Untitled Diagram.drawio.png

当Java程序在“运行阶段”首次使用某个类时,这个类的字节码会被加载到JVM中。这个阶段称为“加载阶段”。加载不仅仅是将类的字节码读入内存,还会创建一个代表该类的Class对象。这个Class对象可以看作是一个可以操作的数据结构。

直到类加载完了才生成了这么一个对象

Untitled Diagram.drawio-6.png

由于Obj知道自身是属于哪一个对象,因此能够getClass()方法找到它的Class对象,并且能通过Class对象提供的一系列API来修改这个数据结构,进而进行这些操作:创建对象、调用方法、操作属性等

4. 反射的主要类

//加载类
Class clazz = Class.forName(classPathName);
Object o = clazz.newInstance();

4.1 java.lang.reflect.Field

代表类的成员变量,字段 (Field) 对象表示某个类的成员变量

Field nameField = clazz.getField("age");

通过get获取属性值,也可以通过set修改属性值:

nameField.set(o, 30);
System.out.println("修改age后的值为:" + nameField.get(o));

运行结果:

修改age后的值为:30

4.2 java.lang.reflect.Method

代表类的方法,方法(Method)对象表示某个类的方法

Method method = clazz.getMethod(methodName);
//通过invoke调用方法
method.invoke(o);

前面已经使用过,这里就不多说了。

4.3 java.lang.reflect.Constructor

代表类的构造方法,构造函数(Constructor)对象表示构造器

Constructor constructor1 = clazz.getConstructor();
Constructor constructor2 = clazz.getConstructor(String.class);

在程序中可以通过构造器来构造对象:

//使用无参构造器
Object object = constructor1.newInstance();
//使用带参构造器
Object object1 = constructor2.newInstance("test");
System.out.println(object);
System.out.println(object1);

运行结果:

Obj{name='mimi', age=20}
Obj{name='test', age=20}

5. java.lang.Class

5.1 概述

  1. Class也是类,因此也继承Object类
  2. Class类对象不是new出来的,而是当一个类第一次被使用时,ClassLoader(类加载器)调用loadClass()方法创建出来的
  3. 对于某个类的字节码文件,在内存中只有一份,因为类只加载一次
  4. 每个类的实例都会记得自己是由哪个 Class 实例所生成,因此可以通过getClass()获得
  5. 通过Class可以完整地得到一个类的完整结构,并通过一系列API来操作这个类
  6. Class对象是存放在堆的类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括 方法代码,变量名,方法名,访问权限等等)

5.2 获取Class对象的六种方式

  1. Class.forName() 应用场景:通过配置文件获取
String classPathName = "com.chenshu.Obj";
Class cls1 = Class.forName(classPathName);
  1. 类名.class 应用场景:用于参数传递
Class cls2 = Obj.class;
  1. 对象.getClass() 应用场景:有对象实例
Obj obj = new Obj();
Class cls3 = obj.getClass();
  1. classLoader.loudClass()
ClassLoader classLoader = obj.getClass().getClassLoader();
Class cls4 = classLoader.loadClass(classPathName);
  1. 基本数据类型.class
Class<Integer> integerClass = int.class;
Class<Character> characterClass = char.class;
  1. 包装类型.TYPE
Class<Integer> type = Integer.TYPE;
Class<Character> type1 = Character.TYPE;

5.3 常用API及使用

定义一个下面的类用于测试:

class User {
    //四种修饰符修饰的属性
    public String name;
    protected int age;
    String gender;
    private double salary;
    
    //无参公开构造器
    public User() {
        
    }
    //有参公开构造器
    public User(String name) {
        this.name = name;
    }
    //有参私有构造器
    private User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    //四种修饰符的方法
    public void m1() {

    }
    protected void m2() {

    }
    void m3() {

    }
    private void m4() {

    }
}

先获取User的Class对象,后面的API都通过这个Class对象来使用:

Class<?> userCls = Class.forName("com.chenshu.test.User");

  1. getName()获取全类名
System.out.println(userCls.getName());

运行结果:

com.chenshu.test.User

  1. getSimpleName()获取简单类名
System.out.println(userCls.getSimpleName());

运行结果:

User

  1. getFields()获取本类和父类的public属性
Field[] fields = userCls.getFields();
for (Field field : fields) {
    System.out.println("本类及父类的public属性:" + field.getName());
}

运行结果:

本类及父类的public属性:name

  1. getDeclaredFields()获取所有本类的属性
Field[] declareFields = userCls.getDeclaredFields();
for (Field field : declareFields) {
    System.out.println("本类的所有属性:" + field.getName());
}

运行结果:

本类的所有属性:name
本类的所有属性:age
本类的所有属性:gender
本类的所有属性:salary

  1. getMethods()获取本类及父类的public方法
Method[] methods = userCls.getMethods();
for (Method method : methods) {
    System.out.println("本类及父类的public方法:" + method.getName());
}

运行结果:

本类及父类的public方法:m1
本类及父类的public方法:wait
本类及父类的public方法:wait
本类及父类的public方法:wait
本类及父类的public方法:equals
本类及父类的public方法:toString
本类及父类的public方法:hashCode
本类及父类的public方法:getClass
本类及父类的public方法:notify
本类及父类的public方法:notifyAll

  1. getDeclaredMethods() 获取本类的所有方法
Method[] declareMethods = userCls.getDeclaredMethods();
for (Method method : declareMethods) {
    System.out.println("本类的所有方法:" + method.getName());
}

运行结果:

本类的所有方法:m1
本类的所有方法:m2
本类的所有方法:m3
本类的所有方法:m4

  1. getConstructors() 获取本类的public构造方法(没有父类)
Constructor<?>[] constructors = userCls.getConstructors();
for (Constructor constructor : constructors) {
    System.out.println("本类的public构造方法:" + constructor.getName());
}

运行结果:

本类的public构造方法:com.chenshu.test.User
本类的public构造方法:com.chenshu.test.User

  1. getDeclaredConstructors() 获取本类所有构造方法
Constructor<?>[] declaredConstructors = userCls.getDeclaredConstructors();
for (Constructor constructor : declaredConstructors) {
    System.out.println("本类的所有构造方法:" + constructor.getName());
}

运行结果:

本类的所有构造方法:com.chenshu.test.User
本类的所有构造方法:com.chenshu.test.User
本类的所有构造方法:com.chenshu.test.User

  1. getPackage() 获取该类所属包
System.out.println(userCls.getPackage());

运行结果:

package com.chenshu.test

  1. getSuperclass() 获取该类的父类

新建一个People类,并让User继承People:

class People {

}
class User extends People{

测试代码:

Class<?> superclass = userCls.getSuperclass();
System.out.println(superclass);

运行结果:

class com.chenshu.test.People

  1. getInterfaces() 获取该类的所有接口

先编写两个接口,并让User类implement它们:

interface IA {
    
}
interface IB{
    
}
class User extends People implements IA, IB{

测试代码:

Class<?>[] interfaces = userCls.getInterfaces();
for(Class anInterface : interfaces) {
    System.out.println("接口信息:" + anInterface);
}

运行结果:

接口信息:interface com.chenshu.test.IA
接口信息:interface com.chenshu.test.IB

  1. getAnnotations() 获取该类的所有注解

随便写一个注解,用于测试:

@Resource
class User extends People implements IA, IB{

测试代码:

Annotation[] annotations = userCls.getAnnotations();
for (Annotation annotation : annotations) {
    System.out.println("注解信息:" + annotation);
}

运行结果:

注解信息:@javax.annotation.Resource(shareable=true, lookup=, name=, description=, authenticationType=CONTAINER, type=class java.lang.Object, mappedName=)

6. 反射暴力破解

前面提到的所有API,都只能获取到 公有的字段、方法、构造器,想要通过反射来获得私有的字段、方法、构造器就必须得用到暴力破解

暴力破解指的就是使用setAccessible()这个方法,在这个方法中可以传truefalse两种参数,分别代表禁用或启用安全检查,如果禁用安全检查,就可以不受修饰符的影响,随意实例化、调用方法、修改和获取属性,接下来我们来学习如何利用反射机制暴力破解。

创建一个User类用于测试:

class User {
    private int age;
    private String name;

    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + ''' +
                '}';
    }

    private User(int age, String name) {
        this.age = age;
        this.name = name;
    }

    private void userMethod() {
        System.out.println("this is a private method..");
    }
}

6.1 暴力创建实例

上面的User类只有一个private修饰的构造方法,通过new的方式无法创建实例,但是如果通过反射机制可以暴力破解。

测试代码:

Class userClazz = Class.forName("com.chenshu.vialate.User");
//获取私有构造器
Constructor declaredConstructor = userClazz.getDeclaredConstructor(int.class, String.class);
//暴破,使用反射可以访问private构造器
declaredConstructor.setAccessible(true);
Object user = declaredConstructor.newInstance(20, "zhangsan");
//打印user
System.out.println(user);

运行结果:

User{age=20, name='zhangsan'}

6.2 暴力破解属性

同样的,没有set方法是无法修改private修饰的属性的,但是通过反射,可以暴力破解。

测试代码:

Field age  =userClazz.getDeclaredField("age");
age.setAccessible(true);
age.set(user, 99);
System.out.println(user);

我们可以发现age字段成功被修改:

User{age=99, name='zhangsan'}

6.3 暴力使用方法

最后,在出了User类这个作用域后,无法使用private修饰的方法,但是通过反射机制,一切都是浮云。

测试代码:

Method method = userClazz.getDeclaredMethod("userMethod");
method.setAccessible(true);
method.invoke(user);

运行结果:

this is a private method..
  • 12
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

干脆面la

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值