java学习笔记--反射( 后期学习补充用法)
可以先参考下大佬的: 大佬1 大佬2 类字节码文件是在硬盘上存储的,是一个个的.class文件。我们在new一个对象时,JVM会先把字节码文件的信息读出来放到内存中,第二次用时,就不用在加载了,而是直接使用之前缓存的这个字节码信息。 字节码的信息包括:类名、声明的方法、声明的字段等信息。在Java中“万物皆对象”,这些信息当然也需要封装一个对象,这就是Class类、Method类、Field类。 通过Class类、Method类、Field类等等类可以得到这个类型的一些信息,甚至可以不用new关键字就创建一个实例,可以执行一个对象中的方法,设置或获取字段的值,这就是反射技术。 方法区存的是类的信息,不是存类对象的,类加载器加载类是通过方法区上类的信息在堆上创建一个类的Class对象,这个Class对象是唯一的,由JVM保证唯一,之后对这个类的创建都是根据这个Class对象来操作的 反射是什么呢?当我们的程序在运行时,需要动态的加载一些类这些类可能之前用不到所以不用加载到jvm,而是在运行时根据需要才加载,这样的好处对于服务器来说不言而喻,举个例子我们的项目底层有时是用mysql,有时用oracle,需要动态地根据实际情况加载驱动类,这个时候反射就有用了,假设 com.java.dbtest.myqlConnection,com.java.dbtest.oracleConnection这两个类我们要用,这时候我们的程序就写得比较动态化,通过Class tc = Class.forName(“com.java.dbtest.TestConnection”);通过类的全类名让jvm在服务器中找到并加载这个类,而如果是oracle则传入的参数就变成另一个了。 Java 反射机制主要提供了以下功能 在运行时判断任意一个对象所属的类。 在运行时构造任意一个类的对象。 在运行时判断任意一个类所具有的成员变量和方法。 在运行时调用任意一个对象的方法 今天回过头来找了点视频来简单了解下了反射机制 以下纯属个人学习总结 如有问题 欢迎讨论 一起进步 问:*有人会问 反射到底是用来干什么的?* 答:目前学了感觉是没啥用 但是到了框架部分 就大有用处 就好比框架是个房子 里面的构造都已经给你搭建好了 你想要对其中的部分进行修改添加 这个时候就需要反射来动态修改了 主要的方法都在java.lang.reflect中 reflect是反射的英文 不知道这么说 是不是可以理解 因为我也没学到过框架 只是照葫芦画瓢在这瞎几把乱说 如有问题 请指出。 Class.forName是在bin层次下 new FileReader是在项目层次下 下面会用到 先简要了解下 反射(类的加载概述和加载时机) 类的加载概述 当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。 加载 就是指将class文件读入内存,并为之创建一个Class对象。任何类被使用时系统都会建立一个Class对象。 连接 验证 是否有正确的内部结构,并和其他类协调一致 准备 负责为类的静态成员分配内存,并设置默认初始化值 解析 将类的二进制数据中的符号引用替换为直接引用 初始化 加载时机 创建类的实例 访问类的静态变量,或者为静态变量赋值 调用类的静态方法 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象 初始化某个类的子类 直接使用java.exe命令来运行某个主类 反射(类加载器的概述和分类) :类加载器的概述 负责将.class文件加载到内存中,并为之生成对应的Class对象。虽然我们不需要关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行。 :类加载器的分类 Bootstrap ClassLoader 根类加载器 Extension ClassLoader 扩展类加载器 Sysetm ClassLoader 系统类加载器 :类加载器的作用 Bootstrap ClassLoader 根类加载器 也被称为引导类加载器,负责Java核心类的加载 比如System,String等。在JDK中JRE的lib目录下rt.jar文件中 Extension ClassLoader 扩展类加载器 负责JRE的扩展目录中jar包的加载。 在JDK中JRE的lib目录下ext目录 Sysetm ClassLoader 系统类加载器 负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径 反射(反射概述) :反射概述 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法; 对于任意一个对象,都能够调用它的任意一个方法和属性; 这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。 要想解剖一个类,必须先要获取到该类的字节码文件对象。 而解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象。 :三种方式 a:Object类的getClass()方法,判断两个对象是否是同一个字节码文件 b:静态属性class,锁对象 c:Class类中静态方法forName(),读取配置文件 举一个栗子: package fuxi; class Person{ } public class One { public static void main(String args[]) throws ClassNotFoundException { Person person=new Person(); Class clazz1=person.getClass(); //根据实体类得到class字节码 Class clazz2=Person.class; //静态属性class加载 Class clazz3=Class.forName("fuxi.Person");//路径要写全 java文件中的得到字节码 System.out.println(clazz1.equals(clazz2)); System.out.println(clazz1.equals(clazz3)); System.out.println(clazz2.equals(clazz3)); } } 输出: true true true 意思就是三个得到的字节码class都是同一个 用反射和配置文件解决问题: package fuxi; import java.io.BufferedReader; import java.io.FileReader; interface Animal{ void run(); } class Dog implements Animal{ @Override public void run() { System.out.print("汪汪汪"); } } class Cat implements Animal{ @Override public void run() { System.out.print("喵喵喵"); } } class Ox{ public void run(Animal animal) { animal.run(); } } public class One { public static void main(String args[]) throws Exception { BufferedReader br = new BufferedReader(new FileReader("Animal.properties")); String name=br.readLine(); System.out.println(name); Class clazz=Class.forName(name); Ox animal=new Ox(); animal.run((Animal) clazz.newInstance()); } } 还有在项目下创建一个配置文件 内容是: fuxi.Dog 如果是原来的多态 写死了 那么每次都要修改源文件 而用反射的话 就可以的动态记载配置文件里面的内容 只要修改配置文件就行 降低了耦合度 通过反射获得构造函数并使用 Class类的newInstance()方法是使用该类无参的构造函数创建对象, 如果一个类没有无参的构造函数, 就不能这样创建了,可以调用Class类的getConstructor 举个栗子: package fuxi; class Person { String name="xx"; int age=3; public Person(String name, int age) { this.name = name; this.age = age; } public Person() { name="xx"; age=3; } @Override public String toString() { return name + " " + age; } } public class One { public static void main(String args[]) throws Exception { Class clazz = Class.forName("fuxi.Person"); Person person = (Person) clazz.newInstance(); System.out.print(person); } } 输出: xx 3 这个时候有无参的构造函数 所以可以 如果去掉这个无参的构造函数 那么就会报错 这个时候就得Class类的getConstructor获取class类的构造函数 package fuxi; import java.lang.reflect.Constructor; class Person { String name="xx"; int age=3; public Person(String name, int age) { this.name = name; this.age = age; } public Person() { name="xx"; age=3; } @Override public String toString() { return name + " " + age; } } public class One { public static void main(String args[]) throws Exception { Class clazz = Class.forName("fuxi.Person"); Constructor[] constructors=clazz.getConstructors();//获得这个类的构造函数数组 System.out.println(constructors[0]); System.out.println(constructors[1]); Constructor constructor=clazz.getConstructor(String.class,int.class);////获取有参构造 System.out.println(constructor); Person person=(Person) constructor.newInstance("xiaoming",1);//通过有参构造创建对象 System.out.print(person); } } 输出: public fuxi.Person(java.lang.String,int) public fuxi.Person() public fuxi.Person(java.lang.String,int) xiaoming 1 通过反射获取成员变量并使用 Field * Class.getField(String)方法可以获取类中的指定字段(可见的), 如果是私有的可以用getDeclaedField(“name”)方法获取,通过set(obj, “李四”)方法可以设置指定对象上该字段的值, 如果是私有的需要先调用setAccessible(true)设置访问权限,用获取的指定的字段调用get(obj)可以获取指定对象中该字段的值 package fuxi; import java.io.File; import java.lang.reflect.Constructor; import java.lang.reflect.Field; class Person { public String name="小明"; public int age=3; public Person(String name, int age) { this.name = name; this.age = age; } public Person() { name="xx"; age=3; } @Override public String toString() { return name + " " + age; } } public class One { public static void main(String args[]) throws Exception { Class clazz = Class.forName("fuxi.Person"); Field[] fields=clazz.getFields(); //获取类属性的全部属性 前提的public 如果是包内访问和private都不行 System.out.println(fields[0]); System.out.println(fields[1]); Person person=(Person) clazz.newInstance(); //通过无参构造创建对象 //Field field=clazz.getField("name"); //获取姓名字段 //field.set(person, "小张"); //修改姓名的值 Field field=clazz.getDeclaredField("name"); //暴力反射获取字段 field.setAccessible(true); //去掉私有权限 field.set(person, "小张"); System.out.print(person); } } 输出: public java.lang.String fuxi.Person.name public int fuxi.Person.age 小张 3 总结: 属性权限如果不是公有的话 1. 要获取属性只能 clazz.getDeclaredField 暴力获取属性 2. 要修改属性只能 setAccessible 去掉权限限制 这么看来反射是不是很强盗 ? 也说明了他的无敌 通过反射获取方法并使用 * Method * Class.getMethod(String, Class…) 和 Class.getDeclaredMethod(String, Class…)方法可以获取类中的指定方法,调用invoke(Object, Object…)可以调用该方法 package fuxi; import java.io.File; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import javax.print.attribute.standard.Media; class Person{ String name; int age; public Person(String name,int age) { this.name=name; this.age=age; } public void printf() { System.out.println(name); } public void printf(int age) { System.out.println(name+" "+age); } } public class One { public static void main(String args[]) throws Exception { Class clazz=Class.forName("fuxi.Person"); Constructor constructor=clazz.getConstructor(String.class,int.class); Person person=(Person) constructor.newInstance("小明",10); Method method1=clazz.getMethod("printf"); method1.invoke(person); Method method2=clazz.getMethod("printf", int.class); method2.invoke(person, 11); } } 输出: 小明 小明 11 或许你们会有个疑惑 为什么clazz.getMethod(“printf”, int.class)? 其实这一部分还属于反射部分 还是字节码的部分 所以得传入class形式 最后invoke的时候才是具体的值 泛型反射: 这个之前学泛型的时候提到过 现在简单回顾一下 泛型检查是在编译器 运行时 class文件中类型都被泛型擦除为Object 这个时候我们可以通过泛型反射 往ArrayList<Integer>中 添加String类型 package fuxi; import java.lang.reflect.Method; import java.util.ArrayList; public class One { public static void main(String args[]) throws Exception { ArrayList<Integer> arrayList=new ArrayList<Integer>(); arrayList.add(1); arrayList.add(2); Class clazz=Class.forName("java.util.ArrayList"); Method method=clazz.getMethod("add", Object.class); method.invoke(arrayList, "adb"); System.out.print(arrayList); } } 输出: [1, 2, adb] 通过反射写一个通用的设置某个对象的某个属性为指定的值 package fuxi; import java.lang.reflect.Field; class Person{ String name; int age; public Person(String name,int age) { this.name=name; this.age=age; } } public class One { public static void main(String args[]) throws Exception { Person person=new Person("小明", 10); tool(person, "name", "小王"); System.out.print(person.name); } public static void tool(Object person,String name,Object object) throws Exception { Class clazz=Class.forName("fuxi.Person"); Field field=clazz.getDeclaredField(name); field.setAccessible(true); field.set(person, object); } } 输出: 小王 总结: 1. Class.forName可以读取一个类的字节码文件即Class文件 Class可以通过newInstance()创建一个实体类 通过反射获取带参构造方法并使用 Constructor Class类的newInstance()方法是使用该类无参的构造函数创建对象, 如果一个类没有无参的构造函数, 就不能这样创建了,可以调用Class类的getConstructor(String.class,int.class)方法获取一个指定的构造函数然后再调用Constructor类的newInstance(“张三”,20)方法创建对象 通过反射获取成员变量并使用 Field Class.getField(String)方法可以获取类中的指定字段(可见的), 如果是私有的可以用getDeclaedField(“name”)方法获取,通过set(obj, “李四”)方法可以设置指定对象上该字段的值, 如果是私有的需要先调用setAccessible(true)设置访问权限,用获取的指定的字段调用get(obj)可以获取指定对象中该字段的值 通过反射获取方法并使用 Method Class.getMethod(String, Class…) 和 Class.getDeclaredMethod(String, Class…)方法可以获取类中的指定方法,调用invoke(Object, Object…)可以调用该方法,Class.getMethod(“eat”) invoke(obj) Class.getMethod(“eat”,int.class) invoke(obj,10) 通过反射越过泛型检查 动态代理 最后再来讲一下 动态代理: 代理:本来应该自己做的事情,请了别人来做,被请的人就是代理对象。 代理类在程序运行时创建的代理方式被成为 动态代理。 也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。 所以说动态代理其实就是通过反射来生成一个代理 优点: 可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。 但是只能针对接口进行动态代理 所以 你要代理的类只能继承接口而不是一个类 在Java中java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口就可以生成动态代理对象。 重要:InvocationHandler接口 在使用动态代理时,我们需要定义一个位于代理类与委托类之间的中介类,这个中介类被要求实现InvocationHandler接口 作为调用处理器”拦截“对代理类方法的调用 当我们调用代理类对象的方法时,这个“调用”会转送到invoke方法中,代理类对象作为proxy参数传入,参数method标识了我们具体调用的是代理类的哪个方法,args为这个方法的参数。这样一来,我们对代理类中的所有方法的调用都会变为对invoke的调用,这样我们可以在invoke方法中添加统一的处理逻辑 最终会调用InvocationHandler的方法 InvocationHandler Object invoke(Object proxy,Method method,Object[] args) //意思 :调用处理器 Java动态代理其实内部也是通过Java反射机制来实现的,即已知的一个对象,然后在运行时动态调用其方法,这样在调用前后作一些相应的处理,这样说的比较笼统 接口: package Father; public interface FatherInter { public void login(); public void submit(); } 继承接口实现类: public class Son implements FatherInter { @Override public void login() { System.out.println("登录"); } @Override public void submit() { System.out.println("提交"); } } 调用处理器: public class MyInvocationHandler implements InvocationHandler { private Object target; public MyInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("权限校验"); method.invoke(target, args); //执行被代理target对象的方法 System.out.println("日志记录"); return null; } } Main函数: public class Main { public static void main(String[] args) { Son si = new Son(); si.login(); si.submit(); System.out.println("-------------------------------"); MyInvocationHandler m = new MyInvocationHandler(si); FatherInter s = (FatherInter)Proxy.newProxyInstance(si.getClass().getClassLoader(), si.getClass().getInterfaces(), m); //Proxy提供用于创建动态代理类和实例的静态方法 //newProxyInstance返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。 s.login(); s.submit(); } } 输出: 登录 提交 ------------------------------- 权限校验 登录 日志记录 权限校验 提交 日志记录 总结: 1. 动态代理 被代理的类要继承接口才行 基类不行 2. 写一个类继承InvocationHandler 作为中间层 给它传入你的实现类 额外添加的代码写在里面的invoke方法中 3. class类的getInterfaces()方法返回的是 实现接口数组 4. Proxy.newProxyInstance返回的是接口 而不是实现类 遇到这么一个问题 5. invoke(Object proxy, Method method, Object[] args) proxy其实没有啥用 method是为了method.invoke(target, args)执行被代理类的方法args则是被调用方法的参数 6. Proxy类和一个InvocationHandler接口,通过使用这个类和接口就可以生成动态代理对象。JDK提供的代理只能针对接口做代理。我们有更强大的代理cglib,Proxy类中的方法创建动态代理类对象 Proxy.newProxyInstance(si.getClass().getClassLoader(), si.getClass().getInterfaces(), m);中getClassLoader()和getInterfaces()是不必动的 固定死了 代表加载器和实现类实现的接口 m是调用处理器:InvocationHandler的实现类 Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to Father.FatherInter at Father.Main.main(Main.java:13) 代理类没有实现接口,而是实现了某一基类。大家要注意了哦。 参考: 代理类: https://blog.csdn.net/andyzhaojianhui/article/details/72833114