一、反射机制概述
1. 反射的基本概念
Reflection(反射)是被视为动态语言 的关键,反射机制允许程序==在运行时借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法 ==
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息 。 我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构。所以,我们形象的称之为:反射
2. 反射机制提供的功能
在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时判断任意一个类所具有的成员变量和方法
在运行时获取泛型信息
在运行时调用任意一个对象的成员变量和方法
在运行时处理注解
生成动态代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 import org.junit.Test;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;public class ReflectionTest { @Test public void test1 () { Person p1 = new Person ("Tom" , 12 ); p1.age = 10 ; System.out.println(p1.toString()); p1.show(); } @Test public void test2 () throws Exception { Class clazz = Person.class; Constructor cons = clazz.getConstructor(String.class, int .class); Object obj = cons.newInstance("Tom" , 12 ); Person p = (Person) obj; System.out.println(p); Field age = clazz.getDeclaredField("age" ); age.set(p, 10 ); System.out.println(p.toString()); Method show = clazz.getDeclaredMethod("show" ); show.invoke(p); 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" ); System.out.println(nation); } }
3. 反射相关的主要API
java.lang.Class:代表一个类
java.lang.reflect.Method:代表类的方法
java.lang.reflect.Field:代表类的成员变量
java.lang.reflect.Constructor:代表类的构造器
4. 如何看待反射和封装性两个技术
疑问1:通过直接new对象的方式或反射的方式都可以调用类的公共结构,开发中到底用哪个?
建议:==直接new对象来调用==
什么时候会使用反射的方式:在编译时还无法确定要new哪个类的对象,在运行时通过反射调用类的内部结构(反射的特性:动态性)
疑问2:反射机制和面向对象中的封装性是不是矛盾的?如何看待两个技术?
反射机制的确可以在类的外部直接调用类的私有结构。但是封装性告诉我们,哪些结构是建议调用的,哪些结构是不建议调用n的 。比如单例模式中将构造器私有化,就是希望外部直接调用类内创建好的对象
⭐二、理解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类的泛型参数
1 2 3 Class clazz1 = Person.class; 1
方式二
:若已知某个运行时类的对象,**
调用运行时类的对象的getClass()方法
**获取Class类的对象
在Object类中定义了getClass()方法:public final Class getClass();返回值为Class类的对象
1 2 3 Person p1 = new Person ();Class clazz2 = p1.getClass(); 12
方式三
:若已知一个类的全类名,且该类在类路径下,可调用Class类的静态方法forName()获取Class类对象
全类名:包括package名在内的类的完整路径
forName()方法可能会抛ClassNotFoundException异常
在开发时,一般通过forName()方法来获取Class类的实例
1 2 Class clazz3 = Class.forName("test.Person" ); 1
方式四:使用类的加载器:ClassLoader(了解即可)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ClassLoader classLoader = ReflectionTest.class.getClassLoader();Class clazz4 = classLoader.loadClass("test.Person" );1234 @Test public void test3 () throws ClassNotFoundException { Class<Person> clazz1 = Person.class; System.out.println(clazz1); Person p1 = new Person (); Class<? extends Person > clazz2 = p1.getClass(); System.out.println(clazz2); Class<?> clazz3 = Class.forName("test.Person" ); System.out.println(clazz3); ClassLoader classLoader = ReflectionTest.class.getClassLoader(); Class<?> clazz4 = classLoader.loadClass("test.Person" ); System.out.println(clazz4); System.out.println(clazz1 == clazz2); System.out.println(clazz1 == clazz3); System.out.println(clazz1 == clazz4); }
3. 哪些结构可以作为Class类的实例
class :外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
interface :接口
[] :数组
enum :枚举类
annotation :注解@interface
primitive type :基本数据类型
void
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @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; Class c9 = Class.class; int [] a = new int [10 ]; int [] b = new int [100 ]; Class c10 = a.getClass(); Class c11 = b.getClass(); System.out.println(c10 == c11); }
三、理解类的加载过程 & 类的加载器ClassLoader
1. 类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化:
加载 :将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口(即引用地址)。只能通过这个Class对象来访问和使用类数据。这个加载的过程需要类加载器参与。
链接
:将Java类的二进制代码合并到JVM的运行状态之中
验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题
准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配
解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
初始化
:
执行类构造器()方法的过程。 类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)
当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class ClassLoadingTest { public static void main (String[] args) { System.out.println(A.m); } } class A { static { m = 300 ; } static int 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包的加载,最常用;自定义类的都是由系统类加载器加载到内存中的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class ClassLoaderTest { @Test public void test1 () throws ClassNotFoundException { ClassLoader classLoader = ClassLoaderTest.class.getClassLoader(); System.out.println(classLoader); ClassLoader classLoader1 = classLoader.getParent(); System.out.println(classLoader1); ClassLoader classLoader2 = classLoader1.getParent(); System.out.println(classLoader2); ClassLoader classLoader3 = Class.forName("java.lang.String" ).getClassLoader(); System.out.println(classLoader3); } }
3. 使用ClassLoader加载配置文件(properties)
Properties 是 Hashtable 的子类,其对象用于处理配置文件
调用类加载器的getResourceAsStream()方法,获取一个对应配置文件的字节输入流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Test public void test2 () throws IOException { Properties pros = new Properties (); ClassLoader classLoader = ClassLoaderTest.class.getClassLoader(); 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test void t1 () throws InstantiationException, IllegalAccessException { Class<Person> clazz = Person.class; Person p1 = clazz.newInstance(); System.out.println(p1); }
在JavaBean中要求提供一个public的空参构造器,其原因是:
使得可以通过反射机制,创建运行时类的对象
便于子类继承该Bean时,子类的构造器中可以默认调用super();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Test public void t2 () throws ClassNotFoundException, InstantiationException, IllegalAccessException { while (true ) { int num = new Random ().nextInt(3 ); System.out.println(num); String classPath = "" ; switch (num + 1 ) { case 1 : classPath = "java.util.Date" ; break ; case 2 : classPath = "java.lang.Object" ; break ; case 3 : classPath = "pers.dhx_.java0608.Person" ; break ; } Object instance = getInstance(classPath); System.out.println(instance.getClass()); } } public Object getInstance (String classPath) throws ClassNotFoundException, InstantiationException, IllegalAccessException { Class clazz = Class.forName(classPath); return clazz.newInstance(); }
2. 方法二:获取运行时类的指定构造器
调用Class类对象的getDeclaredConstructor(Class … parameterTypes) ,获取运行时类的指定构造器对象(Constructor类对象)
调用构造器对象(Constructor类对象)的newInstance()方法 ,创建该运行时类的对象
1 2 3 4 5 6 7 8 9 10 11 12 @Test public void test2 () throws Exception { Class<Person> clazz = Person.class; Constructor<Person> cons = clazz.getDeclaredConstructor(String.class, int .class); Person p = cons.newInstance("Tom" , 12 ); System.out.println(p); }
3. 举例:体会反射的动态性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Test public void test3 () { int num = new Random ().nextInt(3 ); 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(); } } 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() :返回当前属性的变量名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 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; Field[] fields = clazz.getFields(); for (Field f : fields) { System.out.println(f); } 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) { int modifiers = f.getModifiers(); System.out.print(Modifier.toString(modifiers) + "\t" ); Class<?> type = f.getType(); System.out.print(type.getName() + "\t" ); 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() :返回当前方法的异常类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 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; Method[] methods = clazz.getMethods(); for (Method m : methods) { System.out.println(m); } 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) { Annotation[] annotations = m.getAnnotations(); for (Annotation a : annotations) { System.out.println(a); } int modifiers = m.getModifiers(); System.out.print(Modifier.toString(modifiers) + "\t" ); Class<?> returnType = m.getReturnType(); System.out.print(returnType.getName() + "\t" ); String name = m.getName(); System.out.print(name + "\t" ); System.out.print("(" ); 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(") " ); 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[] getConstructors() :返回此Class对象对应的运行时类中声明为public的所有构造器(不包含父类中的构造器)
public Constructor[] getDeclaredConstructors() :返回此Class对象对应的运行时类中声明的所有构造器(不考虑权限)
获取运行时类的构造器的内部结构:注解、修饰符、返回值类型、方法名、形参列表、异常信息:
public int getModifiers() :以int类型返回当前构造器的修饰符,该修饰符是java.lang.reflect.Modifier中的静态属性
public String getName() :返回当前构造器的名称
public Class<?>[] getParameterTypes() :返回当前方法的形参类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package pers.dhx_.java0624;import org.junit.jupiter.api.Test;import pers.dhx_.java0608.Person;import java.lang.reflect.Constructor;public class ConstructorTest { @Test public void test1 () { Class<Person> clazz = Person.class; System.out.println("声明为public" ); Constructor<?>[] constructors = clazz.getConstructors(); for (Constructor<?> c : constructors) { System.out.println(c); } System.out.println(); System.out.println("全部构造器" ); 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() :获取实际的泛型类型参数数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 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; Class<? super Person> superclass = clazz.getSuperclass(); System.out.println(superclass); Type genericSuperclass = clazz.getGenericSuperclass(); System.out.println(genericSuperclass); } @Test public void test2 () { Class<Person> clazz = Person.class; Type genericSuperclass = clazz.getGenericSuperclass(); ParameterizedType paramType = (ParameterizedType) genericSuperclass; Type[] actualTypeArguments = paramType.getActualTypeArguments(); System.out.println(actualTypeArguments[0 ].getTypeName()); } }
5. 获取运行时类实现的接口
public Class<?>[] getInterfaces() :返回此Class对象对应的运行时类实现的所有接口(不包含父类实现的接口)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @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(包)
1 2 3 4 5 6 7 @Test public void test4 () { Class<Person> clazz = Person.class; Package aPackage = clazz.getPackage(); System.out.println(aPackage); }
7. 获取运行时类声明的注解
public Annotation[] getAnnotations() :返回此Class对象对应的运行时类声明的所有注解
1 2 3 4 5 6 7 8 9 @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语言访问检查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 package pers.dhx_.java0624;import org.junit.jupiter.api.Test;import pers.dhx_.java0608.Person;import java.lang.reflect.Field;import java.lang.reflect.Modifier;public class FieldTest { @Test void t1 () throws Exception { Class clazz = Person.class; Person p = (Person) clazz.newInstance(); Field _name = clazz.getField("age" ); _name.set(p, 18 ); System.out.println(p); } @Test public void t2 () throws Exception { Class clazz = Person.class; Person p = (Person) clazz.newInstance(); Field name = clazz.getDeclaredField("name" ); Field age = clazz.getDeclaredField("age" ); name.setAccessible(true ); age.setAccessible(true ); System.out.println("修改前" + p); name.set(p, "Amy" ); age.set(p, 18 ); System.out.println("修改后" + p); } @Test public void test1 () { Class<Person> clazz = Person.class; Field[] fields = clazz.getFields(); for (Field f : fields) { System.out.println(f); } System.out.println(); System.out.println(); 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) { int modifiers = f.getModifiers(); System.out.println(modifiers); System.out.print(Modifier.toString(modifiers) + "\t" ); Class<?> type = f.getType(); System.out.print(type.getName() + "\t" ); String name = f.getName(); System.out.println(name); } } }
2. 调用运行时类中的指定方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 @Test void t1 () throws Exception { Class clazz = Person.class; Person p = (Person) clazz.newInstance(); Field _name = clazz.getField("age" ); _name.set(p, 18 ); System.out.println(p); } @Test public void t2 () throws Exception { Class clazz = Person.class; Person p = (Person) clazz.newInstance(); Field name = clazz.getDeclaredField("name" ); Field age = clazz.getDeclaredField("age" ); name.setAccessible(true ); age.setAccessible(true ); System.out.println("修改前" + p); name.set(p, "Amy" ); age.set(p, 18 ); System.out.println("修改后" + p); } @Test void t3 () throws Exception { Class<Person> clazz = Person.class; Person p = clazz.newInstance(); Method nation = clazz.getDeclaredMethod("showNation" , String.class); nation.setAccessible(true ); System.out.println(nation.invoke(p, "China" )); System.out.println(p); Method person = clazz.getDeclaredMethod("showDesc" ); person.setAccessible(true ); System.out.println(person.invoke(p)); }
3. 调用运行时类中的指定构造器
Class类对象获取指定构造器的方法:
public Constructor getDeclaredConstructor(Class<?>… parameterTypes) :返回此Class对象对应的运行时类中指定形参列表的构造器(不考虑权限)
Constructor类提供的newInstance()方法:
public T newInstance(Object … initargs) :通过当前调用该方法的构造器对象创建该运行时类的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Test void testConstructors () throws Exception { Class<Person> clazz = Person.class; Constructor<Person> cons1 = clazz.getDeclaredConstructor(String.class); Constructor cons2 = clazz.getDeclaredConstructor(int .class, String.class); cons1.setAccessible(true ); cons2.setAccessible(true ); Person p1 = cons1.newInstance("234" ); Person p2 = (Person) cons2.newInstance(12 , "Toms" ); System.out.println(p1); System.out.println(p2); }
七、反射的应用:动态代理模式
1. 代理模式
代理模式是一种比较好理解的设计模式。简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作。
举个例子:你找了小红来帮你问话,小红就可以看作是代理你的代理对象,代理的行为(方法)是问话。
https://medium.com/@mithunsasidharan/understanding-the-proxy-design-pattern-5e63fe38052a
代理模式有静态代理和动态代理两种实现方式,我们先来看一下静态代理模式的实现。
2. 静态代理
静态代理中,我们对目标对象的每个方法的增强都是手动完成的(后面会具体演示代码),非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类)。 实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。
上面我们是从实现和应用角度来说的静态代理,从 JVM 层面来说, 静态代理在编译时就将接口、被代理类、代理类都确定下来了,变成了一个个实际的字节码文件 。
定义一个接口及其实现类(被代理类);
创建一个代理类同样实现这个接口
在代理类中声明一个该接口类型的引用(在多态情况下实际指向被代理类的对象),然后在代理类的对应方法中调用被代理类中的对应方法 。这样的话,我们就可以通过代理类屏蔽对被代理类对象的访问,并且可以在目标方法执行前后做一些自己想做的事情(执行其他的方法) 。
下面通过代码展示!
1.定义发送短信的接口
1 2 3 4 public interface SmsService { String send (String message) ; } 123
2.发送短信的接口的实现类(被代理类)
1 2 3 4 5 6 7 public class SmsServiceImpl implements SmsService { public String send (String message) { System.out.println("send message:" + message); return message; } } 123456
3.创建代理类并同样实现发送短信的接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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.实际使用
1 2 3 4 5 6 7 8 9 10 11 public class Main { public static void main (String[] args) { SmsService smsService = new SmsServiceImpl (); SmsProxy smsProxy = new SmsProxy (smsService); smsProxy.send("java" ); } }
运行上述代码之后,控制台打印出:
1 2 3 4 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() ,这个方法主要用来生成一个动态代理对象:
1 2 3 4 5 6 7 public static Object newProxyInstance (ClassLoader loader, Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException { ...... }
这个方法一共有 3 个参数:
loader :类加载器,用于加载动态代理对象。一般指定为被代理类的类加载器
interfaces :代理类需要实现的一些接口。一般指定为被代理类实现的接口,代理类与被代理类需要实现同样的接口
h :实现了 InvocationHandler 接口的动态代理类的对象;
要实现动态代理的话,还必须定义一个InvocationHandler 接口的实现类(动态代理类)来自定义处理逻辑。 当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler 接口的实现类(动态代理类)的 invoke 方法来调用。
1 2 3 4 5 6 7 8 9 public interface InvocationHandler { 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.定义发送短信的接口
1 2 3 4 public interface SmsService { String send (String message) ; }
2.发送短信的接口的实现类(被代理类)
1 2 3 4 5 6 7 public class SmsServiceImpl implements SmsService { public String send (String message) { System.out.println("send message:" + message); return message; } }
3.定义一个 JDK 动态代理类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 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()); Object result = method.invoke(target, args); System.out.println("after method " + method.getName()); return result; } }
invoke() 方法: 当我们的动态代理对象调用原生方法的时候,最终实际上调用的是 invoke() 方法,然后 invoke() 方法代替我们去调用了被代理对象的原生方法
4.获取代理对象的工厂类
1 2 3 4 5 6 7 8 9 10 11 12 13 public class JdkProxyFactory { public static Object getProxy (Object target) { return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new DebugInvocationHandler (target) ); } }
getProxy() :通过Proxy.newProxyInstance()方法获取某个类的代理对象。返回的代理对象为目标类实现的接口的一个实例
5.实际使用
1 2 3 4 5 6 7 SmsService proxyInstance = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl ());String message = proxyInstance.send("java" );
运行上述代码之后,控制台打印出:
1 2 3 4 before method send send message:java after method send
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 package pers.dhx_.java0625;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;interface Human { String getBelief () ; void eatFood (String food) ; } class SuperMan implements Human { @Override public String getBelief () { return "I believe Marks" ; } @Override public void eatFood (String food) { System.out.println("eating " + food); } } class ProxyFactory { public static Object getProxyInstance (Object obj) { MyInvocationHandler handler = new MyInvocationHandler (); handler.bind(obj); return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler); } } class MyInvocationHandler implements InvocationHandler { private Object obj; public void bind (Object ojb) { this .obj = ojb; } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { Object returnValue = method.invoke(obj, args); return returnValue; } } public class ProxyTest { public static void main (String[] args) { SuperMan superman = new SuperMan (); Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superman); proxyInstance.eatFood("bwjkkcya" ); System.out.println(proxyInstance.getBelief()); } }
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 用于拦截增强被代理类的方法。
1 2 3 4 5 6 7 public interface MethodInterceptor extends Callback { public Object intercept (Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable;} 123456
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 ) 实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖。
1 2 3 4 5 6 <dependency > <groupId > cglib</groupId > <artifactId > cglib</artifactId > <version > 3.3.0</version > </dependency >
1.实现一个使用阿里云发送短信的类
1 2 3 4 5 6 7 8 9 package github.javaguide.dynamicProxy.cglibDynamicProxy;public class AliSmsService { public String send (String message) { System.out.println("send message:" + message); return message; } }
2.自定义 MethodInterceptor(方法拦截器)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;public class DebugMethodInterceptor implements MethodInterceptor { @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.获取代理类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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.实际使用
1 2 3 AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);aliSmsService.send("java" );
运行上述代码之后,控制台打印出:
1 2 3 4 before method send send message:java after method send
3.3. JDK 动态代理和 CGLIB 动态代理对比
JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。
4. 静态代理和动态代理的对比
灵活性 :动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!
JVM 层面 :静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。