类加载器和反射
类的加载
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
加载
- 就是指将class文件读入内存,并为之创建一个Class对象。
- 任何类被使用时系统都会建立一个Class对象。
连接
- 验证 是否有正确的内部结构,并和其他类协调一致
- 准备 负责为类的静态成员分配内存,并设置默认初始化值
- 解析 将类的二进制数据中的符号引用替换为直接引用
- 初始化 就是我们以前讲过得初始化步骤
类初始化时机
- 创建类的实例
- 访问类的静态变量,或者为静态变量赋值
- 调用类的静态方法
- 使用反射方式来强制创建某个类或者接口对应的java.lang.Class对象
- 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类
类加载器
- 类加载器
- 负责将.class文件加载到内存中,并为之生成对应的Class对象。
- 虽然我们不需要关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行。
- 类架子啊器的组成
- Bootstrap ClassLoader 根类加载器
- Extension ClassLoader 扩展类加载器
- System ClassLoader 系统类加载器
类加载器的作用
- Bootstrap ClassLoader 根类加载器
- 也被称为引导类加载器,负责Java核心类的加载
- 比如System,String等。在JDK中JRE的lib目录下rt.jar文件中。
- Extension ClassLoader扩展类加载器
- 负责JRE的扩展目录中jar包的加载
- 在JDK中JRE的lib目录下ext目录
- System ClassLoader 系统类加载器
- 负责在JVM启动时加载来自java命令的class文件,以及classPath环境变量所指定的jar包和类路径
反射
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能构调用他的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
简单的讲,反射就是指我现在只有class文件 ,然后你拿到class文件去使用成员变量,构造方法 ,成员方法。 不是拿着java文件而是class文件,反着去用它
要先解剖一个类,个必须要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法,所以要先获取到每一个字节码文件对应的Class类型的对象。
如何获取Class文件类对象
/* * 反射:就是通过class文件对象,去使用该文件中的成员变量,构造方法,成员方法。 * * 要想这样使用,首先你必须得到class文件对象,其实就是得到Class类的对象。所有的.class文件都是Class类的对象 * * Class类: * Class类中可以的到下面几个类 * 分别把这三个东西定义成类,通过这几个类的对象去调方法,使用该方法,这叫反射 * 成员变量 Field * 构造方法 Constructor * 成员方法 Method * Constructor :提供关于类的单个构造方法的信息以及对它的访问权限。 * * 获取class文件对象的方式: * A:Object类的getClass()方法 * B:数据类型的静态属性class * C:Class类中的静态方法 * public static Class forName(Stirng className) * * 一般我们到底使用谁呢? * A:自己玩 任选一种 * B:开发 第三种 * 为什么呢?因为第三种是一个字符串,而不是一个具体的类名。这样我们就可以把这样的字符串配置到文件中。 * */ public class ReflectDemo { public static void main(String[] args) throws ClassNotFoundException { // 方式1 Person p = new Person(); Class c = p.getClass(); // 运行期间对象c拿到了对象p的class文件对象 Person p2 = new Person(); Class c2 = p2.getClass(); System.out.println(p == p2); // false /* * 这里为什么是等于呢,因为这里这里获取的 * 是同一个类的字节码文件对象,只有一个字节码文件对象,所以是true * */ System.out.println(c == c2); // true // 方式2 Class c3 = Person.class; System.out.println(c == c3); // 方式3 // ClassNotFoundException Class c4 = Class.forName("cn.itcast_01.Person"); // 这里的参数是获取该类的全路径名称 System.out.println(c == c4); } }
如果通过反射获取构造方法
package cn.itcast_02; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; /* * 通过反射获取构造方法并使用。 * */ public class ReflectDemo { public static void main(String[] args) throws Exception{ // 获取字节码文件对象 Class c = Class.forName("cn.itcast_01.Person"); // 获取所有的公共的构造方法 // public Constructor[] getConstructors():所有公共构造方法 // public Constructor[] getDeclaredConstructors():所有构造方法 // Constructor[] cons = c.getDeclaredConstructors(); // // 遍历获取的构造方法打印输出路径 // for(Constructor con: cons){ // System.out.println(con); // 获取单个构造方法 下面的...叫可变参数,可有可无,可多,可少 // public Constructor[] getConstructor(Class<?>... ParameterTypes): 获取指定的公共的构造方法 // 参数表示的是:你要获取的构造法方法的构造参数个数及数据类型的class字节码文件对象 Constructor con = c.getConstructor(); // 这里返回的是构造方法对象 // Person p = new Person(); // public T newInstance(Object... intargs) // 使用此Constructor 对象表示的构造方法来创建该构造方法的声明类的实例,并用指定的初始化参数初始化该实例。 Object obj = con.newInstance(); System.out.println(obj); } }
如何使用反射来创建对象
public class ReflectDemo{ public void main(Stirng[] args){ // 获取字字节码文件对象 Class c = Class.forName("cn.itcast_01.Person"); // 通过字节码文件对象获取构造方法 Constructor con = c.getConstructor(); // 通过反射来创建对象 Object obj2 = con.newInstance(); // 把创建好的对象强制转换为Person就可以调Person的方法了 Person p = (Person)obj2; p.show(); } }
通过反射获取带参构造方法,创建对象
import java.lang.reflect.Constructor; /* * 需求:通过反射区获取该构造方法并使用: * public Person(String name,int age,String address) * * * */ public class RefilectDemo02 { public static void main(String[] args) throws Exception { //获取字节码文件对象 Class c1 = Class.forName("cn.itcast_01.Person"); // 通过字节码文件对象获取带参构造方法对象 参数的意思是获取同等参数的构造方法,但是参数得夹class Constructor con = c1.getConstructor(String.class, int.class, String.class); // 通过带参构造方法对象创建对象 // public T newInstance(Object... initargs) Object obj = con.newInstance("林青霞",27,"北京"); System.out.println(obj); } }
通过反射获取私有构造方法
import java.lang.reflect.Constructor; /* * 需求:通过反射获取私有构造方法并使用 * */ public class RefilectDemo03 { public static void main(String[] args) throws Exception { // 获取字节码文件对象 Class c = Class.forName("cn.itcast_01.Person"); // 获取私有构造方法 // NoSuchMethodException:没这个方法异常 // 原因是一开始我们使用的方法只能获取公共的,下面这种方式就可以了。 //Constructor con = c.getConstructor(String.class); Constructor con= c.getDeclaredConstructor(String.class); // 用该私有构造方法创建对象 // IllegalAccessException:非法的访问异常。 // 暴力访问 con.setAccessible(true); // 值为true则指示反射的对象在使用时应该取消Java语言访 问检查。 /* * 这样就不安全了吗,可以在写class文件的时候把calss文件进行一个加密,每个class可以多写几句话,这样就不能通过反射进行还原了也可以通过异或某个字符,写进去,读的时候在异或一下 * */ Object obj = con.newInstance("风清扬"); System.out.println(obj); } }
通过反射获取成员变量
package cn.itcast_03; import java.lang.reflect.Constructor; import java.lang.reflect.Field; /* * 通过反射获取成员变量并使用 * */ public class ReflectDemo { public static void main(String[] args) throws Exception { // 获取字节码文件对象 Class c = Class.forName("cn.itcast_01.Person"); // 获取所有的公共成员变量 //Field[] fields = c.getFields(); // 获取所有的成员变量 // Field[] fields = c.getDeclaredFields(); // for(Field field :fields){ // System.out.println(field); // // } // 通过无参构造方法创建对象 Constructor con = c.getConstructor(); Object obj = con.newInstance(); // 获取单个的成员变量 // 获取address并对其赋值 Field addressField = c.getField("address"); // public void set(Object obj,Object value) // 将指定对象变量上此Field 对象表示的字段设置文指定的新值。 /* * 其实是给这个个obj对象的addressField属性设置为北京 * */ addressField.set(obj,"北京"); // 给obj对象的addressField字段设置值为北京 System.out.println(obj); // 获取name并对其赋值 Field f = c.getDeclaredField("name"); f.setAccessible(true); // 设置可访问私有的 f.set(obj,"张三"); System.out.println(obj); } }
通过反射获取构造方法进行创建对象来使用方法
package cn.itcast_03; import java.lang.reflect.Constructor; import java.lang.reflect.Method; public class ReflectDemo02 { public static void main(String[] args) throws Exception { // 获取字节码文件对象 Class c = Class.forName("cn.itcast_01.Person"); // 获取所有公共的方法 // Method[] methods = c.getMethods(); // Method[] methods = c.getMethods(); // 获取所有的方法 // Method[] methods = c.getDeclaredMethods(); // for(Method method :methods){ // System.out.println(method); // } // 调用位五参数方法 // 通过字节文件对象获取构造方法对象 Constructor con = c.getConstructor(); // 通过构造方法创建使用newInstance方法创建实例 Object obj = con.newInstance(); // 通过字节码文件对象获取show方法 Method m1 = c.getMethod("show"); // 使用字节码文件创建出来的show方法对象,调用invoke方法,传入一个show方法所在类的对象,进行使用show方法 m1.invoke(obj); System.out.println("--------------------"); // 调用带参方法 // public void method(Stirng s) // 通过字节码文件对象获取指定指定方法名称,类型,相同的方法 Method m2 = c.getMethod("method",String.class); // 通过字节码文件对象拿到的方法对象,使用obj方法进行调用,传入参数“hello”进行使用 m2.invoke(obj,"hello"); System.out.println("--------"); Method m3 = c.getMethod("getString",String.class,int.class); // Object objString = m3.invoke(obj,"hello",100); String s = (String) m3.invoke(obj,"hello",100); System.out.println(s); // 调用私有方法 // private void function() Method m4 = c.getDeclaredMethod("function"); // 打开java获取私有m4私有方法的机制 m4.setAccessible(true); // 使用obj队形啊调用私有方法m4 m4.invoke(obj); } }
反射练习题
习题1:通过配置we年运行类中的方法
class.txt文件内容
className=cn.itcast_04.Teacher #这里表示需要获取的类的全路径名称 methodName=love #这里表示需要使用获取类的指定方法
Teacher.java文件内容
package cn.itcast_04; public class Teacher { public void love(){ System.out.println("爱生活,爱讲课"); } }
Student.java文件的内容
package cn.itcast_04; public class Student { public void love(){ System.out.println("爱生活,爱java"); } }
Test.java主方法所在类的内容
package cn.itcast_04; import java.io.FileReader; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Properties; /* * 通过配置文件运行类中的方法 * 反射: * 需要配置文件配合使用。 * 使用class.txt代替 * 并且你知道有两个键。 * className * methodName * */ public class Test { public static void main(String[] args) throws Exception { // 反射钱的做法,不好 /* Student s = new Student(); s.love(); Teacher t = new Teacher(); s.love(); */ // 反射后的做法 // 加载键值对数据 Properties prop = new Properties(); // 创建一个初入流进行读取查class.txt里面的数据 FileReader fr = new FileReader("D:\\JavaSE\\Java_Fanshe\\java_Fanshe\\class.txt"); // 使用prop调用load方法以简单的线性格式从字符流读取属性列表(关键字和元素) prop.load(fr); // 挂你资源 fr.close(); // 获取数据,获取clas.txt文件中的键对应的值 String className = prop.getProperty("className"); // 获取需要的类的全路径名称,用去获取字节码码文件对象 String methodName = prop.getProperty("methodName"); // 获取需要的调用的指定的方法 // 反射 Class c = Class.forName(className); // 通过获取到的字节码文件对象获取无偿构造 Constructor con = c.getConstructor(); // 使用反射获取到的无参构造方法进行创建实例(对象) Object obj = con.newInstance(); // 调用方法 Method m = c.getMethod(methodName); // 使用invoke方法,通过obj来调用m方法进行使用 m.invoke(obj); } }
习题2:我给你ArrayList<Integer>的一个对象,我想在这个集合中添加一个字符串数据,如何实现呢?
package cn.itcast_04; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; /* * 我给你Array<Integer>的一个对象,我想在这个集合中添加一个字符串数据,如何实现? * */ public class ArrayListDemo { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { // 创建集合对象 ArrayList<Integer> array = new ArrayList<Integer>(); // 想要跨过泛型机制,就要使用反射进行跨越 // 获取集合array对象的class文件对象 Class c = array.getClass(); // 使用获取的集合的字节码文件对象来获取array集合的add方法对象 Method m = c.getMethod("add", Object.class); // 调用array的add方法,传入值是helleo m.invoke(array,"hello"); System.out.println(array); } }
习题3:写一个方法,public void setProperty(Object obj,String propertyName,Object value){},此方法可以将obj对象中名为propertyName的属性的值设为value。
Tool.java工具类
package cn.itcast_04; import java.lang.reflect.Field; public class Tool { public void setProperty(Object obj,String propertyName,Object value) throws NoSuchFieldException, IllegalAccessException { // 根据对象获取字节码文件对象 Class c = obj.getClass(); // 获取该对象的propertyName成员变量 Field field = c.getDeclaredField(propertyName); // 取消访问检查 field.setAccessible(true); // 给对象的成员变量赋值为指定的值 field.set(obj,value); } }
ToolDemo.java测试类
package cn.itcast_04; public class ToolDemo { public static void mai n(String[] args) throws NoSuchFieldException, IllegalAccessException { Person p = new Person(); Tool t = new Tool(); t.setProperty(p,"name","张三"); t.setProperty(p,"age",26); System.out.println(p); } } class Person{ private String name; public int age; @Override public String toString() { return name + '\t'+ age; } }
动态代理
动态代理
代理,本来应该自己做的事情,却请了别人来做,被请的人就是代理对象。
举例:春季回家买票让人代买
动态代理:在程序运行过程中产生的这个对象
而程序运行过程中产生对象其实就是我们刚才反射讲解的内容,所以,动态dialing其实就是通过反射来生成一个代理
在java中java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口就可以生成动态代理对象,JDK提供的动态代理只能针对接口做代理。我们有更强大的代理cglib
Proxy类中的方法创建动态代理类对象
public stataic Object newProxyInstance(ClassLoaderloader.Class<?>[] interfaces.InvocationHandler h)
最终会调用InvocationHandler的方法
InvocationHandler
Object invoke(Object proxy,Method method.Object[] args)
Proxy类中创建动态代理对象的方法的三个参数;
ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
Interface独享的数组,表示的是我将要给我需要代理的对象提供了一组什么接口,如果我提供了一组接口给他,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了