Java基础学习——第十五章 Java反射机制
一、反射机制概述
1. 反射的基本概念
- Reflection(反射)是被视为**动态语言**的关键,反射机制允许程序==在运行时借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法==
- 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。 我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构。所以,我们形象的称之为:反射
2. 反射机制提供的功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* 反射的功能演示
*/
public class ReflectionTest {
//反射之前,对于Person类的操作
@Test
public void test1() {
//1. 创建Person类的对象
Person p1 = new Person("Tom", 12);
//2. 通过对象,调用其内部的属性和方法
p1.age = 10;
System.out.println(p1.toString());
p1.show();
//在Person类的外部,不能通过Person类的对象调用其内部的私有结构
//比如:name、showNation()以及私有的构造器
}
//反射之后,对于Person类的操作
@Test
public void test2() throws Exception {
//1. 通过反射,创建Person类的对象
Class clazz = Person.class;
//获取Person类的构造器
Constructor cons = clazz.getConstructor(String.class, int.class);
//获取Person类的对象
Object obj = cons.newInstance("Tom", 12);
Person p = (Person) obj;
System.out.println(p); //Person{name='Tom', age=12}
//2. 通过反射,调用对象指定的属性和方法
//调用属性
Field age = clazz.getDeclaredField("age");
age.set(p, 10);
System.out.println(p.toString()); //Person{name='Tom', age=10}
// 调用方法
Method show = clazz.getDeclaredMethod("show");
show.invoke(p); //你好,我是一个人
//通过反射,可以在Person类的外部调用其私有结构,比如:name、showNation()以及私有的构造器
//调用私有的构造器
Constructor cons1 = clazz.getDeclaredConstructor(String.class);
cons1.setAccessible(true);
Person p1 = (Person) cons1.newInstance("Jerry");
System.out.println(p1);
//调用私有的属性
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(p1, "HanMeimei");
System.out.println(p1);
//调用私有的方法
Method showNation = clazz.getDeclaredMethod("showNation", String.class);
showNation.setAccessible(true);
String nation = (String) showNation.invoke(p1, "China"); //相当于p1.showNation("中国")
System.out.println(nation); //China
}
}
3. 反射相关的主要API
- java.lang.Class:代表一个类
- java.lang.reflect.Method:代表类的方法
- java.lang.reflect.Field:代表类的成员变量
- java.lang.reflect.Constructor:代表类的构造器
4. 如何看待反射和封装性两个技术
- 疑问1:通过直接new对象的方式或反射的方式都可以调用类的公共结构,开发中到底用哪个?
- 建议:直接new对象来调用
- 什么时候会使用反射的方式:在编译时还无法确定要new哪个类的对象,在运行时通过反射调用类的内部结构(反射的特性:动态性)
- 疑问2:反射机制和面向对象中的封装性是不是矛盾的?如何看待两个技术?
- 反射机制的确可以在类的外部直接调用类的私有结构。但是封装性告诉我们,哪些结构是建议调用的,哪些结构是不建议调用的。比如单例模式中将构造器私有化,就是希望外部直接调用类内创建好的对象
二、理解Class类并获取Class实例
1. 理解Class类:反射的源头
1.1 回顾类的加载过程
- 使用 javac.exe 命令编译**“.java”** 结尾的源文件后,会生成一个或多个字节码文件(以 “.class” 结尾);再使用 java.exe 命令对某个字节码文件进行解释运行,相当于把某个字节码文件加载到内存中,此过程就被称为类的加载。加载到内存中的类即为运行时类,作为Class类的一个实例对象(类本身也可以作为一个对象)
- 一个加载到内存中的运行时类,在JVM中只会有唯一的Class类对象
1.2 Class类概述
- Class本身也是一个类,表示类的类
- Class类的对象表示正在运行的 Java 应用程序中的类和接口(运行时状态)
- 加载到内存中的运行时类,会缓存一段时间。在此期间,我们可以通过不同的方式来访问此运行时类(Class类的对象)
- 一个Class类的对象对应的是一个加载到JVM中的字节码文件
- 通过Class类可以获取一个类中所有被加载的结构
- Class类是反射的根源,对于任何想要动态加载、运行的类,唯有先获得其对应的Class类的对象
1.3 Class类的常用方法
方法名 | 功能说明 |
---|---|
static Class forName(String name) | 返回指定全类名(包括package名在内的类的完整路径)的Class 对象 |
Object newInstance() | 调用缺省构造函数,返回该Class对象的一个实例 |
getName() | 返回此Class对象所表示的实体(类、接口、数组类、基本类型或void)名称 |
Class getSuperClass() | 返回当前Class对象(运行时类)的父类的Class对象 |
Class [] getInterfaces() | 获取当前Class对象的接口 |
ClassLoader getClassLoader() | 返回该类的类加载器 |
Class getSuperclass() | 返回表示此Class所表示的实体的超类的Class对象 |
Constructor[] getConstructors() | 返回一个包含某些Constructor对象的数组 |
Field[] getDeclaredFields() | 返回一个包含某些Field对象的数组 |
Method getMethod(String name, Class …paramTypes) | 返回一个Method对象,此对象的形参类型为paramType |
2. 如何获取Class类的实例(4种方法)
- 方式一:若已知具体的运行时类,**调用运行时类的class属性**获取Class类的对象。该方法最为安全可靠,程序性能最高
- Class是一个泛型类,在实例化Class类的对象时,可以将当前运行时类作为Class类的泛型参数
Class clazz1 = Person.class; //Class<Person> clazz = Person.class
- 方式二:若已知某个运行时类的对象,**调用运行时类的对象的getClass()方法**获取Class类的对象
- 在Object类中定义了getClass()方法:public final Class getClass();返回值为Class类的对象
Person p1 = new Person();
Class clazz2 = p1.getClass(); //Class<? extends Person> clazz2 = p1.getClass();
- 方式三:若已知一个类的全类名,且该类在类路径下,可**调用Class类的静态方法forName()**获取Class类对象
- 全类名:包括package名在内的类的完整路径
- forName()方法可能会抛ClassNotFoundException异常
- 在开发时,一般通过forName()方法来获取Class类的实例
Class clazz3 = Class.forName("test.Person"); //Class<?> clazz3 = Class.forName("test.Person");
- 方式四:使用类的加载器:ClassLoader(了解即可)
//获取类的加载器(ClassLoader类的对象)
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
//调用类的加载器的loadClass方法
Class clazz4 = classLoader.loadClass("test.Person");//Class<?> clazz4 = classLoader.loadClass("test.Person");
@Test
public void test3() throws ClassNotFoundException {
//方式一:调用运行时类的class属性
Class<Person> clazz1 = Person.class;
System.out.println(clazz1); //class test.Person
//方式二:调用运行时类的对象的getClass()方法
Person p1 = new Person();
Class<? extends Person> clazz2 = p1.getClass();
System.out.println(clazz2); //class test.Person
//方式三:调用Class类的静态方法forName()
Class<?> clazz3 = Class.forName("test.Person");
System.out.println(clazz3); //class test.Person
//方式四:使用类的加载器:ClassLoader
//获取类的加载器(ClassLoader类的对象)
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
//调用类的加载器的loadClass方法
Class<?> clazz4 = classLoader.loadClass("test.Person");
System.out.println(clazz4); //class test.Person
//一个加载到内存中的运行时类,在JVM中只会有唯一的Class类对象,因此这些引用实际都指向同一个Class类对象
System.out.println(clazz1 == clazz2); //true
System.out.println(clazz1 == clazz3); //true
System.out.println(clazz1 == clazz4); //true
}
3. 哪些结构可以作为Class类的实例
- class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
- interface:接口
- []:数组
- enum:枚举类
- annotation:注解@interface
- primitive type:基本数据类型
- void
@Test
public void test4() {
Class c1 = Object.class; //类
Class c2 = Comparable.class; //接口
Class c3 = String[].class; //一维数组
Class c4 = int[][].class; // 二维数组
Class c5 = ElementType.class; //枚举类
Class c6 = Override.class; //注解
Class c7 = int.class; //基本数据类型
Class c8 = void.class; //void
Class c9 = Class.class; //Class类本身
int[] a = new int[10]; //元素类型为int型的一维数据
int[] b = new int[100]; //元素类型为int型的一维数据
Class c10 = a.getClass();
Class c11 = b.getClass();
//只要数组的元素类型与维度一致,就指向同一个Class类的对象
System.out.println(c10 == c11); //true
}
三、理解类的加载过程 & 类的加载器ClassLoader*
1. 类的加载过程
- 当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化:
- 加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口(即引用地址)。只能通过这个Class对象来访问和使用类数据。这个加载的过程需要类加载器参与。
- 链接:将Java类的二进制代码合并到JVM的运行状态之中
- 验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
- 初始化:
- 执行类构造器<clinit>()方法的过程。 类构造器<clinit>()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)
- 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
- 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步
public class ClassLoadingTest {
public static void main(String[] args) {
System.out.println(A.m); //100
}
}
class A {
static {
m = 300;
}
static int m = 100;
}
//第二步:链接结束后m=0
//第三步:初始化后,m的值由<clinit>()方法执行决定
// 这个A的类构造器<clinit>()方法由类变量的赋值和静态代码块中的语句按照顺序合并产生,类似于
// <clinit>(){
// m = 300;
// m = 100;
// }
2. 类的加载器ClassLoader
- 类加载器的作用:把类(class)装载进内存中
- 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口(即引用地址)。只能通过这个Class对象来访问和使用类数据
- 类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。 在此期间,我们可以通过不同的方式来访问此运行时类(唯一的Class类对象)。JVM垃圾回收机制可以回收这些Class对象
- 类加载器的类型(3种):
- 引导类加载器(Bootstrap Classloader):由C++编写,是JVM自带的类加载器,负责Java平台核心库的加载,无法直接获取
- 扩展类加载器(Extension Classloader):负责jre/lib/ext目录下的jar包或 –D java.ext.dirs 指定目录下的jar包的加载
- 系统类加载器(System Classloader):负责java –classpath 或 –D java.class.path所指的目录下的类与jar包的加载,最常用;自定义类的都是由系统类加载器加载到内存中的
public class ClassLoaderTest {
@Test
public void test1() throws ClassNotFoundException {
//1. 自定义类的加载器为系统类加载器
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);
//2.获取系统类加载器的父类加载器,即扩展类加载器
ClassLoader classLoader1 = classLoader.getParent();
System.out.println(classLoader1);
//3.获取扩展类加载器的父类加载器,即引导类加载器
ClassLoader classLoader2 = classLoader1.getParent();
System.out.println(classLoader2); //null 引导类加载器无法直接获取到
ClassLoader classLoader3 = Class.forName("java.lang.String").getClassLoader();
System.out.println(classLoader3); //null 引导类加载器无法直接获取到
}
}
3. 使用ClassLoader加载配置文件
- Properties 是 Hashtable 的子类,其对象用于处理配置文件
- 调用类加载器的getResourceAsStream()方法,获取一个对应配置文件的字节输入流
@Test
public void test2() throws IOException {
Properties pros = new Properties();
//读取配置文件的方式一:使用节点流FileInputStream
//因为使用了单元测试方法,此时的配置文件默认在当前Module下
//FileInputStream fis = new FileInputStream("jdbc.properties");
//pros.load(fis);
//读取配置文件的方式二:使用ClassLoader的getResourceAsStream()方法
//获取一个系统类加载器
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
//调用类加载器的getResourceAsStream()方法,获取一个对应配置文件的字节输入流
//此时的配置文件默认在当前Module的src目录下
InputStream is = classLoader.getResourceAsStream("jdbc1.properties");
pros.load(is);
String user = pros.getProperty("user");
String password = pros.getProperty("password");
System.out.println("user = " + user + ", password = " + password);
}
四、通过反射创建运行时类的对象
1. 方法一:调用Class对象的newInstance()方法
- 方法:调用运行时类对应的Class对象的newInstance()方法,创建该运行时类的对象
- newInstance()方法内部调用了运行时类的空参构造器
- 使用newInstance()方法创建运行时类对象的要求:
- 该运行时类必须提供空参的构造器,不满足时会报InstantiationException异常
- 需要满足构造器的访问权限,不满足时会报IllegalAccessException异常;通常空参构造器的权限设置为public
@Test
public void test1() throws IllegalAccessException, InstantiationException {
//1. 获取运行时类对应的Class对象
Class<Person> clazz = Person.class;
//2. 调用该Class对象的newInstance()方法,创建对应的运行时类的对象
Person p = clazz.newInstance(); //newInstance()方法内部调用了运行时类的空参构造器
System.out.println(p);
}
- 在JavaBean中要求提供一个public的空参构造器,其原因是:
- 使得可以通过反射机制,创建运行时类的对象
- 便于子类继承该Bean时,子类的构造器中可以默认调用super();
2. 方法二:获取运行时类的指定构造器*
- 调用Class类对象的getDeclaredConstructor(Class … parameterTypes),获取运行时类的指定构造器对象(Constructor类对象)
- 调用构造器对象(Constructor类对象)的newInstance()方法,创建该运行时类的对象
@Test
public void test2() throws Exception {
//1. 获取运行时类对应的Class对象
Class<Person> clazz = Person.class;
//获取Person类的构造器对象(Constructor类对象)
Constructor<Person> cons = clazz.getDeclaredConstructor(String.class, int.class);
//调用构造器对象(Constructor类对象)的newInstance()方法,创建该运行时类的对象
Person p = cons.newInstance("Tom", 12);
System.out.println(p); //Person{name='Tom', age=12}
}
3. 举例:体会反射的动态性
@Test
public void test3() {
int num = new Random().nextInt(3);//0, 1, 2
String classPath = "";
switch (num) {
case 0:
classPath = "java.util.Date";
break;
case 1:
classPath = "java.lang.Object";
break;
case 2:
classPath = "test.Person";
break;
}
Object obj = null;
try {
obj = getInstance(classPath);
System.out.println(obj);
} catch (Exception e) {
e.printStackTrace();
}
}
/*
创建一个指定运行时类的对象
classPath:指定运行时类的全类名
*/
public Object getInstance(String classPath) throws Exception {
Class<?> clazz = Class.forName(classPath);
return clazz.newInstance();
}
五、通过反射获取运行时类的完整结构
- 通过反射可以获取运行时类的:
- 属性(Field)
- 方法(Method)
- 构造器(Constructor)
- 父类(Superclass)
- 实现的接口(Interface)
- 注解(Annotation)
1. 获取运行时类的属性
- 获取运行时类的属性:
- public Field[] getFields():返回此Class对象对应的运行时类及其父类中声明为public的所有属性
- public Field[] getDeclaredFields():返回此Class对象对应的运行时类中声明的所有属性(不考虑权限,且不包含父类的属性)
- 获取运行时类的属性的内部结构:修饰符、数据类型、变量名:
- public int getModifiers():以int类型返回当前属性的修饰符,该修饰符是java.lang.reflect.Modifier中的静态属性
- public Class<?> getType():返回当前属性的数据类型
- public String getName():返回当前属性的变量名
import org.junit.Test;
import test1.Person;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
/**
* 获取运行时类的属性结构
*/
public class FieldTest {
//获取运行时类的属性
@Test
public void test1() {
Class<Person> clazz = Person.class;
//public Field[] getFields():返回此Class对象对应的运行时类及其父类中声明为public的所有属性
Field[] fields = clazz.getFields();
for (Field f : fields) {
System.out.println(f);
}
//public Field[] getDeclaredFields():返回此Class对象对应的运行时类中声明的所有属性(不考虑权限,且不包含父类的属性)
Field[] declaredFields = clazz.getDeclaredFields();
for (Field f : declaredFields) {
System.out.println(f);
}
}
//获取运行时类的属性的内部结构:修饰符、数据类型、变量名
@Test
public void test2() {
Class<Person> clazz = Person.class;
Field[] declaredFields = clazz.getDeclaredFields();
for (Field f : declaredFields) {
//1. public int getModifiers():以int类型返回当前属性的修饰符
int modifiers = f.getModifiers();
System.out.print(Modifier.toString(modifiers) + "\t");
//2. public Class\<?> getType():返回当前属性的数据类型
Class<?> type = f.getType();
System.out.print(type.getName() + "\t");
//3. public String getName():返回当前属性的变量名
String name = f.getName();
System.out.println(name);
}
}
}
2. 获取运行时类的方法
- 获取运行时类的方法:
- public Method[] getMethods():返回此Class对象对应的运行时类及其父类中声明为public的所有方法
- public Method[] getDeclaredMethods():返回此Class对象对应的运行时类中声明的所有方法(不考虑权限,且不包含父类的方法)
- 获取运行时类的方法的内部结构:注解、修饰符、返回值类型、方法名、形参列表、异常信息:
- public Annotation[] getAnnotations():返回当前方法声明的注解
- public int getModifiers():以int类型返回当前方法的修饰符,该修饰符是java.lang.reflect.Modifier中的静态属性
- public Class<?> getReturnType():返回当前方法的返回值类型
- public String getName():返回当前方法的方法名
- public Class<?>[] getParameterTypes():返回当前方法的形参类型
- public Class<?>[] getExceptionTypes():返回当前方法的异常类型
import org.junit.Test;
import test1.Person;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* 获取运行时类的方法结构
*/
public class MethodTest {
//获取运行时类的方法
@Test
public void test1() {
Class<Person> clazz = Person.class;
//public Method[] getMethods():返回此Class对象对应的运行时类及其父类中声明为public的所有方法
Method[] methods = clazz.getMethods();
for (Method m : methods) {
System.out.println(m);
}
//public Field[] getDeclaredFields():返回此Class对象对应的运行时类中声明的所有方法(不考虑权限,且不包含父类的方法)
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method m : declaredMethods) {
System.out.println(m);
}
}
//获取运行时类的方法的内部结构:
@Test
public void test2() {
Class<Person> clazz = Person.class;
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method m : declaredMethods) {
//1. public Annotation[] getAnnotations():返回当前方法声明的注解
Annotation[] annotations = m.getAnnotations();
for (Annotation a : annotations) {
System.out.println(a);
}
//2. public int getModifiers():返回当前方法的修饰符
int modifiers = m.getModifiers();
System.out.print(Modifier.toString(modifiers) + "\t");
//3. public Class<?> getReturnType():返回当前方法的返回值类型
Class<?> returnType = m.getReturnType();
System.out.print(returnType.getName() + "\t");
//4. public String getName():返回当前方法的方法名
String name = m.getName();
System.out.print(name + "\t");
System.out.print("(");
//5. public Class<?>[] getParameterTypes():返回当前方法的形参的类型
Class<?>[] parameterTypes = m.getParameterTypes();
if (!(parameterTypes == null || parameterTypes.length == 0)) {
for (int i = 0; i < parameterTypes.length; i++) {
if (i == parameterTypes.length - 1) {
System.out.print(parameterTypes[i].getName() + " args_" + i);
break;
}
System.out.print(parameterTypes[i].getName() + " args_" + i + ", ");
}
}
System.out.print(") ");
//6. public Class<?>[] getExceptionTypes():返回当前方法的异常类型
Class<?>[] exceptionTypes = m.getExceptionTypes();
if (!(exceptionTypes == null || exceptionTypes.length == 0)) {
System.out.print("throws ");
for (int i = 0; i < exceptionTypes.length; i++) {
if (i == exceptionTypes.length - 1) {
System.out.print(exceptionTypes[i].getName());
break;
}
System.out.print(exceptionTypes[i].getName() + ", ");
}
}
System.out.println();
}
}
}
3. 获取运行时类的构造器
- 获取运行时类的构造器:
- public Constructor<T>[] getConstructors():返回此Class对象对应的运行时类中声明为public的所有构造器(不包含父类中的构造器)
- public Constructor<T>[] getDeclaredConstructors():返回此Class对象对应的运行时类中声明的所有构造器(不考虑权限)
- 获取运行时类的构造器的内部结构:注解、修饰符、返回值类型、方法名、形参列表、异常信息:
- public int getModifiers():以int类型返回当前构造器的修饰符,该修饰符是java.lang.reflect.Modifier中的静态属性
- public String getName():返回当前构造器的名称
- public Class<?>[] getParameterTypes():返回当前方法的形参类型
import org.junit.Test;
import test1.Person;
import java.lang.reflect.Constructor;
/**
* 获取运行时类的构造器结构
*/
public class ConstructorTest {
@Test
public void test1() {
Class<Person> clazz = Person.class;
//public Constructor<T>[] getConstructors():返回此Class对象对应的运行时类中声明为public的所有构造器
Constructor<?>[] constructors = clazz.getConstructors();
for (Constructor<?> c : constructors) {
System.out.println(c);
}
//public Constructor<T>[] getDeclaredConstructors():返回此Class对象对应的运行时类的所有构造器(不考虑权限)
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor<?> c : declaredConstructors) {
System.out.println(c);
}
}
}
4. 获取运行时类的父类及父类的泛型
- public Class<? Super T> getSuperclass():返回此Class对象对应的运行时类的父类类型(不包含父类的父类)
- public Type getGenericSuperclass():返回此Class对象对应的运行时类带泛型的父类类型
- Type[] getActualTypeArguments():获取实际的泛型类型参数数组
import org.junit.Test;
import test1.Person;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
/**
* 获取运行时类的父类及父类的泛型
*/
public class SuperClassTest {
//获取运行时类的父类类型以及带泛型的父类类型
@Test
public void test1() {
Class<Person> clazz = Person.class;
//1. public Class<? Super T> getSuperclass():返回此Class对象对应的运行时类的父类类型
Class<? super Person> superclass = clazz.getSuperclass();
System.out.println(superclass); //class test1.Creature
//2. public Type getGenericSuperclass():返回此Class对象对应的运行时类带泛型的父类类型
Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println(genericSuperclass); //test1.Creature<java.lang.String>
}
//获取运行时类的父类的泛型类型
@Test
public void test2() {
Class<Person> clazz = Person.class;
//public Type getGenericSuperclass():返回此Class对象对应的运行时类带泛型的父类类型
Type genericSuperclass = clazz.getGenericSuperclass();
ParameterizedType paramType = (ParameterizedType) genericSuperclass;
//获取实际的泛型类型参数数组: getActualTypeArguments()
Type[] actualTypeArguments = paramType.getActualTypeArguments();
System.out.println(actualTypeArguments[0].getTypeName()); //java.lang.String
}
}
5. 获取运行时类实现的接口
- public Class<?>[] getInterfaces():返回此Class对象对应的运行时类实现的所有接口(不包含父类实现的接口)
@Test
public void test3() {
Class<Person> clazz = Person.class;
Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> i : interfaces) {
System.out.println(i.getName());
}
//获取该运行时类父类实现的接口
Class<?>[] interfaces1 = clazz.getSuperclass().getInterfaces();
for (Class<?> i : interfaces1) {
System.out.println(i.getName());
}
}
6. 获取运行时类所在的package(包)
- Package getPackage():返回此Class对象对应的运行时类所在的package(包)
@Test
public void test4() {
Class<Person> clazz = Person.class;
Package aPackage = clazz.getPackage();
System.out.println(aPackage); //package test1
}
7. 获取运行时类声明的注解
- public Annotation[] getAnnotations():返回此Class对象对应的运行时类声明的所有注解
@Test
public void test5() {
Class<Person> clazz = Person.class;
Annotation[] annotations = clazz.getAnnotations();
for (Annotation a : annotations) {
System.out.println(a);
}
}
六、通过反射调用运行时类的指定结构
1. 调用运行时类中的指定属性
- 在反射中,可以直接通过Field类操作运行时类中的属性,通过Field类提供的set()和get()方法就可以完成设置和获取属性的操作
- Class类对象获取指定属性的两个方法:
- public Field getField(String name):返回此Class对象对应的运行时类中指定变量名的public的属性
- public Field getDeclaredField(String name):返回此Class对象对应的运行时类中指定变量名的属性(不考虑权限)
- Field类提供的set()和get()方法:
- public void set(Object obj, Object value):设置指定运行时类的对象obj的当前属性的值为value
- public Object get(Object obj):获取指定运行时类的对象obj的当前属性的值
1.1 setAccessible()方法的使用
- Method、Field 和 Constructor 类中都声明了setAccessible()方法,可以通过它们的对象直接调用
- public void setAccessible(boolean flag):用于启动和禁用访问安全检查的开关
- 参数值为true:表明运行时类的指定结构在使用时应取消Java语言访问检查,使得原本无法访问的私有成员也可以被访问
- 参数值为false:表明运行时类的指定结构应该实施Java语言访问检查
@Test
public void testFiled1() throws Exception {
Class<Person> clazz = Person.class;
//创建运行时类的对象
Person p = clazz.newInstance();
//1. public Field getField(String name):返回此Class对象对应的运行时类中指定的public的属性
Field id = clazz.getField("id");
//public void set(Object obj, Object value):设置指定运行时类的对象obj的当前属性的值为value
id.set(p, 1001);
//public Object get(Object obj):获取指定运行时类的对象obj的当前属性的值
Object o = id.get(p);
int pId = (int) o;
System.out.println(pId);
}
@Test
public void testFile2() throws Exception {
Class<Person> clazz = Person.class;
//创建运行时类的对象
Person p = clazz.newInstance();
//1. public Field getDeclaredField(String name):返回此Class对象对应的运行时类中指定的属性(不考虑权限)
Field name = clazz.getDeclaredField("name");
//2. Method和Field、Constructor对象都有setAccessible()方法:保证当前属性是可访问的
name.setAccessible(true);
//public void set(Object obj, Object value):设置指定运行时类的对象obj的当前属性的值为value
name.set(p, "Tom");
//public Object get(Object obj):获取指定运行时类的对象obj的当前属性的值
System.out.println(name.get(p));
}
2. 调用运行时类中的指定方法
- 在反射中,可以直接通过Method类提供的invoke()方法调用运行时类的方法
- Class类对象获取指定方法的两个方法:
- public Method getMethod(String name, Class<?>… parameterTypes):返回此Class对象对应的运行时类中指定方法名和形参列表的public的方法
- public Method getDeclaredMethod(String name, Class<?>… parameterTypes):返回此Class对象对应的运行时类中指定方法名和形参列表的方法(不考虑权限)
- Method类提供的invoke()方法:
- public Object invoke(Object obj, Object[] args):通过指定运行时类的对象obj来调用当前运行时类的方法,并将形参数组传递给该方法;invoke()的返回值即为当前运行时类的方法的返回值
- Object 对应原方法的返回值,若原方法无返回值,则返回null
- 若原方法为静态方法,此时形参Object obj(方法的调用者)可以是 类名.class 或 null
- 若原方法形参列表为空,则Object[] args为null
- 若原方法声明为private,则需在调用此invoke()方法前,显式调用方法对象的setAccessible(true)方法,确保能访问该方法
- public Object invoke(Object obj, Object[] args):通过指定运行时类的对象obj来调用当前运行时类的方法,并将形参数组传递给该方法;invoke()的返回值即为当前运行时类的方法的返回值
@Test
public void testMethod() throws Exception {
Class<Person> clazz = Person.class;
//*********************************** 调用运行时类的非静态方法 ***********************************
//创建运行时类的对象
Person p = clazz.newInstance();
//1. public Method getDeclaredMethod(String name, Class<?>... parameterTypes):返回此Class对象对应的运行时类中指定方法名和形参列表的方法(不考虑权限)
Method show = clazz.getDeclaredMethod("show", String.class);
//2. Method和Field、Constructor对象都有setAccessible()方法:保证当前方法是可访问的
show.setAccessible(true);
//Object invoke(Object obj, Object[] args):通过指定运行时类的对象obj来调用当前方法,并将形参数组传递给该方法
//invoke()的返回值即为当前运行时类的方法的返回值
Object obj = show.invoke(p, "中国"); //相当于 String nation = p.show("中国");
String nation = (String) obj;
System.out.println(nation);
//*********************************** 调用运行时类的静态方法 ***********************************
//private static void showDesc()
Method showDesc = clazz.getDeclaredMethod("showDesc");
showDesc.setAccessible(true);
//如果调用的运行时类的方法没有返回值,则invoke()方法的返回值为null
Object returnVal = showDesc.invoke(Person.class);
System.out.println(returnVal); //null
}
3. 调用运行时类中的指定构造器
- Class类对象获取指定构造器的方法:
- public Constructor<T> getDeclaredConstructor(Class<?>… parameterTypes):返回此Class对象对应的运行时类中指定形参列表的构造器(不考虑权限)
- Constructor类提供的newInstance()方法:
- public T newInstance(Object … initargs):通过当前调用该方法的构造器对象创建该运行时类的对象
@Test
public void test2() throws Exception {
//获取运行时类对应的Class对象
Class<Person> clazz = Person.class;
//1. 获取Person类的构造器对象(Constructor类对象)
Constructor<Person> cons = clazz.getDeclaredConstructor(String.class, int.class);
//2. Method和Field、Constructor对象都有setAccessible()方法:保证当前构造器是可访问的
cons.setAccessible(true);
//调用构造器对象(Constructor类对象)的newInstance()方法,创建该运行时类的对象
Person p = cons.newInstance("Tom", 12);
System.out.println(p);
}
七、反射的应用:动态代理模式
1. 代理模式
代理模式是一种比较好理解的设计模式。简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作。
举个例子:你找了小红来帮你问话,小红就可以看作是代理你的代理对象,代理的行为(方法)是问话。
https://medium.com/@mithunsasidharan/understanding-the-proxy-design-pattern-5e63fe38052a
代理模式有静态代理和动态代理两种实现方式,我们先来看一下静态代理模式的实现。
2. 静态代理
静态代理中,我们对目标对象的每个方法的增强都是手动完成的(后面会具体演示代码),非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类)。 实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。
上面我们是从实现和应用角度来说的静态代理,从 JVM 层面来说, 静态代理在编译时就将接口、被代理类、代理类都确定下来了,变成了一个个实际的字节码文件。
- 静态代理实现步骤:
- 定义一个接口及其实现类(被代理类);
- 创建一个代理类同样实现这个接口
- 在代理类中声明一个该接口类型的引用(在多态情况下实际指向被代理类的对象),然后在代理类的对应方法中调用被代理类中的对应方法。这样的话,我们就可以通过代理类屏蔽对被代理类对象的访问,并且可以在目标方法执行前后做一些自己想做的事情(执行其他的方法)。
下面通过代码展示!
1.定义发送短信的接口
public interface SmsService {
String send(String message);
}
2.发送短信的接口的实现类(被代理类)
public class SmsServiceImpl implements SmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}
3.创建代理类并同样实现发送短信的接口
public class SmsProxy implements SmsService {
//在代理类中声明一个该接口类型的引用,在多态情况下指向被代理类的对象
private final SmsService smsService;
public SmsProxy(SmsService smsService) {
this.smsService = smsService;
}
@Override
public String send(String message) {
//调用方法之前,我们可以添加自己的操作
System.out.println("before method send()");
//在代理类的对应方法中调用被代理类中的对应方法
smsService.send(message);
//调用方法之后,我们同样可以添加自己的操作
System.out.println("after method send()");
return null;
}
}
4.实际使用
public class Main {
public static void main(String[] args) {
//创建被代理类的对象
SmsService smsService = new SmsServiceImpl();
//创建代理类的对象:将被代理类的对象作为参数传递给代理类的构造器,赋给smsService属性
SmsProxy smsProxy = new SmsProxy(smsService);
//通过代理类的对象调用该方法时,内部实际调用了被代理类的对应方法
smsProxy.send("java");
}
}
运行上述代码之后,控制台打印出:
before method send()
send message:java
after method send()
可以输出结果看出,我们已经增加了 SmsServiceImpl
的send()
方法。
3. 动态代理
相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类( CGLIB 动态代理机制)。
从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
说到动态代理,Spring AOP、RPC 框架应该是两个不得不提的,它们的实现都依赖了动态代理。
动态代理在我们日常开发中使用的相对较少,但是在框架中的几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助。
就 Java 来说,动态代理的实现方式有很多种,比如 JDK 动态代理、CGLIB 动态代理等等。
guide-rpc-framework 使用的是 JDK 动态代理,我们先来看看 JDK 动态代理的使用。
另外,虽然 guide-rpc-framework 没有用到 CGLIB 动态代理 ,我们这里还是简单介绍一下其使用以及和JDK 动态代理的对比。
3.1. JDK 动态代理机制
3.1.1. 介绍
在 Java 动态代理机制中 InvocationHandler
接口和 Proxy
类是核心。
Proxy
类中使用频率最高的方法是:newProxyInstance()
,这个方法主要用来生成一个动态代理对象:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
......
}
这个方法一共有 3 个参数:
- loader:类加载器,用于加载动态代理对象。一般指定为被代理类的类加载器
- interfaces:代理类需要实现的一些接口。一般指定为被代理类实现的接口,代理类与被代理类需要实现同样的接口
- h:实现了
InvocationHandler
接口的动态代理类的对象;
要实现动态代理的话,还必须定义一个InvocationHandler
接口的实现类(动态代理类)来自定义处理逻辑。 当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler
接口的实现类(动态代理类)的 invoke
方法来调用。
public interface InvocationHandler {
/**
* 当使用代理对象调用方法的时候实际会调用invoke()方法
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
invoke()
方法有下面三个参数:
- proxy :动态生成的代理类对象
- method : 代理类对象调用的方法,也是我们希望被代理类对象调用的方法
- args : 当前 method 方法的参数列表
也就是说:你通过Proxy
类的 newProxyInstance()
创建的代理对象在调用方法时,实际会调用实现InvocationHandler
接口的类(动态代理类)的 invoke()
方法。 你可以在 invoke()
方法中自定义处理逻辑,比如在方法执行前后做什么事情。
3.1.2. JDK 动态代理类使用步骤
- 定义一个接口及其实现类(被代理类);
- 定义一个 JDK 动态代理类,实现
InvocationHandler
接口并重写invoke()
方法,在invoke()
方法中调用被代理类的原生方法,并自定义一些处理逻辑 - 定义一个获取代理对象的工厂类,并在其中定义一个静态方法,该方法通过**
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
**方法创建动态代理对象 - 在实际使用时,我们通过将被代理类的对象传入工厂类的静态方法,来获取代理对象;当我们通过代理对象调用原生方法时,实际调用了实现
InvocationHandler
接口的类(动态代理类)的invoke()
方法,从而代替我们调用了被代理类的对应方法
3.1.3. 代码示例
这样说可能会有点空洞和难以理解,我上个例子,大家感受一下吧!
1.定义发送短信的接口
public interface SmsService {
String send(String message);
}
2.发送短信的接口的实现类(被代理类)
public class SmsServiceImpl implements SmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}
3.定义一个 JDK 动态代理类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class DebugInvocationHandler implements InvocationHandler {
//实际指向被代理类的对象
private final Object target;
public DebugInvocationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
//调用方法之前,我们可以添加自己的操作
System.out.println("before method " + method.getName());
//在 `invoke()` 方法中调用被代理类的原生方法
Object result = method.invoke(target, args);
//调用方法之后,我们同样可以添加自己的操作
System.out.println("after method " + method.getName());
//invoke()方法的返回值实际上就是被代理类方法的返回值
return result;
}
}
invoke()
方法: 当我们的动态代理对象调用原生方法的时候,最终实际上调用的是 invoke()
方法,然后 invoke()
方法代替我们去调用了被代理对象的原生方法
4.获取代理对象的工厂类
- 反射的体现
public class JdkProxyFactory {
public static Object getProxy(Object target) { //target:被代理类的对象
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), //被代理类的类加载器
target.getClass().getInterfaces(), //被代理类实现的接口(代理类与被代理类需要实现同样的接口)
new DebugInvocationHandler(target) //实现InvocationHandler接口的实现类(动态代理类)的对象
);
}
}
getProxy()
:通过Proxy.newProxyInstance()
方法获取某个类的代理对象。返回的代理对象为目标类实现的接口的一个实例
5.实际使用
//将被代理类的对象传入工厂类的静态方法,来获取代理对象
SmsService proxyInstance = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
//通过代理对象调用方法时,实际调用了实现InvocationHandler接口的类(动态代理类)的invoke()方法,从而调用了被代理类的对应方法
//invoke()方法的返回值实际上就是被代理类方法的返回值
String message = proxyInstance.send("java");
运行上述代码之后,控制台打印出:
before method send
send message:java
after method send
3.2. CGLIB 动态代理机制
3.2.1. 介绍
JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。
为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。
CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。很多知名的开源框架都使用到了CGLIB, 例如 Spring 中的 AOP(面向切面编程)模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。
在 CGLIB 动态代理机制中 MethodInterceptor
接口和 Enhancer
类是核心。
你需要自定义 MethodInterceptor
并重写 intercept
方法,intercept
用于拦截增强被代理类的方法。
public interface MethodInterceptor
extends Callback{
// 拦截被代理类中的方法
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
MethodProxy proxy) throws Throwable;
}
- obj :被代理的对象(需要增强的对象)
- method :被拦截的方法(需要增强的方法)
- args :方法入参
- proxy :用于调用原始方法
你可以通过 Enhancer
类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor
中的 intercept
方法。
3.2.2. CGLIB 动态代理类使用步骤
- 定义一个类;
- 自定义
MethodInterceptor
并重写intercept
方法,intercept
用于拦截增强被代理类的方法,和 JDK 动态代理中的invoke
方法类似; - 通过
Enhancer
类的create()
创建代理类;
3.2.3. 代码示例
不同于 JDK 动态代理不需要额外的依赖。CGLIB(Code Generation Library) 实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
1.实现一个使用阿里云发送短信的类
package github.javaguide.dynamicProxy.cglibDynamicProxy;
public class AliSmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}
2.自定义 MethodInterceptor
(方法拦截器)
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 自定义MethodInterceptor
*/
public class DebugMethodInterceptor implements MethodInterceptor {
/**
* @param o 代理对象(增强的对象)
* @param method 被拦截的方法(需要增强的方法)
* @param args 方法入参
* @param methodProxy 用于调用原始方法
*/
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//调用方法之前,我们可以添加自己的操作
System.out.println("before method " + method.getName());
Object object = methodProxy.invokeSuper(o, args);
//调用方法之后,我们同样可以添加自己的操作
System.out.println("after method " + method.getName());
return object;
}
}
3.获取代理类
import net.sf.cglib.proxy.Enhancer;
public class CglibProxyFactory {
public static Object getProxy(Class<?> clazz) {
// 创建动态代理增强类
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置被代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new DebugMethodInterceptor());
// 创建代理类
return enhancer.create();
}
}
4.实际使用
AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
aliSmsService.send("java");
运行上述代码之后,控制台打印出:
before method send
send message:java
after method send
3.3. JDK 动态代理和 CGLIB 动态代理对比
- JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
- 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。
4. 静态代理和动态代理的对比
- 灵活性 :动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!
- JVM 层面 :静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。