反射是java开发中比较常见和重要的一个知识,我们平时在开发中,可能会遇到,但相对其他知识没有那么频繁,曾几何时,我刚开始接触反射,查阅各种博客,大多数博客,无一例外,都是在介绍反射是获取运行中任意…,如何使用…,但是总觉得有点深奥,没有真正的让我们去理解和灵活运用,所以我想用这篇文章更加直白的去介绍这个知识,让大家轻松掌握这个知识。
反射带个“反”字,相对而言就是“正”,“正”是什么呢,一般情况下,我们使用某个类,需要知道它是干什么的,实例化这个类,然后使用它,比如:
Person person = new Person(); // 实例化person对象
person.name = "张三"; // 赋值 name = 张三
person.age = 24; // 赋值 age = 24
person.work();// 调用work方法
像这样通过new 对象,创建实例,然后再使用的方式,是我们一般的操作方式,称之为“正”,而反射我们没法一开始就知道要初始化类的对象是什么,没法通过new的方式,创建实例,我们需要使用JDK提供的反射API进行反射调用:
Class clz = Class.forName("com.example.lib.Person");
Object obj = clz.newInstance();
// 获取name成员变量
Field nameField = clz.getField("name");
// 设置name
nameField.set(obj,"张三");
// 获取age成员变量
Field ageField = clz.getField("age");
// 设置age
ageField.setInt(obj,24);
// 调用work方法
Method method = clz.getMethod("work");
method.invoke(obj);
上述两段代码,执行的效果其实是一样的,方式一在运行前就已经确定了运行的类(person),方式二运行时才知道运行的类,通过字符串值(com.example.lib.Person)获取运行的类。
so,反射是什么。
反射是在运行时才知道要操作的类是什么,并且可以在运行时获取到类的完整构造,并使用对应的方法和属性。(引自大白话说Java反射:入门、使用、原理)
反射发生在运行期,对应的还有编译期,下面给一张图体会一下他们的区别:
由上图可以看出,编译期主要是将Java源码转换成字节码,运行期主要是处理器产生的代码并加载、执行。
前面我们大概描述了一下,反射是什么东西,下面我们来介绍一下,如何使用反射,在这之前,我想先介绍一下,反射提供了那些功能给我们:
// 第一种getClass方法获取
Person person = new Person();
Class<?> personClass = person.getClass();
// 第二种类名.class获取
personClass = Person.class;
// 第三种Class.forName(类全路径名)获取
try {
personClass = Class.forName("com.example.lib.Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 创建类对象
Class clz = Person.class;
try {
// 第一种通过class newInstance()方法
Person person1 = (Person) clz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
// 第二种通过Constructor newInstance()方法
try {
Constructor constructor = clz.getConstructor(); // 不带参数
Person person2 = (Person) constructor.newInstance();
Constructor constructor2 = clz.getConstructor(String.class, int.class); // 带参数
Person person3 = (Person) constructor2.newInstance("张三", 18);
} catch (NoSuchMethodException | InstantiationException
| IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
Class class1 = Person.class;
Field[] allFields = class1.getDeclaredFields(); // 获取class对象的所有属性
Field[] publicFields = class1.getFields(); // 获取class对象的public属性
try {
Field ageField = class1.getDeclaredField("age"); // 获取class指定属性
Field idField = class1.getField("name"); // 获取class指定的public属性
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
Method[] methods = class1.getDeclaredMethods(); // 获取class对象的所有声明方法
Method[] allMethods = class1.getMethods(); // 获取class对象的所有方法 包括父类的方法
Class clz = Class.forName("com.example.lib.Person");
Object obj = clz.newInstance();
// 调用work方法
Method method = clz.getMethod("work");
method.invoke(obj);
动态代理一般是为了给方法添加预处理或者添加后续操作或者修改、拦截某些方法,不影响原有业务逻辑,这里我们主要关注两个东西,一个是InvocationHandler(接口),一个是Proxy(代理类)。
下面介绍一个Android的例子(主要是hook 剪切板,我们每次剪切后的文字,都是我们事先设置的字符串),代码如下:
1.创建一个代理类,实现InvocationHandler接口,传入系统类,在invoke方法里面,通过方法名拦截对应方法。
// 代理类
public class MyClipProxy implements InvocationHandler {
private final IBinder mBase;
public MyClipProxy(IBinder mBase) {
this.mBase = mBase; // 传入系统代理类
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 拦截系统原有的方法
if("queryLocalInterface".equals(method.getName())){
Class<?> mStubClass = Class.forName("android.content.IClipboard$Stub");
Class<?> mStubClipboard = Class.forName("android.content.IClipboard");
return Proxy.newProxyInstance(mStubClass.getClassLoader(),new Class[]{mStubClipboard},
new MyClip(mBase,mStubClass));
}
// 不是这个方法按原来系统执行
return method.invoke(mBase,args);
}
}
2.对于系统获取剪切板文字方法进行修改,达到无论如何剪切获取的都是“hook系统源码,哈哈哈。。。”
public class MyClip implements InvocationHandler {
private Object mBase;
public MyClip(IBinder base, Class stub) {
try {
Method asInterface = null;
asInterface = stub.getDeclaredMethod("asInterface", IBinder.class);
mBase = asInterface.invoke(null, base);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 这里我们拦截粘贴的方法,
if ("getPrimaryClip".equals(method.getName())) {
return ClipData.newPlainText(null, "hook系统源码,哈哈哈。。。");
}
// 再拦截是否有复制的方法,放系统认为一直都有
if ("hasPrimaryClip".equals(method.getName())) {
return true;
}
// 其他启动还是返回原有的
return method.invoke(mBase, args);
}
}
3.通过反射获取剪切板服务代理对象,修改对应的成员变量,让系统每次拿到的数据都是我们通过反射修改过的数据
/**
* hook系统剪切板服务
*/
public class ClipHelper {
public static void binder() {
try {
// 反射方式获取ServiceManager对象
Class<?> serviceManagerClass = Class.forName("android.os.ServiceManager");
// 拿到getService方法
Method getServiceMethod = serviceManagerClass.getDeclaredMethod("getService", String.class);
// 通过方法拿到系统服务代理对象
IBinder binder = (IBinder) getServiceMethod.invoke(null, "clipboard");
// 创建自己的代理对象
IBinder myBinder = (IBinder) Proxy.newProxyInstance(serviceManagerClass.getClassLoader(),
new Class[]{IBinder.class}, new MyClipProxy(binder));
// 拿到ServiceManager的数组
Field field = serviceManagerClass.getDeclaredField("sCache");
field.setAccessible(true);
Map<String, IBinder> map = (Map<String, IBinder>) field.get(null);
// 将服务存入map
map.put("clipboard", myBinder);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
4.需要的执行的地方,调用ClipHelper.binder()
try {
ClipHelper.binder();
} catch (Exception e) {
e.printStackTrace();
Log.e("ddup", "clip hook error = " + e.getMessage());
}
我们为了避免类型强转,会使用到泛型,通过反射我们可以还原泛型的原始类型,这就为我们比如传入指定类型,自动解析对象,提供了支持。
public class People<T> {
}
public class Person extends People<String> {
}
Person person = new Person() ;
Class<?> class1 = person.getClass();
Type type = class1.getGenericSuperclass();// 获取class对象的直接超类的Type
if(type instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();
if (actualTypeArguments != null && actualTypeArguments.length > 0) {
Class<?> clzType = (Class<?>) actualTypeArguments[0];
System.out.println("泛型类型是: "+clzType);
}
}else {
System.out.println("获取泛型类型出错!");
}
最后输出:
泛型类型是: class java.lang.String
上面的代码是获取类的泛型,还有获取方法参数中的实际类型,获取方法返回值泛型实际类型,这里不在说了,感兴趣可以看一下,这篇文章通过反射获得泛型实际类型。
上面讲述了反射的常用API,其实里面已经涉及到了反射的应用场景,主要在以下几个方面:
反射是作用在运行期,可以动态加载类,实现动态代理,比较灵活,但是比较消耗性能,适合执行某些特定的操作,真正需要大量生成代码还是使用APT之类的技术较好。
关于反射,我了解也只有这么多了,其中的原理,还没有来得及研究。
纸上得来终觉浅,还是得我们一行行实际。
Thanks:
大白话说Java反射:入门、使用、原理
Java学习之反射机制及应用场景
创作不易,觉得不错的话,请点赞、转发、评论鼓励,谢谢。