Java – 反射
简介
反射的概念是将类的各个组成部分封装为其它对象。
反射是Java对类的封装访问机制,比如IDEA这样的IDE里,是如何知道一个类中包含了什么方法,并提示开发者的?这其中就是使用反射,通过对类或对象的访问可以获取类所包含的方法或成员数据等。
Java在编译时,会把类中的成员,方法,等数据封装成一个对象
Field[] fields; Field类数组存储类的所有成员变量
Constructor[] constructors; Constructor 类数组存储类的所有构造函数
Method[] methods; Method 类数组存储类的所有成员方法。
获取类对象
获取类对象有三种方式
1.Class.forName("全类名"),将字节码文件加载进内存,返回 Class 对象
多用于配置文件,将类名定义在配置文件中,在读取文件时加载类
Class.forName("cn.unsoft.io.ZipStream");
2.类名.class : 通过类名的属性 class 获取
多用于参数的传递
Class zipStreamClass = ZipStream.class;
3.对象.getClass() :通过Object类中定义的 getClass() 方法获取。
多用于对象的获取字节码的方式
ZipStream z = new ZipStream();
Class zipClass = z.getClass();
注意:同一个字节码文件,在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个
使用Class对象
当我们成功获得对象后,我们就可以对这个对象进行各种操作了
获取构造方法
获取构造方法,只是为了用于创建对象
// 获取类的所有公共构造方法的数组
public Constructor<?>[] getConstructors()
// 获取指定的构造方法,如果构造方法中提供了参数,那么这里就是传入参数类型的 class
public Constructor<T> getConstructor(Class<?>... parameterTypes)
// 返回所有构造方法对象的数组(包括私有构造方法)
public Constructor<?>[] getDeclaredConstructors()
// 返回单个构造方法对象
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
获取类的构造方法后,并创建一个对象
// 获取 Student 类
Class<Student> studentClass = Student.class;
// 获取 Constructor 构造对象
Constructor constructor = studentClass.getConstructor(String.class, int.class);
// 创建 Student 对象
Object s = constructor.newInstance();
// 使用getDeclaredConstructor取出私有化构造函数
Class<Student> studentClass1 = Student.class;
Constructor<Student> declaredConstructor = studentClass1.getDeclaredConstructor();
// 开启暴力反射调用构造
declaredConstructor.setAccessible(true);
// 创建对象,并传入构造函数参数
Student student = declaredConstructor.newInstance("张三", 18);
构造方法对象常用方法
.getModifiers(); => 获取方法的修饰符,修饰符使用 int 表示,修饰符对应表如下
.getName(); => 获取方法的方法名称 String
.getAnnotation(Override.class); => 获取方法的注解 Class ? Annotation
.getParameterCount(); => 获取方法的参数个数 int
.getParameterTypes(); => 获取方法的参数类型(数组)
.getParameters(); => 获取方法的参数 Parameter[]
获取成员变量
我们可以获得对应对象的成员变量,并且进行取值或赋值。
// 获取这个类的所有【公共】成员变量
public Field[] getFields();
// 通过传入指定成员变量名称获取成员变量
public Field getField(String name);
// 获取这个类的所有成员变量,不考虑权限修饰符,所有成员都会获取
public Field[] getDeclaredField();
// 通过传入指定成员变量名称获取成员变量,不考虑权限修饰符,所有成员都会获取
public Field getDeclaredFields();
操作成员变量
成员变量操作,无非就两种,一种是赋值操作,一种是取值操作,在Field 类中,包含了两个方法,分别是 get 和 set 方法
// 传入一个实例对象,取得这个实例对应的成员变量的值
Object get(Object obj)
// 传入一个实例对象和值,将赋值对应的实例的对应成员变量值
void set(Object obj, Object value)
这里要注意:set 方法对于 private 修饰的成员变量,Java具有安全检查机制,如果直接赋值,Java会报出异常。
我们使用暴力赋值的方式,对private修饰的成员变量进行赋值
// 通过反射获取到类中的成员变量
Field name = zipStreamClass.getField("name");
// 因为 成员变量 name 是 private 的,所以需要关闭安全检查
name.setAccessible(true);
// 设置实例 obj 实例的 name 成员变量为 "张三"
name.set(obj,"张三");
成员变量对象常用方法
.getModifiers(); => 获取成员变量的修饰符,修饰符使用 int 表示,修饰符对应表如下
.getName(); => 获取成员变量的名称
.getAnnotation(Annotation.class); => 获取成员变量的注解
.getType(); => 获取成员变量的类型
.set(); => 设置获取成员变量的值
获取成员方法
获取成员方法可以获得调用这个成员方法的权限
// 获取类中的可执行的【公共】方法,包含父类的 public 方法
// 要求传入一个指定的方法名,和方法需要传入的参数类形的 class
public Method getMethod(String name, Object... args);
// 获取类中的可执行的 【公共】方法,包含父类的 public 方法
public Method[] getMethods();
// 获取类中的可执行的所有方法,包括私有的,但不包含父类方法
public Method getDeclaredMethod(String name, Object... args);
// 获取类中的可执行的所有方法,包括私有的,但不包含父类方法
public Method[] getDeclaredMethods();
获取一个方法对象,并对某个实例进行方法执行
// 获取一个类的反射
Class<Student> studentClass2 = Student.class;
// 获取这个类的 setName(String name) 带参方法,后面需要提供参数类型的 class
Method setName = studentClass2.getMethod("setName", String.class);
// 执行某个对象的 setName() 方法,并传参
setName.invoke(stu,"张三");
Method setAge = studentClass2.getDeclaredMethod("setAge", int.class);
// 如果这个方法是私有方法的话,也需要开启暴力执行
setAge.setAccessible(true);
// 执行stu对象里的setAge方法
setAge.invoke(stu,18);
方法的对象常用方法
.getModifiers(); => 获取方法的修饰符
.getName(); => 获取方法的方法名
.getAnnotation(Annotation.class); => 获取方法的注解
.getParameterCount(); => 获取方法的参数个数
.getParameterTypes(); => 获取方法的参数类型
.invoke(); => 执行方法,需要提供有这个方法的实例对象,如果方法有参数,则需要传入参数,其返回值是反射方法执行后的返回值
获取名称
可以获取类的名称
studentClass.getName();
获取成员变量的名称
field.getName()
获取成员方法的名称
setName.getName();
示例案例
需求:在不修改代码的前提下,通过修改配置文件,来执行配置文件中指定的类和方法
// 1.通过读取配置文件获得类和方法
Properties pro = new Properties();
// 通过获取这个文件所在的配置文件
InputStream propertiesFile = new FileInputStream("pro.properties");
// 载入配置文件
pro.load(propertiesFile);
String className = pro.getProperty("class");
String methodName = pro.getProperty("method");
// 2.取得类的反射对象
Class refClass = Class.forName(className);
// 3.取得这个类的构造函数,创建这个类的实例,因为构造函数里使用了带参构造,所以也要传递构造中的参类型类的 class
Constructor refContructor = refClass.getConstructor(String.class,int.class);
Object refObj = refContructor.newInstance("张三",18);
// 4.获取类中的方法,并调用这个类的方法
Method method = refClass.getMethod(methodName);
method.invoke(refObj); // 因为方法没有参数,所以没有传参
Person类定义
public class Person {
public String name;
public int age;
public Person(String name,int age) {
this.name = name;
this.age = age;
}
public void work() {
System.out.println("我是" + this.name + ", 我今年" + this.age + "岁");
System.out.println(this.name + "在工作");
}
}
Cat 类定义
public class Cat {
public String name;
public int age;
public Cat(String name,int age) {
this.name = name;
this.age = age;
}
public void sleep() {
System.out.println("我是" + this.name + ", 我今年" + age + "岁");
System.out.println(this.name + "在睡觉");
}
}
Dog 类定义
public class Dog {
public String name;
public int age;
public Dog(String name,int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println("我是" + this.name + ", 我今年" + age + "岁");
System.out.println(this.name + "在吃东西");
}
}
结果
通过修改配置文件,就可以在不修改代码的前提下,创建不同的类,执行不同的方法
配置文件为 Person
class=cn.unsoft.ref.Person
method=work
输出结果 ==>
我是张三, 我今年18岁
张三在工作
配置文件为 Cat
class=cn.unsoft.ref.Cat
method=sleep
==>
我是张三, 我今年18岁
张三在睡觉
配置文件为 Dog
class=cn.unsoft.ref.Dog
method=eat
==>
我是张三, 我今年18岁
张三在吃东西
动态代理
当一段已经开发完成且正常运行的代码,需要新增扩展功能的时候,我们需要往这段已经开发好的代码进行代码修改,这种方式我们称为“侵入式开发”,缺点也非常明显,在一段已经开发好的代码中进行修改,有可能会出现不可预知的问题。
使用动态代理,则是一种“无侵入式”的开发方式,能给开发好的代码不通过修改原有的代码结构而给代码增加额外的功能。
概念
往一个已经写好的代码中添加新功能,那么就需要创建一个新的类,这个类重新创建了一个新方法,这个方法除了会调用旧方法外,还会加入一些新功能
原来的代码:
sing(){
正在唱歌。。。
}
新代码
newSing(){
唱歌前做准备。。。
sing();
}
为了确保代理类与原先的类规范调用,在 Java 中,提供了一个代理类实现。
实现
Java.lang.reflect.Proxy 类提供了一种为对象产生代理对象的方法
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
参数一:需要提供类加载器,通常类加载器就是调用我们程序的加载器,可以通过 this.class.getClassLoader() 获取
参数二:指定接口,这些接口用于指定生成的代理有什么方法
参数三:用来指定生成的代理对象要执行什么(即当被调用代理方法时,要执行什么)
案例
例:往原来的方法中增加日志功能,而不需要修改原来的代码
1.创建原代码的类,用于表示已经开发完成的旧代码
public class Work implements WorkProxy {
private String name;
public Work() {
}
public Work(String name) {
this.name = name;
}
@Override
public String working() {
System.out.println(name + "正在工作……");
return "working Finish";
}
}
2.创建代理用的接口,定义要代理那个类
public interface WorkProxy {
public abstract String working(String name);
}
3.如果1中的 working 方法,需要添加日志,可以使用newProxyInstance
public class ProxyUtils {
public static Object getProxy(Object obj) {
return Proxy.newProxyInstance(ProxyUtils.class.getClassLoader(),
new Class[]{WorkProxy.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("日志:正在正在准备");
Object result = method.invoke(obj, args);
System.out.println("日志:正在已完成,结果:" + result);
return result;
}
});
}
}
Object proxy => 表示当前 Proxy 对象
Method method => 表示当前需要调用的方法对象
Object[] args => 表示调用方法的参数
4.调用代理接口方法,实现增加功能
public static void main(String[] args) {
Work w = new Work();
WorkProxy proxy = (WorkProxy) ProxyUtils.getProxy(w);
proxy.working("鸡哥");
}
5.运行结果
日志:工作正在准备
鸡哥正在工作……
日志:工作已完成,结果:working Finish
共有 0 条评论