ch18 类加载机制与反射
-
类的加载,连接和初始化
系统可能在第一次使用某个类时加载该类,也可能采用预加载机制来加载某个类
-
JVM和类
-
一个Java程序就是一个Java虚拟机进程
-
两个JVM之间数据独立,所以一个类的静态属性并不会跨虚拟机进程共享
-
-
类的加载
-
加载->连接->初始化
-
类加载是指将类的class文件读入内存,并为之创建一个java.lang.Class对象,当程序中使用任何类时,系统都将为之建立一个java.lang.Class对象
-
系统中的所有类实际上也是实例,它们都是java.lang.Class的实例
-
类的加载由类加载器完成,类加载器通常由JVM提供,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器
-
类的来源:
-
本地文件系统class
-
JAR包
-
网络
-
动态编译Java文件
-
-
-
类的连接
-
验证->准备->解析
-
-
-
类的初始化
-
在类的初始化阶段,虚拟机负责对类进行初始化,主要就是对静态Field进行初始化
-
-
Java初始化静态Field:声明时指定初始值或使用静态初始化块
-
JVM初始化一个类的步骤:
-
如未加载和连接,则加载并连接该类
-
如其直接父类未初始化,则先初始化其直接父类
-
如有初始化语句,则依次执行这些初始化语句
-
当执行步骤2时,系统对直接父类的初始化步骤也遵循此步骤1~3,依次迭代直到java.lang.Object类。当程序主动
使用任何一个类时,系统会保证该类以及所有父类(包括直接父类和间接父类)都被初始化
-
类初始化的时机
-
当Java程序首次通过下面6种方式使用某个类或者接口时,系统就会初始化该类或接口:
-
创建类的实例 (new,反射,反序列化)
-
调用某个类的静态方法
-
访问(读写)某个类或接口的静态Field
-
使用反射方式强制创建某个类或接口对应的java.lang.Class对象,如Class.forName(“Person”)
-
初始化某个类的子类
-
直接使用java.exe运行某个主类
-
-
final型静态Field在编译时就能定下来,故不会触发类初始化行为
-
当使用ClassLoader类的loadClass()方法来加载某个类时,该方法只是加载该类,并不会执行该类的初始化。
-
使用Class的forName()静态方法才会导致强制初始化该类
-
-
类加载器
负责将.class文件加载到内存中,并为之生成对应的java.lang.Class对象
-
类加载器简介
-
在JVM中,一个类用其全限定类名和其类加载器作为其唯一标识
-
JVM启动时,会形成由3个类加载器组成的初始类加载器层次结构
-
Bootstrap ClassLoader:根类加载器(引导类加载器,负责加载Java的核心类)
-
Extension ClassLoader:扩展类加载器
-
System ClassLoader:系统类加载器(在JVM启动时加载来自java命令的-classpath选项,java.class.path
-
系统属性或者CLASSPATH环境变量所指定的JAR包和类路径。程序都可以通过ClassLoader的静态方法getSystemClassLoader()获取系统类加载器)
-
-
-
类加载机制
-
类型:全盘负责,父类委托,缓存机制
-
层级关系:用户类加载器->系统类加载器->扩展类加载器->根类加载器
-
getParent()方法
-
根类加载器不是由Java实现的
-
系统类加载器是AppClassLoader的实例,扩展类加载器是ExtClassLoader的实例,这两个类都是URLClassLoader的实例
-
类加载class的8个步骤
-
-
创建并使用自定义的类加载器
-
JVM中除了根类加载器之外的所有类加载器都是ClassLoader子类的实例
-
通过扩展ClassLoader子类,重写包含的方法可以实现自定义类加载器
-
两个关键方法:
-
loadClass(String name,boolean resolve)
-
findClass(String name)
-
-
推荐重写findClass()方法,而不是loadClass()方法
-
loadClass()执行步骤:
-
用findLoadedClass(String)来检查是否已经加载类,如果已经加载则返回;
-
在父类加载器上调用loadClass()方法。如果父类加载器为null,则使用根类加载器来加载;
-
调用findClass(String)方法查找类。
-
-
核心方法Class defineClass(String name,byte[] b,int off,int len),该方法负责将指定类的字节码文件(class文件)读入字节数组byte[] b内,并把它转换为Class对象
-
-
URLClassLoader类
-
两个构造器创建ClassLoader对象
-
通过loadClass()方法可以加载指定类
-
应用:从文件系统加载MySQL驱动,并使用该驱动来获取数据库连接、
-
-
-
通过反射查看类信息
-
获取Class对象
-
三种方式:
-
Class.forName(String),传入包含完整包名的全限定类名称
-
调用某个类的class属性来获取该类对应的Class对象
-
调用某个对象的getClass()方法
-
-
-
从Class中获取信息
-
构造器
-
Field
-
方法
-
Annotation
-
-
-
使用反射生成并操作对象
-
创建对象
-
使用Class对象的newInstance()方法来创建该Class对象对应类的实例,要求该Class对象的对应类有默认构造器
-
使用Class对象获取指定的Constructor对象,再调用Constuctor对象的newInstance()方法来创建该Class对象对应的实例。通过这种方式可以选择使用指定的构造器来创建实例
-
在很多JavaEE框架中都需要根据配置文件信息来创建Java对象,从配置文件读取的只是某个类的字符串类名,程序需要根据该字符串来创建对应的实例,就必须使用反射
-
-
调用方法
-
geMethods()方法或者getMethod()方法获取全部方法或者指定方法——分别返回Method对象数组或者Method对象
-
获得Method对象后,就可通过该Method的invoke(Object obj,Object...args)调用相应的方法,obj是执行该方法的主调,后面的args是执行该方法时传入该方法的实参
-
权限问题:setAccessible(boolean flag),实现通过反射来调用private方法,private构造器和访问private属性
-
-
访问属性值
-
通过Class对象的getFields()或getField()方法可以获取该类所包括的全部Field或指定Field
-
-
操作数组
-
java.lang.reflect包下还提供了一个Array类,Array对象可以代表所有的数组。程序可以通过Array来动态创建数组,操作数组元素等
-
Object arr=Array.newInstance(String.class,10)
-
-
-
使用反射生成JDK动态代理
-
使用Proxy和InvocationHandler创建动态代理
-
Proxy提供了用于创建动态代理类和代理对象的静态方法,它也是所有动态代理类的父类。如果在程序中为一个或多个接口动态的生成实现类。就可以使用Proxy来创建动态代理类;如果需要为一个或多个接口动态地创建实例,也可以使用Proxy来创建动态代理实例。
-
系统生成的每个代理对象都有一个与之关联的InvocationHandler对象,定义一个InvocationHandler实现类需要重写invoke()方法——调用代理对象的所有方法时都会被替换成调用该invoke()方法,Object invoke(Object proxy,Method method,Object[] args)
-
-
动态代理和AOP
-
JDK动态代理只能为接口创建动态代理
-
采用动态代理可以非常灵活的实现解耦,通常都是为指定的目标对象生成动态代理
-
这种动态代理在AOP(Aspect Orient Programming)中被称为AOP代理,AOP代理包含目标对象的全部方法,可以替代目标对象。但是二者存在差异:AOP代理里的方法可以在执行目标方法前后插入一些通用处理
-
-
-
反射和泛型
-
泛型和Class类
-
使用Class<T>泛型可以避免强制类型转换
-
对象工厂
-
-
使用反射来获取泛型信息
-
通过指定类对应的Class对象,可以获得类中的所有Field及其类型:
Class<?> a=f.getType()//普通类型
-
获取泛型类型:Type gType=f.getGenericType()
-
ParameterizedType对象
-
getRawType():返回没有泛型信息的原始类型
-
getActualTypeArguments():返回泛型参数的类型
-
-
-