一、介绍
什么是反射?
反射是java语言的一个特性,它允程序在运行时(注意不是编译的时候)来进行自我检查并且对内部的成员进行操作。
反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意方法和属性,这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
为什么要用反射?
Java Reflection API简介
Java Reflection功能非常强大,并且非常有用,比如:
-
获取任意类的名称、package信息、所有属性、方法、注解、类型、类加载器等
-
获取任意对象的属性,并且能改变对象的属性
-
调用任意对象的方法
-
判断任意一个对象所属的类
-
实例化任意一个类的对象
-
通过反射我们可以实现动态装配,降低代码的耦合度,动态代理等。
在JDK中,主要由以下类来实现Java反射机制,这些类(除了第一个)都位于java.lang.reflect包中
Class类:代表一个类,位于java.lang包下。
Field类:代表类的成员变量(成员变量也称为类的属性)。
Method类:代表类的方法。
Constructor类:代表类的构造方法。
Array类:提供了动态创建数组,以及访问数组的元素的静态方法。
二、反射的使用
1、创建类对象
正常我们创建类对象都是通过new关键字来创建,但是我们还有其他两种方式
-
通过 Class 对象的 newInstance() 方法
-
通过 Constructor 对象的 newInstance() 方法
基础数据:
package com.example.mylibrary.ref;
public class MyPerson {
private String name = "default";
public int age = 2;
protected boolean sex;
private MyPerson(String name) {
this.name = name;
}
protected MyPerson(int age) {
this.age = age;
}
public MyPerson() {
}
public MyPerson(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
private String getPrivateName() {
return name;
}
protected int getProtectAge() {
return age;
}
}
1.1通过 Class 对象的 newInstance() 方法
Class cls=Class.forName("com.example.mylibrary.ref.MyPerson"); MyPerson person=(MyPerson) cls.newInstance();
1.2通过 Constructor 对象的 newInstance() 方法
Constructor constructor=cls.getConstructor(); MyPerson myPerson=(MyPerson) constructor.newInstance();
以上都是构造器无默认参数,如果有的话1.1已无法满足,且1.1的方法在java9的时候已过期,接下我们主讲1.2通过Constructor 创作对象
有参构造:
1、public Constructor<T> getConstructor(Class<?>... parameterTypes)
要指定构造器的参数类型,必须一一对其
2、public T newInstance(Object... initargs)
在获取对象时,需要传入默认参数
Constructor constructor=cls.getConstructor(String.class,Integer.TYPE); MyPerson myPerson=(MyPerson) constructor.newInstance("你好",12);
1.3.获得成员变量Field
获取成员变量有四种:
1、Field[] fields = cls.getFields(); 2、Field field = cls.getField("age"); 3、Field[] dcfis = cls.getDeclaredFields(); 4、Field dfied = cls.getDeclaredField("name");
getFields:获取所有public的属性变量
getField:获取指定为public的属性变量对象
getDeclaredFields:获取所有变量,包括public、protect、private
getDeclaredField:获取任性一个属性对象,包括public、protect、private
看如下debug日志:
注意:
1.通过getDeclared获取的是全部,如果方法前面没有Declared基本获取的就是public
2.如果field设置value异常不生效,需要设置field.setAccessible(true)
1.4构造器的获取
构造器获取区分public和全部,通过如下方式获取
Constructor[] construPublic= cls.getConstructors();//public Constructor[] consrAll=cls.getDeclaredConstructors();//all Log.log(construPublic.length+""); Log.log(consrAll.length+"");
1.5.类的方法Method
Method是class中的方法,方法的获取如下
1.Method[] methods = cls.getMethods();
这种获取到的方法是当前类下可用的所有public方法,包括class自身的一些方法
如
getMethods=wait
getMethods=equals
getMethods=toString
getMethods=hashCode
getMethods=getClass
getMethods=notify
getMethods=notifyAll
2.Method[] methodDecs = cls.getDeclaredMethods();
这种获取的是当前类下的所有方法,不包括class自身提供的,只有用户定义的所有类型
getDeclaredMethods=getName
getDeclaredMethods=setName
getDeclaredMethods=getAge
getDeclaredMethods=setAge
getDeclaredMethods=getProtectAge
getDeclaredMethods=getPrivateName
1.6. 如何调用Method
先准备一个对象:MyPerson person=new MyPerson();
1.无参数调用
Method method_getAge = cls.getMethod("getAge"); method_getAge.setAccessible(true); Object o = method_getAge.invoke(new MyPerson());
2.有参调用
Method method_setAge = cls.getMethod("setAge", Integer.TYPE); method_setAge.setAccessible(true);
method_setAge.invoke(person,122);
通过Method设置或者获取都一样,方法的调用通过invoke(),必须传入当前对象,后面有多少个参数就一一对应,没有就不传。
这样基本就完成了反射的作用
注意:
Method提供了getDefaultValue(),这个方法是获取注解提供的默认值,不是获取目标类的值。如果不是通过注解设置默认值,返回的是null。
1.7.内部类的获取
我们上面都是介绍单独类,有些人说如果我的类是类部类怎么获取?其实很简单
Class clsParent = Class.forName("com.example.mylibrary.ref.Parent"); Class clsChild = Class.forName(clsParent.getName() + "$Child");
path=父类+$+内部类名称
二、动态代理Proxy
掌握了基本的反射使用,那我们继续了解动态代理。我们经常听到Hook,钩子说法很悬乎,也很科幻,一些公司面试喜欢闻,你会Hook嘛?其实就这些。没有那么悬乎,剩下的就是Hook涉及到的复杂场景,任何复杂的代码都是从基础做起。
动态代理通过Proxy类完成
1、获取一个代理对象:newProxyInstance
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) { Objects.requireNonNull(h); Class<?> caller = System.getSecurityManager() == null ? null : Reflection.getCallerClass(); Constructor<?> cons = getProxyConstructor(caller, loader, interfaces); return newProxyInstance(caller, cons, h); }
参数介绍
1.1、loader:加载器
1.2、interfaces:接口类接口,代理是通过代理类的接口进行拦截,而不是直接修改方法体
1.3、InvocationHandler:拦截器
拦截器介绍:
public Object invoke(Object proxy, Method method, Object[] objects)
1、proxy:代理对象
2、method:代理对象调用的方法
3、objects:参数
小试牛刀:
public static void main(String[] args) throws Exception {
final PersonInterface person = new Child("sun");
Object proxy = Proxy.newProxyInstance(person.getClass().getClassLoader(), person.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
String name = method.getName();
Log.log("Object o, Method method, Object[] objects----------");
if (method.getName().equals("setName")) {
String item=(String) objects[0];
objects[0]="modify="+item;
}
return method.invoke(person, objects);
}
});
if (proxy instanceof PersonInterface) {
PersonInterface testPerson = (PersonInterface) proxy;
testPerson.setName("proxy");
}
PersonInterface testPerson = (PersonInterface) proxy;
testPerson.setName("proxy");
Log.log(testPerson.getName());
}
package com.example.mylibrary.ref;
import com.example.mylibrary.Log;
public class Child implements PersonInterface {
String name = "";
public Child(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
public void show() {
Log.log("name=" + name);
}
}
package com.example.mylibrary.ref;
public interface PersonInterface {
public String getName();
public void setName(String name);
}
这样,我们就完成了Hook了。记住,动态代理是拦截接口,而不是修改方法体