反射
- 反射机制
- class类
- 类加载
- 反射获取类的结构信息
- 通过反射创建对象
- 练习
反射机制
-
引出反射
-
根据配置文件re.properties指定信息,创建Cat对象并调用方法hi
properties配置文件如下
classPath=com.reflection_.Cat method=hi
Cat类
package com.reflection_; public class Cat { private String name = "jack"; public void hi(){ System.out.println("hi "+name); } }
实现类ReflectionQuestion
package com.reflection_.question; import com.reflection_.Cat; public class ReflectionQuestion { public static void main(String[] args) { //1.使用传统方法实现,通过new一个对象,调用方法来实现 Cat cat = new Cat(); cat.hi(); } }
我们使用传统的方式,能实现上述需求,但如果我们Cat类需要增加功能或者换一种实现时,我们就需要去修改源码了!
所以我们可以尝试使用反射来实现上述需求
package com.reflection_.question; import com.reflection_.Cat; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Properties; @SuppressWarnings({"all"}) public class ReflectionQuestion { public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { //1.使用properties类,读写配置文件 Properties properties = new Properties(); properties.load(new FileInputStream("src\\re.properties")); String classfullpath = properties.get("classfullpath").toString(); String methodName = properties.get("method").toString(); System.out.println("classfullpath = " + classfullpath); System.out.println("methodName = " + methodName); //2.创建对象,发现new的是一个String 错误!! // new classfullpath(); //3.此时就需要使用反射机制来解决了 //3.1 加载类,返回class类型的对象cls Class cls = Class.forName(classfullpath); //3.2 通过cls得到你加载的类 com.reflection_.Cat 的对象实例 Object o = cls.newInstance(); System.out.println("o的运行类型 = " + o.getClass());//运行类型 //3.3 通过cls得到你加载的类 com.reflection_.Cat 的 methodName 的方法对象 // 在反射中,可以把方法视为对象(万物皆对象!) Method method1 = cls.getMethod(methodName); //3.4 通过method1 调用方法:即通过方法对象调用方法! method1.invoke(o); } }
我们可以发现,使用传统方法时:对象.方法(),反射机制:方法.invoke(对象)
这样的需求在学习框架是特别多,即通过外部文件配置,在不修改源码的情况下,来控制程序,也符合设计模式的OCP原则(开闭原则:不修改源码,扩容功能)。
-
-
反射机制
- 反射机制允许程序在执行期借助于Reflection API取得任何类的内部消息(比如成员变量,构造器,成员方法等等),并且能操作对象的属性及方法。反射在设计模式和框架底层都会用到
- 加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,形象的称之为:反射
反射机制原理图
-
Java反射机制可以完成
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时得到任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的成员变量和方法
- 生产动态代理
-
反射相关的主要类:
- java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象
- java.lang.reflect.Method:代表类的方法,Method对象表示某个类的方法
- java.lang.reflect.Field:代表类的成员变量,Field对象表示某个类的成员变量
- java.lang.reflect.Constructor:代表类的构造方法,Constructor对象表示某个类的构造器
代码演示:
package com.reflection_; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Properties; /** * @author minster */ public class Reflection01 { public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException { //1.使用properties类,读写配置文件 Properties properties = new Properties(); properties.load(new FileInputStream("src\\re.properties")); String classfullpath = properties.get("classfullpath").toString(); String methodName = properties.get("method").toString(); System.out.println("classfullpath = " + classfullpath); System.out.println("methodName = " + methodName); //3.此时就需要使用反射机制来解决了 //3.1 加载类,返回class类型的对象cls Class cls = Class.forName(classfullpath); //3.2 通过cls得到你加载的类 com.reflection_.Cat 的对象实例 Object o = cls.newInstance(); //运行类型 System.out.println("o的运行类型 = " + o.getClass()); //3.3 通过cls得到你加载的类 com.reflection_.Cat 的 methodName 的方法对象 // 在反射中,可以把方法视为对象(万物皆对象!) Method method1 = cls.getMethod(methodName); //3.4 通过method1 调用方法:即通过方法对象调用方法! method1.invoke(o); //获取Cat类中的某个字段(成员变量),getField不能获取私有属性 Field age = cls.getField("age"); System.out.println(age); //()中能指定构造器参数类型,下面返回无参构造器 Constructor constructor01 = cls.getConstructor(); System.out.println(constructor01); //这里传入的String.class 就是String类的Class对象 Constructor constructor02 = cls.getConstructor(String.class); System.out.println(constructor02); } }
-
反射的优点和缺点
- 优点:可以动态的创建和使用对象(也是框架的底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑
- 缺点:使用反射基本是解释执行,对执行速度有影响
package com.reflection_; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * @author minster */ public class Reflection02 { public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { m1(); } public static void m1() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { long start = System.currentTimeMillis(); Class<?> cls = Class.forName("com.reflection_.Cat"); Object o = cls.newInstance(); Method hi = cls.getMethod("hi"); for (int i = 0; i < 900000000; i++) { hi.invoke(o); } long end = System.currentTimeMillis(); System.out.println("耗时:" + (end-start)); } }
-
反射调用优化-关闭访问检查
- Method和Field、Constructor对象都有setAccessible()方法
- setAccessible作用是启动和禁用访问安全检查的开关
- 参数值为true表示反射的对象在使用时取消访问检查,提高反射的效率。参数值为false则表示反射的对象执行访问检查。
优化代码
package com.reflection_; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * @author minster */ public class Reflection02 { public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { m1(); } public static void m1() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { long start = System.currentTimeMillis(); Class<?> cls = Class.forName("com.reflection_.Cat"); Object o = cls.newInstance(); Method hi = cls.getMethod("hi"); //关闭反射的访问检测 hi.setAccessible(true); for (int i = 0; i < 900000000; i++) { hi.invoke(o); } long end = System.currentTimeMillis(); System.out.println("耗时:" + (end-start)); } }
Class类
-
基本介绍:
-
Class类也是类,因此也继承Object类
-
Class类对象不是new出来的,而是系统创建的。
//追源码可以发现是通过ClassLoader类来加载Cat类的Class对象 public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); }
-
对于某个类的Class类对象,在内存中只有一份,因此类只加载一次
-
每个类的实例都会记得自己是由哪个Class实例所生成
-
通过Class可以完整地得到一个类的完整结构,通过一系列API
package com.reflection_.class_; import com.reflection_.Car; import java.lang.reflect.Field; /** * @Author: minster * @Date: 2021/9/22 8:41 */ public class Class02 { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException { String classPath = "com.reflection_.Car"; Class<?> cls = Class.forName(classPath); //显示cls对象,是哪个类的Class对象 com.reflection_.cat System.out.println(cls); //输出cls的运行类型 java.lang.Class System.out.println(cls.getClass()); //创建Class对象的实例,即通过反射获取对象 Car car = (Car)cls.newInstance(); System.out.println(car); //通过反射获取对象的属性 Field brand = cls.getField("brand"); System.out.println(brand.get(car)); //通过反射获取的字段进行赋值 brand.set(car,"奔驰"); System.out.println(brand.get(car)); //获取所有属性 Field[] fields = cls.getFields(); for (Field f : fields) { System.out.println(f.get(car)); } } }
-
Class对象时存放在堆的
-
类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括方法代码、变量名、方法名、访问权限等)
-
-
获取类对象的6种方式
-
已知一个类的全类名,且在该类的路径下,可以通过Class类的对象的静态方法forName()获取,可能抛出ClassNotFoundException。多用于读取配置文件,读取类全路径,加载类阶段
-
若已知具体的类,通过类的class来获取,该方式最为安全可靠,程序性能最高。多用于参数传递,比如通过反射得到对应构造器对象。
-
若已知某个类的实例,调用该实例的getClass()方法获取Class对象。用于通过创建好的对象,获取Class对象。
-
使用类的加载器来获取类的对象
-
基本数据类型可以通过
class cls = int.class
获取Class对象 -
基本数据类型对应的包装类,可以通过type获取Class对象
代码演示:
package com.reflection_.class_; import com.reflection_.Car; /** * @Author: minster * @Date: 2021/9/22 9:30 */ public class GetClass { public static void main(String[] args) throws ClassNotFoundException { String classPath = "com.reflection_.Car"; //1. 通过Class类的对象的静态方法forName()获取 Class<?> cls1 = Class.forName(classPath); System.out.println(cls1.getName()); //2. 通过类的class来获取 Class cls2 = Car.class; System.out.println(cls2.getName()); //3. 调用该实例的getClass()方法获取Class对象 Car car = new Car(); Class<? extends Car> cls3 = car.getClass(); System.out.println(cls3.getName()); //4. 使用类的加载器来获取类的对象 //先得到类的加载器 ClassLoader classLoader = car.getClass().getClassLoader(); //再通过类的加载器得到Class对象 Class<?> cls4 = classLoader.loadClass(classPath); System.out.println(cls4); //通过hashCode可以看出来他们都是同一个类的对象 System.out.println("cls1HashCode = "+cls1.hashCode()); System.out.println("cls2HashCode = "+cls2.hashCode()); System.out.println("cls3HashCode = "+cls3.hashCode()); System.out.println("cls4HashCode = "+cls4.hashCode()); //5. 通过`class cls = int.class`获取Class对象 Class<Integer> integerClass = int.class; System.out.println(integerClass); //6. 通过type获取Class对象 Class<Integer> type = Integer.TYPE; System.out.println(type); System.out.println("intHashCode = "+integerClass.hashCode()); System.out.println("IntegerHashCode = "+type.hashCode()); } }
运行结果:
-
-
在Java中出了基本数据类型有Class对象之外,下面的类型也有Class对象
类加载
-
基本说明
反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载。
- 静态加载:编译时加载相关的类,如果没有则报错,依赖性太强
- 动态加载:运行时加载需要的类,如果运行时不用该类,则不报错,降低了依赖性
代码演示
package com.reflection_.class_; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Scanner; /** * @Author: minster * @Date: 2021/9/22 10:04 */ public class Class03 { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { Scanner scanner = new Scanner(System.in); System.out.println("input key"); String key = scanner.nextLine(); switch (key){ case"1": Dog dog = new Dog(); dog.cry(); System.out.println("1"); break; case "2" : System.out.println("ok"); break; case "3" : System.out.println("ok"); default: System.out.println("not do"); } scanner.close(); } }
使用cmd进行编译,可以发现即使我们还没传key进去,程序也会报错,因为Dog类是静态加载,因此必须编写Dog类才可以!
此时我们把
case 1
处的代码注释,使用反射加载类Person
package com.reflection_.class_; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Scanner; /** * @Author: minster * @Date: 2021/9/22 10:04 */ public class Class03 { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { Scanner scanner = new Scanner(System.in); System.out.println("input key"); String key = scanner.nextLine(); switch (key){ case"1": /* Dog dog = new Dog; dog.cry();*/ System.out.println("1"); break; case "2" : Class<?> person = Class.forName("Person"); Object o = person.newInstance(); Method m = person.getMethod("hi"); m.invoke(o); break; case "3" : System.out.println("ok"); default: System.out.println("not do"); } scanner.close(); } }
输出1,可以发现程序能正常执行
输入2,程序抛出异常!这就是所谓的动态加载,当运行时执行到该类的代码时才会使程序异常!
-
类加载时机
- 当创建对象时(new)——静态加载
- 当子类被加载时——静态加载
- 调用类中的静态成员时——静态加载
- 通过反射——动态加载
-
类加载过程图
在类加载的三个阶段中,各个阶段会分别完成以下任务
-
加载(Loading)阶段:JVM在该阶段主要目的是将字节码从不同的数据源(可能是class文件、也可能是jar包,甚至网络)转换为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class对象。
-
连接(Linking)阶段:该阶段包含验证、准备、解析三个阶段
-
验证阶段:
- 确保被加载的类的正确性,确保你加载的字节码文件没有被恶意地修改过,而且一切都是符合JVM对于字节码格式的要求
- 在字节码语言层面,Java代码无法做到的事情都是可以实现的,至少语义上可以表达出来的,虚拟机如果不检查输入的字节流,对其完全信任的话,很可能会因为载入了有害的字节流而导致系统崩溃,所以验证是虚拟机对自身保护的一项重要工作
- 包括:文件格式验证、元数据验证、字节码验证和符号引用验证
-
准备阶段:为类变量赋初值,不给实例变量赋值,这儿的初值指的是零值。例如
public static int value = 123;
value在准备阶段过后的初始值为0而不是123;
//在准备阶段,属性会如何处理? //1. n1是实例属性,不是静态变量,因此在准备阶段,是不会分配内存的! //2. n2是静态变量,分配内存;n2是默认初始化0,而不是20 //3. n3是static final常量,它和静态变量不一样,因为一旦赋值就不会改变,所以n3 = 30; public int n1 = 10; public static int n2 = 20; public static final int n3 = 30;
-
解析阶段:将常量池的符号引用转化为直接引用,主要针对接口或类,字段。接口方法,类方法的符号引用。
-
-
初始化(initialization)阶段:为类的静态变量赋予正确的初始值,即程序员显式地赋予的初始值
-
到初始化阶段,才真正开始执行类中定义的Java程序代码,此阶段是执行
<clinit>()
方法的过程。 -
<clinit>()
方法由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并。 -
JVM会保证一个类的
<clinit>()
方法在多线程环境中被正确加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()
方法,其他线程都需要阻塞等待,知道活动现场执行<clinit>()
方法完毕!protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { //省略 } }
我们在创建一个对象时,追源码进去,可以看到在加载类时会有一个方法使用了
synchronized
锁。正因如此,才能保证某个类在内存中只有一份Class对象。
如果直接使用类的静态属性,也会导致类的加载
类加载阶段的深入理解见JVM类加载的深入理解
-
-
通过反射获取类的结构信息
-
基于
java.lang.Class
的API- getName():获取全类名。
- getSimpleName():获取简单类名
- getFields():获取所有
public
修饰的属性,包含本类以及父类(包含Object)的 - getDeclaredField():获取本类中所有属性
- getMethods():获取所有
public
修饰的方法,包含本类以及父类的 - getDeclaredMethods():获取本类中所有方法
- getConstructors():获取本类所有
public
修饰的构造器。 - getDeclaredConstructors():获取本类中所有构造器
- getPackage():以Package形式返回包信息
- getSuperClass():以Class形式返回父类信息
- getInterfaces():以Class[]形式返回接口信息
- getAnnotations():以Annotation[]形式返回注解信息
-
基于
java.lang.reflect.Field
类的API-
getModifiers():以int形式返回修饰符**[说明:默认修饰符是0,public是1,private是2,protected是4,static是8,final是16]**
//如果有一个字段是有多个修饰符,则两个int相加 public static String name = "minster"; System.out.println(name,getModifiers());
上面语句会输出9
-
getType():以Class形式返回类型
-
getName():返回属性名
-
-
基于
java.lang.reflect.Method
类的API- getModifiers():以int形式返回修饰符**[说明:默认修饰符是0,public是1,private是2,protected是4,static是8,final是16]**
- getReturnType():以Class形式获取返回类型
- getName():返回方法名
- getParameterTypes():以Class[]返回参数类型数组
通过反射创建对象
-
调用类中的public修饰的无参构造器
-
调用类中的指定构造器
-
Class相关方法
-
newInstance():调用类中的无参构造器,获取对应类的对象
-
getConstructor(Class…clazz):根据参数列表,获取对应的public构造器对象
-
getDecalaredConstructor(Class…clazz):根据参数列表,获取对应的所有构造器
-
-
Constructor类相关方法
- setAccessible():暴破
- newInstance(Object…obj):调用构造器
-
案例演示
- 通过反射创建某类的对象,要求该类中必须有public的无参构造
- 通过调用某个特定构造器的方式,实现创建某个类的对象
创建User类
package com.reflection_; /** * @Author: minster * @Date: 2021/9/22 19:36 */ public class User { public String name = "minster"; public int age = 21; public User(){ } private User(String name){ this.name = name; } public User(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; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
创建实现类ReflectionInstance01
package com.reflection_; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; /** * @Author: minster * @Date: 2021/9/22 19:36 */ public class ReflectionInstance01 { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { String classPath = "com.reflection_.User"; Class<?> cls = Class.forName(classPath); //1. 通过public的无参构造器创建实例 Object user1 = cls.newInstance(); System.out.println(user1); //2. 通过public的有参构造器创建实例 Constructor<?> constructor = cls.getConstructor(String.class, int.class); Object ming = constructor.newInstance("ming", 22); System.out.println(ming); //3. 通过非公有的有参构造器创建实例 Constructor<?> declaredConstructor = cls.getDeclaredConstructor(String.class); Object minster = declaredConstructor.newInstance("minster"); System.out.println(minster); } }
运行上述代码,我们发现方法1,2都能正常运行,但是方法3却报异常。
因为我们通过
getDeclaredConstructor(String.class);
获取到的构造器是private
的,所以我们在24行访问时会报出IllegalAccessException
异常!此时我们可以使用暴破法来解决这个问题
//3. 通过非公有的有参构造器创建实例 //因为要获取的构造方法为私有的,所以获取到的declaredConstructor也是私有的! Constructor<?> declaredConstructor = cls.getDeclaredConstructor(String.class); //暴破(暴力破解,可以使用反射访问private构造器/方法/属性)! declaredConstructor.setAccessible(true); Object minster = declaredConstructor.newInstance("minster"); System.out.println(minster);
使用
java.lang.reflection.Constructor
的setAccessible方法可以解决这个问题!任何封装的属性在反射面前都是纸老虎!使用暴破可以使反射访问到private构造器/方法/属性
-
注意:当使用反射访问静态成员或静态方法时,访问的参数可以写成null。(因为静态成员或静态方法属于整个类的属性!)
Student类
class Student{ private static String name; public static String m1(String name){ System.out.println("hi"); } }
Reflection04类
package com.reflection_; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * @Author: minster * @Date: 2021/9/23 9:44 */ public class Reflection04 { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException { String classPath = "com.reflection_.Student"; Class<?> studentClass = Class.forName(classPath); Object o = studentClass.newInstance(); Field name = studentClass.getDeclaredField("name"); //name为私有字段,需要暴破! name.setAccessible(true); //给name字段赋值,指定对象为null,因为name字段为static字段 name.set(null,"minster"); //获取name字段的值 System.out.println(name.get(null)); Method declaredMethod = studentClass.getDeclaredMethod("m1"); //因为m1方法属于静态方法,所以可以使用null对象来调用它 Object o1 = declaredMethod.invoke(null); System.out.println(o1); } }
在反射中,如果方法有返回值,统一返回Object!
练习
-
利用反射创建文件
package com.reflection_.homework; import java.io.File; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * @Author: minster * @Date: 2021/9/23 10:33 */ public class HomeWork02 { public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Class<?> fileClass = Class.forName("java.io.File"); Constructor<?>[] declaredConstructor = fileClass.getDeclaredConstructors(); for (Constructor<?> constructor : declaredConstructor) { System.out.println("File的构造器有:"+constructor); } Constructor<?> declaredConstructor1 = fileClass.getDeclaredConstructor(String.class); Method createNewFile = fileClass.getMethod("createNewFile"); String allPath = "e:\\mynew.txt"; Object file = declaredConstructor1.newInstance(allPath); createNewFile.invoke(file); } }