Java进阶-反射机制

一、概念

Java反射是Java被视为动态(或准动态)语言的一个关键性质。指在程序运行状态中,可以构造任意一个类,可以调用获取任意一个类的方法、变量、构造器等。这种动态获取信息以及动态调用对象方法的功能称为 java 语言的反射机制。反射被视为动态语言的关键,框架设计的灵魂。《百度百科》

以上的解释不理解没关系,下面来结合图分析程序执行的流程:
首先javac编译源代码之后,通过类加载器进内存生成字节码文件对象。一个类的字节码文件大概可以拆分为:包信息、成员变量、构造方法、成员方法等等,个个组成部分映射成一个个对象。图中就是类的正常加载过程,反射的原理就在于如何去操作这些对象

Java代码在计算机中大致分为三个阶段:Source、Class、Runtime

image.png

Person person = new Person(); 执行!
JVM 启动后,javac 会把 Person.java 编译成一个 Person.class 文件,通过 ClassLoader 加载进内存的方法区中,类对象是用来描述字节码文件对象的,每个类只有一个class对象且只会加载一次,作为方法区类的数据结构的接口。然后创建Person的类实例到堆(Heap)内存中。注意:Jvm 在创建对象前,会先检查类是否加载,寻找类对应的class对象,加载完成之后,会给对象分配内存。

了解大致过程之后,发现Person是new出来写死给Jvm执行的,假设 当我们的程序在运行时,需要动态的加载一些类,这些类可能运行之前不确定是否需要加载到Jvm中,而是在运行时根据需要才去加载 。这就需要用到反射机制。

举个栗子:目前主流开发所用的 spring 框架,在配置各种各样的 bean 时,是以配置文件的形式配置的,spring 容器会根据配置的信息去动态加载类。 Spring的工作原理就是让一个对象的创建不用new就可以自动的生产,在运行时与xml Spring的配置文件来动态的创建对象和调用对象,而不需要通过代码来关联。

相关类
类名 释义
Class 类对象,在运行时java类数据结构的接口
Field 类属性(成员变量)
Method 类方法
Constructor 类构造方法

二、Class类

Class对象的三种获取方式

public class Reflection {
 /**
 * 1.Source阶段,通过将class字节码文件加载进内存,返回 class 对象
 * 2.class对象阶段,通过类名的属性 class 来获取
 * 3.Runtime阶段,通过Object.getClass()方法来获取
 *
 * @param args anything
 * @throws ClassNotFoundException
 */
 public static void main(String[] args) throws ClassNotFoundException {
 // 1.多用于配置文件,将类名配置在文件中,读取文件加载类
 Class aClass = Class.forName("com.angst.student.Reflection");
 // 2\. 一般用于参数传递,当做参数使用
 Class aClass1 = Reflection.class;
 // 3\. 多用于对象获取字节码
 Class aClass2 = new Reflection().getClass();

 }
}
Class
方法 释义
getClassLoader() 获得类的加载器
getClasses() 返回一个数组,数组中包含该类中所有公共类和接口类的对象
getDeclaredClasses() 返回一个数组,数组中包含该类中所有类和接口类的对象
getName() 获得类的完整路径名字
getPackage() 获得类的包
getSimpleName() 获得类的名字
getSuperclass() 获得当前类继承的父类的名字
getInterfaces() 获得当前类实现的类或是接口
asSubclass() 把传递的类的对象转换成代表其子类的对象
newInstance() 创建一个空参的对象(无参构造使用)
/**
 *  通过类完整路径获取 Class 对象
 * @param packagePath 包路径
 * @return  class
 * @throws ClassNotFoundException
 */
 public static Class<?> getClass(String packagePath) throws ClassNotFoundException {
 return Class.forName(packagePath);
 }

 /**
 *  通过包名+类名返回 class 对象
 * @param packagePath 包路径
 * @param obj  类名
 * @return class
 * @throws ClassNotFoundException
 */
 public static Class<?> getClass(String packagePath, String obj) throws ClassNotFoundException {
 if (obj.indexOf(".") == 0) { obj = packagePath + obj; }
 return getClass(obj);
 }
Constructor
方法 释义
getConstructor() 获得该类中与参数类型匹配的public构造方法
getConstructors() 获得该类的所有public构造方法
getDeclaredConstructor() 获得该类中与参数类型匹配的构造方法
getDeclaredConstructors() 获得该类所有构造方法
newInstance() 根据传递的参数创建类的对象(new Object)
/**
 * 获取构造器
 * @param clazz  类对象
 * @param parameterTypes  参数类型
 * @return Constructor
 * @throws NoSuchMethodException
 */
 public static Constructor getConstructor(Class clazz, Class... parameterTypes) throws NoSuchMethodException {
 return clazz.getDeclaredConstructor(parameterTypes);
 }
Field
方法 释义
getField() 获得某个public属性对象
getFields() 获得所有public属性对象
getDeclaredField() 获得某个属性对象(包括私有)
getDeclaredFields() 获得所有属性对象(包括私有)
setAccessible() 设置为true,忽略访问权限修复符,操作 private 类属性(暴力反射不安全)
/**
 * 设置某个字段的值
 * @param clazz 类对象
 * @param fieldName  属性字段名称
 * @param value 设置值
 * @throws Exception
 */
 public static void setField(Class<Object> clazz, String fieldName, String value) throws Exception {
 Object obj = clazz.newInstance();
 Field declaredField = clazz.getDeclaredField(fieldName);
 declaredField.setAccessible(true);
 declaredField.set(obj, value);
 }

 /**
 *   获取某个字段的值
 * @param clazz
 * @param fieldName
 * @return
 * @throws Exception
 */
 public static Object getField(Class<Object> clazz, String fieldName) throws Exception {
 Object obj = clazz.newInstance();
 Field declaredField = clazz.getDeclaredField(fieldName);
 declaredField.setAccessible(true);
 return declaredField.get(obj);
 }

 /**
 *  获取所有字段名称列表
 * @param clazz
 * @return
 */
 public static List<Object> getFields(Class<Object> clazz) throws IllegalAccessException, InstantiationException {
 List<Object> list = new ArrayList<>();
 Object obj = clazz.newInstance();
 Field[] fieldList = clazz.getDeclaredFields();
 for (Field fld : fieldList) {
 list.add(fld.get(obj));
 }
 return list;
 }

 /**
 * 获取所有字段名称+类型以 hashMap 返回
 * @param clazz
 * @return
 */
 public static HashMap<String, Class<?>> getFieldMap(Class clazz) {
 HashMap<String, Class<?>> hashMap = new HashMap<String, Class<?>>();
 Field[] fieldList = clazz.getDeclaredFields();
 for (Field fld : fieldList) {
 hashMap.put(fld.getName(), fld.getType());
 }
 return hashMap;
 }

Method
方法 释义
getMethod() 获得该类某个 Public 方法
getMethods() 获得该类所有 Public 方法
getDeclaredMethod() 获得该类某个方法(包括私有)
getDeclaredMethods() 获得该类所有方法(包括私有)
invoke() 根据传递的object对象及参数调用该对象的方法
 * 获取所有方法列表
 * @param clazz 类对象
 * @return
 */
 public static List<Object> getMethods(Class clazz) {
 List<Object> list = new ArrayList<>();
 for (Method declaredMethod : clazz.getDeclaredMethods()) {
 declaredMethod.setAccessible(true);
 list.add(declaredMethod);
 }
 return list;
 }

 /**
 * 指定方法方法调用
 * @param clazz 类对象
 * @param methodName 调用的方法名称
 * @param value 方法入参
 * @param type 入参类型
 * @throws Exception
 */
 public static void invokeMethod(Class clazz, String methodName, String value, Class... type) throws Exception {
 Object obj = clazz.newInstance();
 Method method = clazz.getDeclaredMethod(methodName, type);
 method.invoke(obj, value);
 }
Other
方法 释义
isAnnotation() 如果是注解类型则返回true
isAnnotationPresent() 如果是指定类型注解类型则返回true
isAnonymousClass() 如果是匿名类则返回true
isArray() 如果是一个数组类则返回true
isEnum() 如果是枚举类则返回true
isInstance() 如果obj是该类的实例则返回true
isInterface() 如果是接口类则返回true
isLocalClass() 如果是局部类则返回true
isMemberClass() 如果是内部类则返回true

案例

从上面的一些方法示例来看,反射的操作过程比较麻烦,还不如使用new对象来的简单直接。更别说反射是框架设计的灵魂这一说法,并没有体现出本身的任何价值。下面会通过简单的案例演示,可以初步了解到反射的魅力所在。

需求:在不改变该类任何代码前提下,任意创建类对象,并且执行其中任意方法。通过配置文件来实现反射机制。

步骤:
1.将需要创建的对象的类路径和需要执行的方法定义在配置文件中
2.在程序中加载读取配置文件,并通过反射机制将类加载进内存
3.创建对象,执行指定方法

配置文件定义
#pro.properties
className=org.example.basic.Person
methodName=eat
配置类
package org.example.basic;

public class Person {

 public void eat() {
 System.out.println("anything...");
 }
}
反射类
package org.example.basic;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Properties;


public class Reflection {
 private static String className;
 private static String methodName;

 /**
 * 加载配置文件
 */
 static {
 Properties properties = new Properties();
 try {
 properties.load(Reflection.class.getClassLoader().getResourceAsStream("pro.properties"));
 className = properties.getProperty("className");
 methodName = properties.getProperty("methodName");
 } catch (IOException e) {
 e.printStackTrace();
 }
 }

 /**
 * 加载类进内存,创建对象,执行指定方法
 * @throws Exception
 */
 public static void invokeMethod() throws Exception {
 Class<?> aClass = Class.forName(className);  // 类加载进内存,返回类对象
 Object obj = aClass.newInstance();  // 创建对象
 Method method = aClass.getMethod(methodName);  // 获取指定方法
 method.invoke(obj);  // 执行方法调用
 }


 public static void main(String[] args) throws Exception {
 Reflection.invokeMethod();  // 方法调用输出:anything...

 }
}

Epilogue

以上就是通过动态加载配置文件的类和方法,在不改变该类任何代码前提下,可以实现任意创建类对象,并且执行其中任意方法。可以达到一个解耦的目的,提升程序的扩展性。

反思?

通过以上的反射相关类及方法的介绍和案例的使用,已经初步了解的Java反射机制的强大之处,那么如何结合反射应用到实际工作中或者编写一个简单“测试框架”?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351

推荐阅读更多精彩内容