0.参考
1.反射
1.1.需求引入
“通过外部配置文件, 在不修改Java程序源码的情况下, 来控制程序”
1.2反射概述
- Reflection(反射) :动态语言的关键, 反射机制允许程序在执行器借助Reflection API 取得任何类的内部信息, 并能直接操作任意对象的内部属性及方法- 静态语言:编译时加载相关的类. 若无则编译报错, 依赖性过强. 运行时结构不可变. 如Java- 动态语言:运行时加载需要的类. 若硬编码了某类而运行时却不使用, 即使不存在该类也不报错, 低依赖.- Java不是动态语言但是可以称之为"准动态语言",即Java有一定的动态性,我们可以利用反射机制,字节码的操作来获得类时动态语言的特性- 类加载后, 堆内存中的方法去就产生了一个 Class 对象(类模板, 一个类只有一个 Class类模板). 这个Class对象包含了完整的(加载)类的所有结构信息. 我们可以通过这个对象看到类的结构. 这个Class对象就如一面镜子, 两边分别是Class对象 / 具体类的结构信息.
1.3主要API
- java.lang.Class: 模板类, 反射的源头- Hello.java --(javac.exe编译)--> Hello.class(字节码文件) --(java.exe解释运行)--> 即将class字节码加载到内存中--内存中有一个对应的具体的运行时类: Class的一个实例, 即为Hello类的唯一类模板- java.lang.refiect.Method: 类的方法- java.lang.refiect.Field: 类的成员变量- java.lang.refiect.Constructor:类的构造器- java.lang.reflect.AccessibleObject: 它是 Field、Method 和 Constructor 对象的基类。它提供了**将反射的对象标记为在使用时取消默认 Java 语言访问安全控制检查**的能力。对于公共成员、默认(打包)访问成员、受保护成员和私有成员,在分别使用 Field、Method 或 Constructor 对象来设置或获得字段、调用方法,或者创建和初始化类的新实例的时候,会执行访问检查。- isAccessible:public boolean isAccessible(): 获得此对象的 accessible 标志的值。- 此对象的返回值 就是accessible的标志值,一般情况下无论 public,private,protected,默认等修饰的属性的access值均为false(注意他的意思并非是访问权限而是对该自己执行安全检查)。- setAccessible 设置访问检查/暴破操作:public static void setAccessible(object obj, boolean flag) throws SecurityException- 对通过 getDeclaredField 获取得到的私有属性(方法/构造器 同理), 若不暴破(通过如上方法)--即设置可访问, 就对其进行操作, 则报 IllegalAccessException.- 使用单一安全性检查(为了提高效率)为一对象设置 accessible 标志。如果存在安全管理器,则在 ReflectPermission("suppressAccessChecks") 权限下调用 checkPermission 方法。当flag 为 true,表示不开启安全检查,但是不能更改输入 object的任何元素的可访问性(例如,如果元素对象是 Class 类的 Constructor 对象),则会引发 SecurityException。如果发生 SecurityException,对于少于(不包括)发生异常的元素的数组元素,可以将对象的可访问性设置为 flag;对于超出(包括)引发异常的元素的那些元素,则不更改其可访问性.- 一般情况下,我们并不能对类的私有字段进行操作,利用反射也不例外,但有的时候,例如**要序列化的时候,我们又必须有能力去处理这些字段,这时候,我们就需要调用AccessibleObject上的setAccessible()方法来允许这种访问,而由于反射类中的Field,Method和Constructor继承自AccessibleObject,因此,通过在这些类上调用setAccessible()方法,我们可以实现对这些字段的操作。( **但有的时候这将会成为一个安全隐患,为此,我们可以启用java.security.manager来判断程序是否具有调用setAccessible()的权限。默认情况下,内核API和扩展目录的代码具有该权限,而类路径或通过URLClassLoader加载的应用程序不拥有此权限。例如:当我们以这种方式来执行上述程序时将会抛出异常
>java -Djava.security.manager ExampleExplorerException in thread "main" java.security.AccessControlException: access denied (java.lang.reflect.ReflectPermission suppressAccessChecks)at java.security.AccessControlContext.checkPermission(Unknown Source)
1.4反射原理图
1.5反射的调用优化-关闭访问检查
- Method / Field / Constructor 对象(的共同父类AccessibleObject)的 setAccessible(boolean flag)方法- setAccessible(): 启动或禁止访问安全检查的开关- flag参数:- true取消访问检查, 提高反射效率. 否则关闭.
1.6反射的优缺点
- 优点:- 动态创建/使用对象, 十分灵活. 所以作为框架核心- 缺点:- 使**用反射基本是解释执行, 对执行速度有影响**
2.Class类 (rt.jar中的: java.lang.Class)
2.1基本介绍
- Class也是类, 默认继承Object- Class是**final**类- Class类对象不是new出来的(其唯一构造器为private), 而是随着JVM启动被 Bootstrap启动类加载器 加载- 每个(运行时)类的Class对象, 它对应的Class类只会被加载一次(做一次加载操作), 内存中只有一份对应的Class对象(类模板)- 每个类的每个实例的 getClass: Class<?> 方法, 都能获取自己对应的类模板
/*** Object.getClass():Class<?> 而 Class又继承自Object*/@Testpublic void myTest(){MyBean bean = new MyBean();Class<? extends MyBean> beanClass = bean.getClass();System.out.println(MyBean.class);// class com.ryze.admin.bean.MyUser$MyBean 美刀号:因为是内部类System.out.println(bean.getClass());//class com.ryze.admin.bean.MyUser$MyBeanSystem.out.println(beanClass == MyBean.class);// trueSystem.out.println(beanClass instanceof Class);// trueSystem.out.println(beanClass instanceof Object);// trueSystem.out.println(String.class);// class java.lang.StringSystem.out.println("test".getClass());// class java.lang.String}class MyBean{}
- Class对象(的一系列API)可以完整地获取某个类的完整结构- Class对象是存放在堆中- 类对应的字节码二进制数据(class文件)(也叫类的元数据), 存放在方法区.
![0@L]37G53`6(I~{XGJ19Y93.png](/uploads/projects/ryze_java@javase/0ef3caf051794dd0b000296af420aae4.png)
2.2举例
那些类型可以有Class对象?
1. class:外部类,成员类(成员内部类,静态内部类),局部内部类,匿名内部类1. interface:接口1. []:数组1. enum:枚举1. annotation:注解@interface1. primitive type:基本数据类型1. void
2.3类加载时到生成对象过程
- 
3.类加载
3.1基本介绍
- 反射是Java实现动态特性的关键, 即 **通过反射实现类的动态加载(运行时确定)**
3.2类加载时机
- 静态加载1. (new)对象时1. 在加载子类时, 会优先加载其直接父类, 同理依次向上先加载最高父类(只是加载对应类, 并不会创建一个父类对象)1. 类被加载的时候, 其中的**静态**代码块、**静态**方法、**静态**变量以及前三者涉及/依赖的所有类也会被加载1. 调用某类中的**静态**属性- 动态加载- 反射: Class.forName("com.ryze.MyTest");
3.3类加载过程图
3.3.1 .java文件到初始化全过程
3.3.2类加载具体阶段

1. 加载阶段: JVM在该阶段将字节码从不同数据源(class文件/jar包/网络等)转化为二进制字节流加载到内存中, 并且生成一个对应的Class类对象1. 连接阶段1. 验证:- 目的是为了确保class文件的字节流中包含的信息符合当前虚拟机的要求, 并且不会危害虚拟机自身安全- 验证条目:1. 文件格式验证(是否以魔数 "oxcafebabe"" 开头)1. 元数据验证1. 字节码验证1. 符号引用验证- 可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施, 缩短虚拟机类加载的时间2. **准备:**- JVM在该阶段对 静态变量: ①分配内存 ②默认初始化
public class TestPreparation{// 在 准备阶段 中, 如下属性的情况public int n1 = 10; // 非静态, JVM不会对其分配内存等操作public static int n2 = 20; // 静态, 分配内存--非默认初始化0public static final int n3 = 30;// 静态, 分配内存--final默认初始化30.// final常量, 一旦赋值即不可变}
3. 解析:- 虚拟机将常量池中的符号引用替换为直接引用的过程3. **初始化阶段 Initialization:**- 到初始化阶段, 才真正开始执行类中定义的Java程序代码, 此阶段是执行 <clinit>() 方法的过程- <clinit>() 方法是由 编译器 按照语句在源码中出现顺序, 依次自动收集类中的所有静态变量的赋值动作 和 静态代码块中的语句, 并进行合并.
public class ClassLoad03 {public static void main(String[] args) {System.out.println(B.num); // 100/*clinit() {num = 300;num = 100;}//合并: num = 100*/}}class B {static {num = 300;}static int num = 100;}
- **虚拟机会保证一个类的 clinit() 方法在多线程环境中被正确地加锁**(ClassLoader.loadClass() 中Synchronized同步代码块)、同步. 若多个线程同时区初始化一个类, 结果也只有一个线程去执行这个类的 clinit()方法, 其它线程都 阻塞等待, 直到活动线程执行 clinit()方法完毕. -- 保证内存中某个类(内存中)只有一份对应的Class对象
4.反射机制中的常用方法
* 获取Class实例1. 调用运行类的class属性:例如 Class clazz = Person.class2. 通过运行时类的对象: Person p= new Person; class clazz = p.getClass();3. 调用Class的静态方法 : Class.forName("com.adong.java.person");4. 使用类的加载器: CassLoader cl = 当前类.class.getClassLoader(); Class claszz = Cl.loadClass("com.adong.java.Rerson")* Class方法* newInstance() : 创建对应的运行时类的对象:调用空参构造器,空参构造器不能为private* Field getField(string name) : 获取运行时类的指定属性,要求属性为public* Feild对象: set(Object obj,object obj) : 参数一,为要设置对象,属性二属性值设置为多少.* get(object obj):获取属性的值* Field getDeclaredField(String name):获取运行时类指定的属性* setAccessible(true) :保证当前属性是 可以访问的* Field[] getFields(): 获取当前运行时类及其父类中声明为public访问权限的属性* Field[] getDeclaredFields():获取当前运行时类中声明的所有属性(不包含父类声明的属性)* 可以通过field[] 数组元素知道权限修饰符,数据类型,变量名* Field中方法* int getModifiers(): 返回权限修饰符,可以Modifier.toString(int modifier);获取具体* Class getType() : 获取数据类型, 获取的class对象.getName()获取具体类型* String getName(); : 获取变量名* Method getDeclareMethod(String name,形参.class) : 参数一获取方法的名称,参数二获取方法的形参列表* Object invoke(object obj,String name);参数一为方法的调用者,参数二给方法赋值的实参* 静态的化要是使用运行类.class* Method[] getMethods() : 获取当前运行时类及其所有父类声明为public权限的方法* Method[] getDeclareMethods();:获取当前运动时类中声明的所有方法(不包含父类中声明的方法)* Method中方法* Annotation[] getAnnotations():获取方法声明 的注解* int getModifiers(): 返回权限修饰符,可以Modifier.toString(int modifier);获取具体* Class getReturnType() : 返回值类型 .getName() 获取类型字符串* String getName() : 方法名* Class[] getParameterType() :参数列表* class[] getExceptionTypes() : 获取异常类型* Constructor getDEclaredConstructor(参数.class):获取指定为构造器,参数为构造器的参数列表* Constructor[] getConstructors() : 获取当前运行时类为public权限的构造器* Constructor[] getDeclaredConstructors() ; 获取当前运行时类的全部构造器* Class getSuperclass():获取运行类的父类* Type getGenericSuperclass() : 获取运行时带泛型的父类* Class[] getInterface() :获取运行时类实现的接口* Package getPackage() : 获取运行时类所在的包* Annotation[] getAnnotations():获取运行时类声明的注解* 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。* 哪些类型可以有Class 对象?* (1)class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类* (2)interface:接口* (3)[]:数组* (4)enum:枚举* (5)annotation:注解@interface* (6)primitive type:基本数据类型* (7)void*//getFields():获取当前运行时类及其父类中声明为public访问权限的属性Field[] fields = clazz.getFields();//getDeclaredFields():获取当前运行时类中声明的所有属性。(不包含父类中声明的属性) Field[] declaredFields = clazz.getDeclaredFields();//获取属性的修饰符,数据类型,变量名Field[] declaredFields = clazz.getDeclaredFields();for(Field f : declaredFields){ //1.权限修饰符 int modifier = f.getModifiers(); System.out.print(Modifier.toString(modifier) + "\t"); //2.数据类型 Class type = f.getType(); System.out.print(type.getName() + "\t"); //3.变量名 String fName = f.getName(); System.out.print(fName); }//getMethods():获取当前运行时类及其所有父类中声明为public权限的方法 Method[] methods = clazz.getMethods();//getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法) Method[] declaredMethods = clazz.getDeclaredMethods();//获取方法的注解,权限修饰符,返回值,方法名,参数列表,抛出的异常 Class clazz = Person.class; Method[] declaredMethods = clazz.getDeclaredMethods(); for(Method m : declaredMethods){ //1.获取方法声明的注解 Annotation[] annos = m.getAnnotations(); //2.权限修饰符 System.out.print(Modifier.toString(m.getModifiers()) + "\t"); //3.返回值类型 System.out.print(m.getReturnType().getName() + "\t"); //4.方法名 System.out.print(m.getName()); //5.形参列表 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.抛出的异常 Class[] exceptionTypes = m.getExceptionTypes(); if(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() + ","); } }//getConstructors():获取当前运行时类中声明为public的构造器 Constructor[] constructors = clazz.getConstructors();//getDeclaredConstructors():获取当前运行时类中声明的所有的构造器 Constructor[] declaredConstructors = clazz.getDeclaredConstructors();// 获取运行时类的父类 Class superclass = clazz.getSuperclass();// 获取运行时类的带泛型的父类 Type genericSuperclass = clazz.getGenericSuperclass();//获取运行时类的带泛型的父类的泛型 Type genericSuperclass = clazz.getGenericSuperclass(); ParameterizedType paramType = (ParameterizedType) genericSuperclass; //获取泛型类型 Type[] actualTypeArguments = paramType.getActualTypeArguments(); //二者选一 System.out.println(actualTypeArguments[0].getTypeName()); System.out.println(((Class)actualTypeArguments[0]).getName());// 获取运行时类实现的接口 Class[] interfaces = clazz.getInterfaces();//获取运行时类所在的包 Package pack = clazz.getPackage();//获取运行时类声明的注解 Annotation[] annotations = clazz.getAnnotations();//创建运行时类的对象 Class clazz = Person.class; Person p = (Person) clazz.newInstance();//操作运行时类中的指定的属性 Class clazz = Person.class; //创建运行时类的对象 Person p = (Person) clazz.newInstance(); //1. getDeclaredField(String fieldName):获取运行时类中指定变量名的属性 Field name = clazz.getDeclaredField("name"); //2.保证当前属性是可访问的 name.setAccessible(true); //3.获取、设置指定对象的此属性值 name.set(p,"Tom"); //参数一为要操作属性的对象,参数二修改属性的值,静态的成员变量 :name.set(Person.class,"Tom");修改 //获取变量的值 System.out.println(name.get(p));//操作运行时类中的指定的方法 Class clazz = Person.class; //创建运行时类的对象 Person p = (Person) clazz.newInstance(); //1.获取指定的某个方法 : getDeclaredMethod():参数1 :指明获取的方法的名称 参数2:指明获取的方法的形参列表 Method show = clazz.getDeclaredMethod("show", String.class); //2.保证当前方法是可访问的 show.setAccessible(true); //3. 调用方法的invoke():参数1:方法的调用者 参数2:给方法形参赋值的实参,invoke()的返回值即为对应类中调用的方法的返回值。 Object returnValue = show.invoke(p,"CHN"); //String nation = p.show("CHN"); System.out.println(returnValue); System.out.println("*************如何调用静态方法*****************"); // private static void showDesc() Method showDesc = clazz.getDeclaredMethod("showDesc"); showDesc.setAccessible(true); //如果调用的运行时类中的方法没有返回值,则此invoke()返回null // Object returnVal = showDesc.invoke(null); Object returnVal = showDesc.invoke(Person.class); System.out.println(returnVal);//null //获取指定的构造器:getDeclaredConstructor():参数:指明构造器的参数列表 Constructor constructor = clazz.getDeclaredConstructor(String.class); //保证此构造器是可访问的 constructor.setAccessible(true); //调用此构造器创建运行时类的对象 Person per = (Person) constructor.newInstance("Tom");//读取配置文件Properties pros = new Properties(); //此时的文件默认在当前的module下。 //读取配置文件的方式一: //FileInputStream fis = new FileInputStream("jdbc.properties"); //FileInputStream fis = new FileInputStream("src\\jdbc1.properties"); //pros.load(fis); //读取配置文件的方式二:使用ClassLoader //配置文件默认识别为:当前module的src下 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.获取一个系统类加载器• ClassLoader classloader = ClassLoader.getSystemClassLoader();• System.out.println(classloader);• //2.获取系统类加载器的父类加载器,即扩展类加载器• classloader = classloader.getParent();• System.out.println(classloader);• //3.获取扩展类加载器的父类加载器,即引导类加载器• classloader = classloader.getParent();• System.out.println(classloader);• //4.测试当前类由哪个类加载器进行加载• classloader = Class.forName("exer2.ClassloaderDemo").getClassLoader();• System.out.println(classloader);• //5.测试JDK提供的Object类由哪个类加载器加载• classloader = Class.forName("java.lang.Object").getClassLoader();• System.out.println(classloader);• //*6.关于类加载器的一个主要方法:getResourceAsStream(String str):获取类路径下的指定文件的输入流• InputStream in = null;• in = this.getClass().getClassLoader().getResourceAsStream("exer2\\test.properties");• System.out.println(in);反射的应用:动态代理
