Java 反射
1.Java反射机制概述
Java Reflection
- Reflection(反射)是被视为 动态语言 的关键,反射机制允许程序在执行期借助于 Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性及其方法。
- 加载完类之后,在堆内存的方法区中就产生了一个 Class 类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,我们形象的称之为:反射
- **优点:**可以实现动态创建对象和编译,体现出很大的灵活性
- **缺点:**对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于直接执行相同的操作。
1.1 动态语言&&静态语言
-
动态语言:是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。即在运行时代码可以根据某些条件改变自身结构。(主要的动态语言有:C#、JavaScript、Python、PHP)
-
静态语言:运行时结构不可变的语言就是静态语言(如:Java、C、C++)
-
对于Java来讲,Java不是动态语言,但是Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。
1.2 Java反射机制的功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
1.3反射相关的主要API
- java.lang.Class:代表一个类
- java.lang.reflect.Method:代表类的方法
- java.lang.reflect.Field:代表类的成员变量
- java.lang.reflect.Constructor:代表类的构造器
2.Class类
对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于某个类而言,JRE都为其保留一个不变的Class类型的对象。一个Class对象包含了特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息。
2.1 对于Class类的理解
- Class本身也是一个类
- Class对象只能由系统建立对象
- 一个加载的类在JVM中只会有一个Class实例
- 一个Class对象对应的是一个加载到JVM中的一个.class文件
- 每个类的实例都会记得自己是由哪个Class实例所生成
- 通过Class可以完整地得到一个类中的所有被加载的结构
- Class类是Reflection 的根源,针对任何你想动态加载、运行的类,必须先获得Class对象。
2.2 获取Class类的实例
//获取Class的实例的方式
@Test
public void test3() throws ClassNotFoundException {
//方法一:调用运行时类的属性:.class
Class<Person> clazz1 = Person.class;
System.out.println(clazz1);
//方法二:通过运行时类的对象,调用getClass()
Person person = new Person();
Class clazz2 = person.getClass();
System.out.println(clazz2);
//方法三:调用Class的静态方法:forName(String classPath)
Class clazz3 = Class.forName("JavaSE.Reflection.Person");
System.out.println(clazz3);
//方法四:使用类的加载器ClassLoader
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass("JavaSE.Reflection.Person");
System.out.println(clazz4);
System.out.println(clazz1 == clazz2);
System.out.println(clazz1 == clazz3);
System.out.println(clazz1 == clazz4);
/*
class JavaSE.Reflection.Person
class JavaSE.Reflection.Person
class JavaSE.Reflection.Person
class JavaSE.Reflection.Person
true
true
true
*/
}
2.3 所有类型的Class
class/interface/enum/annotation/primitive type/void/[]
只要元素类型和纬度相同Class类就相同
//所有类型的Class
//只要元素类型和纬度相同Class类就相同
@Test
public void test4(){
Class c1 = Object.class; //类
Class c2 = Comparable.class; //接口
Class c3 = String[].class; //一维数组
Class c4 = int[][].class; //二维数组
Class c5 = Override.class; //注解
Class c6 = Integer.class; //基本数据类型
Class c7 = void.class; //void
Class c8 = ElementType.class;//枚举
Class c9 = Class.class; //Class
System.out.println(c1);
System.out.println(c2);
System.out.println(c3);
System.out.println(c4);
System.out.println(c5);
System.out.println(c6);
System.out.println(c7);
System.out.println(c8);
System.out.println(c9);
// class java.lang.Object
// interface java.lang.Comparable
// class [Ljava.lang.String;
// class [[I
// interface java.lang.Override
// class java.lang.Integer
// void
// class java.lang.annotation.ElementType
// class java.lang.Class
}
3.类的内存加载分析
3.1 Java内存分析
3.2 类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化。
3.3 类的加载与ClassLoader的理解
-
加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
-
链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
- 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
-
初始化:
- 执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
- 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
- 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
-
ClassLoader
测试ClassLoader:
@Test public void test1(){ //对于自定义类,使用系统类加载器进行加载 ClassLoader classLoader = ClassLoaderTest.class.getClassLoader(); System.out.println(classLoader); //调用系统类加载的getParent()方法,获取扩展类加载器 ClassLoader parent = classLoader.getParent(); System.out.println(parent); //调用扩展类加载器的getParent(),无法获取引导类加载器 //引导类加载器主要负责加载java的核心类库,无法加载自定义类的。 ClassLoader parent1 = parent.getParent(); System.out.println(parent1); //说明了加载java的核心类库,无法加载自定义类的。 System.out.println(String.class.getClassLoader()); }
使用classLoader读取Properties配置文件
/* propeerties:读取配置文件 */ @Test public void test2() throws Exception{ Properties properties = new Properties(); //读取文件的方式一:(文件默认在当前的module下) // FileInputStream fis = new FileInputStream("jdbc.properties"); // properties.load(fis); //方式二:(文件相对路径默认在当前的包下) ClassLoader classLoader = ClassLoaderTest.class.getClassLoader(); InputStream is = classLoader.getResourceAsStream("jdbc1.properties"); properties.load(is); System.out.println(properties.getProperty("user")); System.out.println(properties.getProperty("password")); }
4. 创建运行时类的对象
newInstance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参构造器
- 要想此方法正常的创建运行时类的对象,要求:
- 1,运行时类必须提供空参的构造器
- 2,空参的构造器的访问权限得够,通常为public
在javabean中要求提供一个public的空参构造器
- 1,便于通过反射,创建运行时类的对象
- 2,便于子类继承此运行时类,调用super(),保证父类有此构造器
@Test
public void test1() throws IllegalAccessException, InstantiationException {
Class<Person> clazz = Person.class;
Person person = clazz.newInstance();
System.out.println(person);
}
5.获取运行时类的完整结构
5.1 获取对象的属性结构
1.通过下列任意一个方法访问成员变量时将返回 Field 类型的对象或数组。
- getFields():获取当前运行时类及其父类中声明为public访问权限的属性数组
- getField(String name)
- getDeclaredFields():获取当前运行时类中声明的所有属性。不包含父类的属性
- getDeclaredField(String name)
public void test1(){
Class<Person> clazz = Person.class;
Field[] fields = clazz.getFields();
for (Field f:
fields) {
System.out.println(f);
}
}
2.获取属性的具体信息
方法名称 | 说明 |
---|---|
getName() | 获得该成员变量的名称 |
getType() | 获取表示该成员变量的 Class 对象 |
get(Object obj) | 获得指定对象 obj 中成员变量的值,返回值为 Object 类型 |
set(Object obj, Object value) | 将指定对象 obj 中成员变量的值设置为 value |
getlnt(0bject obj) | 获得指定对象 obj 中成员类型为 int 的成员变量的值 |
setlnt(0bject obj, int i) | 将指定对象 obj 中成员变量的值设置为 i |
setFloat(Object obj, float f) | 将指定对象 obj 中成员变量的值设置为 f |
getBoolean(Object obj) | 获得指定对象 obj 中成员类型为 boolean 的成员变量的值 |
setBoolean(Object obj, boolean b) | 将指定对象 obj 中成员变量的值设置为 b |
getFloat(Object obj) | 获得指定对象 obj 中成员类型为 float 的成员变量的值 |
setAccessible(boolean flag) | 此方法可以设置是否忽略权限直接访问 private 等私有权限的成员变量 |
getModifiers() | 获得可以解析出该方法所采用修饰符的整数 |
5.2 获取运行时类的方法结构
1.要动态获取一个对象方法的信息,首先需要通过下列方法之一创建一个 Method
类型的对象或者数组。
- getMethods()
- getMethods(String name,Class<?> …parameterTypes)
- getDeclaredMethods()
- getDeclaredMethods(String name,Class<?>…parameterTypes)
2.获取方法的具体信息
静态方法名称 | 说明 |
---|---|
getName() | 获取该方法的名称 |
getParameterType() | 按照声明顺序以 Class 数组的形式返回该方法各个参数的类型 |
getReturnType() | 以 Class 对象的形式获得该方法的返回值类型 |
getExceptionTypes() | 以 Class 数组的形式获得该方法可能抛出的异常类型 |
invoke(Object obj,Object…args) | 利用 args 参数执行指定对象 obj 中的该方法,返回值为 Object 类型 |
isVarArgs() | 查看该方法是否允许带有可变数量的参数,如果允许返回 true,否则返回 false |
getModifiers() | 获得可以解析出该方法所采用修饰符的整数 |
5.3 获取运行时类的构造器
1.为了能够动态获取对象构造方法的信息,首先需要通过下列方法之一创建一个 Constructor
类型的对象或者数组。
- getConstructors()
- getConstructor(Class<?>…parameterTypes)
- getDeclaredConstructors()
- getDeclaredConstructor(Class<?>…parameterTypes)
2.创建的每个 Constructor 对象表示一个构造方法,然后利用 Constructor 对象的方法操作构造方法。
方法名称 | 说明 |
---|---|
isVarArgs() | 查看该构造方法是否允许带可变数量的参数,如果允许,返回 true,否则返回 false |
getParameterTypes() | 按照声明顺序以 Class 数组的形式获取该构造方法各个参数的类型 |
getExceptionTypes() | 以 Class 数组的形式获取该构造方法可能抛出的异常类型 |
newInstance(Object … initargs) | 通过该构造方法利用指定参数创建一个该类型的对象,如果未设置参数则表示 采用默认无参的构造方法 |
setAccessiable(boolean flag) | 如果该构造方法的权限为 private,默认为不允许通过反射利用 netlnstance() 方法创建对象。如果先执行该方法,并将入口参数设置为 true,则允许创建对 象 |
getModifiers() | 获得可以解析出该构造方法所采用修饰符的整数 |
3.通过 java.lang.reflect.Modifier 类可以解析出 getMocMers() 方法的返回值所表示的修饰符信息。在该类中提供了一系列用来解析的静态方法,既可以查看是否被指定的修饰符修饰,还可以字符串的形式获得所有修饰符。
静态方法名称 | 说明 |
---|---|
isStatic(int mod) | 如果使用 static 修饰符修饰则返回 true,否则返回 false |
isPublic(int mod) | 如果使用 public 修饰符修饰则返回 true,否则返回 false |
isProtected(int mod) | 如果使用 protected 修饰符修饰则返回 true,否则返回 false |
isPrivate(int mod) | 如果使用 private 修饰符修饰则返回 true,否则返回 false |
isFinal(int mod) | 如果使用 final 修饰符修饰则返回 true,否则返回 false |
toString(int mod) | 以字符串形式返回所有修饰符 |
6. 调用运行时类的指定结构
6.1如何操作运行时类的指定属性
@Test
public void test1() throws Exception {
Class<Person> clazz = Person.class;
//创建运行时的对象
Person person = clazz.newInstance();
//获取指定的属性
// //一般不使用该方法
// Field field = clazz.getField("age");
//使用该方法可以获取到指定属性,但不能改变私有属性
Field field = clazz.getDeclaredField("age");
//必须加上,保证当前属性可以访问
field.setAccessible(true);
//设置当前属性的值
//set():第一个参数为对象,第二个参数是值
field.set(person,100);
//get():获取该属性的值
System.out.println(field.get(person));
}
2.如何操作运行时类的指定方法
@Test
public void test2() throws Exception {
Class<Person> clazz = Person.class;
Person person = clazz.newInstance();
//获取指定的某个方法
//getDeclaredMethod():参数1:指明获取的方法名称,参数二:指明获取方法的形参列表
Method show = clazz.getDeclaredMethod("showNation", String.class);
show.setAccessible(true);
//invoke():参数1:方法的调用者,参数2,:实参
//invoke()的返回值即为对应类调用方法的返回值
Object hello = show.invoke(person, "Hello");
System.out.println(hello);
//如何调用static方法
Method showStatic = clazz.getDeclaredMethod("showStatic");
showStatic.setAccessible(true);
showStatic.invoke(Person.class);
}
3.如何操作运行时类的指定构造器
@Test
public void test3() throws Exception {
Class<Person> clazz = Person.class;
//1.获取指定构造器
//getDeclaredConstructor():参数:指明构造器的参数列表
Constructor<Person> constructor = clazz.getDeclaredConstructor(String.class);
//2.可访问
constructor.setAccessible(true);
//3.调用此构造器的对象
Person zs = constructor.newInstance("张三");
System.out.println(zs);
}
7.动态代理(反射的应用)
代理设计模式的原理:
- 使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
- 静态代理的特征是代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。同时,每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。最好可以通过一个代理类完成全部的代理功能。
动态代理:
动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。
- 动态代理使用场合:
- 调试
- 远程方法调用
- 动态代理相比于静态代理的优点:
- 抽象角色中(接口)声明的所有方法都被转移到调用处理器一个集中的方法中处理,这样,我们可以更加灵活和统一的处理众多的方法。