news 2026/4/16 16:57:53

Java进阶知识-反射

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java进阶知识-反射

获取Class对象

有三种方式获取Class对象:

  1. 根据类的完整包名获取Class

    Class clazz = Class.forName(“com.example.xjp.demo.reflect.PersonInfo”);

  2. 根据类名直接获取Class

    Class clazz = PersonInfo.class;

  3. 根据实例类的对象获取Class

    PersonInfo personInfo = new PersonInfo();
    Class clazz = personInfo.getClass();

创建实例

通过反射来生成对象主要有两种方式
1. 使用Class对象的newInstance()方法来创建Class对象对应类的实例

Class clazz = PersonInfo.class; PersonInfo personInfo = (PersonInfo) clazz.newInstance();

2.使用Class对象的构造器来创建实例

Constructor constructor = clazz.getConstructor(PersonInfo.class); //有构造器来创建实例,可以传参数给newInstance(Object ... initargs) PersonInfo personInfo = (PersonInfo) constructor.newInstance();

获取方法

通过反射,可以获取某个类中的所有方法,包括private,public,protect类型的方法
1. 获取类的所有申明的方法,包括public,private,protect类型的方法

Class clazz = PersonInfo.class; Method[] declaredMethods = clazz.getDeclaredMethods();

2.获取类中所有public方法

Class clazz = PersonInfo.class; Method[] methods = clazz.getMethods();

3.获取类中指定的public方法

Class clazz = PersonInfo.class; PersonInfo personInfo = (PersonInfo) clazz.newInstance(); //第一个参数是方法名,第二个参数是该方法参数的类型 Method method = clazz.getMethod("getName", String.class); //调用 PersonInfo 类中的 getName()方法 String name = (String) method.invoke(personInfo , "是最帅的");

4.获取指定的private方法

Class clazz = PersonInfo.class; PersonInfo personInfo = (PersonInfo) clazz.newInstance(); //第一个参数是方法名,第二个参数是该方法参数的类型 Method method = clazz.getDeclaredMethod("getAge", Integer.class); //由于该方法是private的,所以需要设置访问权限 method.setAccessible(true); //调用PersonInfo 类中的 getAge()方法 int age = (int) method.invoke(personInfo, 18);

获取类的成员变量

  1. 获取类中所有成员变量,包括public,private,protect类型

    Field[] declaredFields = clazz.getDeclaredFields();

2.获取类中所有public类型的成员变量

Field[] fields = clazz.getFields();

3.获取指定的成员变量,public类型

Field nameField = clazz.getField("mName"); //修改成员变量mName的值为Tom nameField.set(personInfo, "Tom"); //得到成员变量nName的值 String name = nameField.get(personInfo);

4.获取指定的成员变量,private类型

//得到私有的成员变量 mAge Field ageField = clazz.getDeclaredField("mAge"); //设置其访问权限 ageField.setAccessible(true); //修改成员变量 mAge 的值 ageField.set(test, 23); //获取该成员变量的值 int age = (int) ageField.get(test); public class PersonInfo { private int mAge = 18; public String mName = "xjp"; private int getAge(int age) { return mAge; } public String getName(String msg) { return mName + ":" + msg; } }

反射的应用场景

我们来做一个有意思的功能,在你的应用所有启动Activity之前的地方加一个打印,打印出一些相关信息。你可能会想到如下策略:
应用中所有的Activity都继承自一个BaseActivity基类,基类中实现一个startActivity方法,在该方法之前加上一句打印,那么所有startActivity的地方都调用基类中的方法。

但是有这么一种情况,项目已经进行了80%,大部分Activity已经写好了,好多Activity都不是继承自同一个BaseActivity基类,如果需要改的话,改动地方太多,改动大,不划算。那么有没有一种更好的办法,修改少,又能实现该功能的方法呢?

答案就是利用反射,在系统调用startActivity的地方换成我们自己的startActivity方法,这一招叫做偷梁换柱。那么怎么实现呢?

我们先找到Android系统的startActivity方法是怎么实现的,该方法是Context类的方法,而Context只是一个借口,其实现类是ContextImpl类,代码如下:

@Override public void startActivity(Intent intent, Bundle options) { warnIfCallingFromSystemProcess(); // Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is // generally not allowed, except if the caller specifies the task id the activity should // be launched in. if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0 && options != null && ActivityOptions.fromBundle(options).getLaunchTaskId() == -1) { throw new AndroidRuntimeException( "Calling startActivity() from outside of an Activity " + " context requires the FLAG_ACTIVITY_NEW_TASK flag." + " Is this really what you want?"); } mMainThread.getInstrumentation().execStartActivity( getOuterContext(), mMainThread.getApplicationThread(), null, (Activity) null, intent, -1, options); }

最终启动activity是有mMainThread对象的getInstrumentation()方法获取Instrumentation对象,然后由该对象调用execStartActivity()方法来启动activity。而mMainThread对象是ActivityThread类型,该类是我们的主线程类,里面有有一个mInstrumentation成员变量,该成员变量属于Instrumentation类型。

我们的思路是替换ActivityThread类总的mInstrumentation对象,使用我们自己的 Instrumentation对象。实现如下:

public class ProxyInstrumentation extends Instrumentation { private static final String TAG = "ProxyInstrumentation"; // ActivityThread中原始的对象, 保存起来 Instrumentation mBase; public ProxyInstrumentation(Instrumentation base) { mBase = base; } public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { // Hook之前, XXX到此一游! Log.d(TAG, " 执行了startActivity, 参数如下: " + "who = [" + who + "], " + " contextThread = [" + contextThread + "], token = [" + token + "], " + " target = [" + target + "], intent = [" + intent + "], requestCode = [" + requestCode + "], options = [" + options + "]"); // 开始调用原始的方法, 调不调用随你,但是不调用的话, 所有的startActivity都失效了. // 由于这个方法是隐藏的,因此需要使用反射调用;首先找到这个方法 try { Method execStartActivity = Instrumentation.class.getDeclaredMethod( "execStartActivity", Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class); execStartActivity.setAccessible(true); return (Instrumentation.ActivityResult) execStartActivity.invoke(mBase, who, contextThread, token, target, intent, requestCode, options); } catch (Exception e) { // 某该死的rom修改了 需要手动适配 throw new RuntimeException("do not support!!! pls adapt it"); } }

然后通过反射拿到ActivityThread类中的mInstrumentation,代码如下:

public static void attachContext() throws Exception{ // 先获取到当前的ActivityThread对象 Class<?> activityThreadClass = Class.forName("android.app.ActivityThread"); Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread"); currentActivityThreadMethod.setAccessible(true); Object currentActivityThread = currentActivityThreadMethod.invoke(null); // 拿到原始的 mInstrumentation字段 Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation"); mInstrumentationField.setAccessible(true); Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread); // 创建代理对象 Instrumentation evilInstrumentation = new ProxyInstrumentation(mInstrumentation); // 偷梁换柱 mInstrumentationField.set(currentActivityThread, evilInstrumentation); }

然后在你应用的Application类中调用如上方法即可:

public class DemoApplication extends Application { private static final String TAG = "DemoApplication"; private static Context mContext; @Override public void onCreate() { super.onCreate(); mContext = DemoApplication.this.getApplicationContext(); String processName = mContext.getApplicationInfo().processName; Log.i(TAG, "onCreate: the processName=" + processName); } public static Context getContext(){ return mContext; } @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); try { attachContext(); } catch (Exception e) { e.printStackTrace(); } } public static void attachContext() throws Exception{ // 先获取到当前的ActivityThread对象 Class<?> activityThreadClass = Class.forName("android.app.ActivityThread"); Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread"); currentActivityThreadMethod.setAccessible(true); Object currentActivityThread = currentActivityThreadMethod.invoke(null); // 拿到原始的 mInstrumentation字段 Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation"); mInstrumentationField.setAccessible(true); Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread); // 创建代理对象 Instrumentation evilInstrumentation = new ProxyInstrumentation(mInstrumentation); // 偷梁换柱 mInstrumentationField.set(currentActivityThread, evilInstrumentation); } }

打印如下:

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 12:45:36

Java MySQL 连接

Java MySQL 连接 本章节我们为大家介绍 Java 如何使用 使用 JDBC 连接 MySQL 数据库。 Java 连接 MySQL 需要驱动包&#xff0c;最新版下载地址为&#xff1a;http://dev.mysql.com/downloads/connector/j/&#xff0c;解压后得到 jar 库文件&#xff0c;然后在对应的项目中导…

作者头像 李华
网站建设 2026/4/16 10:42:50

Java进阶——IO 流

文章目录 Java进阶——IO 流 1、File 类的使用 1.1、File 常用构造器1.2、路径分隔符1.3、File 的常用方法 2、IO流原理及流的分类 2.1、IO流原理2.2、流的分类 3、IO流的体系结构4、节点流 4.1、FileReader 读入数据的操作4.2、FileWriter 写出数据的操作4.3、FileReader 和 …

作者头像 李华
网站建设 2026/4/16 14:27:25

值得收藏!Tool-to-Agent检索技术:解决大模型多智能体系统工具检索痛点

本文提出Tool-to-Agent Retrieval (T2A)方法&#xff0c;将工具和智能体纳入同一向量空间解决检索难题。通过构建二分图和统一编码器&#xff0c;实验显示该方法在LiveMCPBench数据集上最高提升28%召回率&#xff0c;轻量模型也有13%提升。研究表明工具细节补充了智能体语义缺失…

作者头像 李华
网站建设 2026/4/15 23:47:11

JAVA (Springboot) i18n国际化语言配置

JAVA i18n国际化语言配置 一、简介二、功能三、Java配置国际化步骤四、Java国际化配置工具类五、Spring Boot配置六、测试 一、简介 在Java中&#xff0c;国际化&#xff08;Internationalization&#xff0c;通常简称为i18n&#xff09;是一个过程&#xff0c;它允许应用程…

作者头像 李华
网站建设 2026/4/16 14:50:10

Java进阶13 线程池

Java进阶13 线程池 一、线程生命周期 线程被创建并启动以后&#xff0c;它并不是一启动就进入了执行状态&#xff0c;也不是一直处于执行状态。线程对象在不同的时期有不同的状态&#xff0c;这各种状态就是线程的生命周期。 Java中的线程状态被定义在了java.lang.Thread.State…

作者头像 李华
网站建设 2026/4/16 11:15:56

Java进阶09文件

Java进阶09 一、File类 File类代表操作系统的文件对象**&#xff08;文件、文件夹&#xff09;** 1、File类创建对象 方法 说明 public File(String pathname) 根据文件路径创建文件对象 public Fie(String parent,String child) 根据父路径名字符串和子路径名字符串创建文件对…

作者头像 李华