java类加载器与反射学习笔记

来源:互联网 发布:农村淘宝服务站申请表 编辑:程序博客网 时间:2024/05/21 00:47

概要:

  1. java类加载机制
    Java类加载器除了根类加载器外,其他累加器都是使用Java语言编写的,因此程序员完全可以开发自己的类加载器,通过使用自定义类
    加载器
    ,可以完成一些特定的功能。
  2. java反射机制
    重点介绍java.lang.reflect包下的接口和类,包括Class、Method、Field、Constructor和Array等,这些类分别代表类、方法、成员变量
    、构造器和数组。java程序可以通过这些反射工具类动态地获取某个对象、某个类的运行时信息,可以动态地创建Java对象,动态地调用Java
    方法,访问并修改指定对象的成员变量值。
  3. 使用Proxy和InvocationHandler实现JDK动态代理
    通过JDK动态代理向读者介绍高层次解耦的方法,还会讲解JDK动态代理和AOP(Aspect Orient Programming,面向切面编程)
    之间的内在关系

类的加载、连接和初始化

系统可能在第一次使用某个类时加载该类,也可能采用预加载机制来加载某个类。

jvm和类

同一个JVM的所有线程、所有变量都处于同一个进程里,他们都使用该JVM进程的内存区。当系统出现以下几种情况时,JVM进程将被终止
1. 程序运行到最后正常结束
2. 程序运行到使用System.exit()或Runtime.getRuntime().exit()代码处结束程序。
3. 程序执行过程中遇到为捕获的异常或错误而结束。
4. 程序所在平台强制结束了JVM进程

类的加载

当程序主动使用某个类时,如果该类还未被加载到内存中,这系统会通过加载、连接、初始化三个步骤来对该类进行初始化。
类的二进制数据的来源:
1. 从本地文件系统加载class文件
2. 从JAR包加载class文件,JDBC编程时用到的数据库驱动类就放在JAR文件中(mysql-connector.jar)
JVM可以从JAR文件中直接加载该class文件
3. 通过网络加载class文件
4. 把一个java源文件动态编译,并执行加载

类加载器无须等到”首次使用”该类时才加载该类,java虚拟机规范允许系统预先加载某些类。

类的连接

当类加载后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。
类连接可分为如下三个阶段:
(1) 验证: 验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致
(2) 准备: 类准备阶段则负责为类的类变量分配内存,并设置默认初始值
(3) 解析: 将类的二进制数据中的符号引用替换成直接引用

类的初始化

在类的初始化阶段,虚拟机负责对类进行初始化,主要就是对类变量进行初始化。在Java类中对类变量指定初始值有两种方式:
1. 声明类变量时指定初始值
2. 使用静态代码块为类变量指定初始值。
JVM初始化一个类包含如下几个步骤:
1. 假如这个类还没有被加载和连接,则程序先加载并连接该类
2. 假如该类的直接父类还没有被初始化,则先初始化其直接父类
3. 假如类中有初始化语句,则系统依次执行这些初始化语句
当执行第2个步骤时,系统对直接父类的初始化步骤也遵循此步骤1~3;如果该父类又有直接父类,则系统再次再次重复这三个步骤来
先初始化这个父类….. 依此类推,JVM最先初始化的总是java.lang.Object类。当程序主动使用任何一个类时,系统会保证该类
以及所有父类(包括直接父类和间接父类)都会被初始化

类初始化的时机

  1. 创建类的实例
    包括用new操作符来创建实例,通过反射来创建实例,通过反序列化的方式来创建实例
  2. 调用某个类的类方法(静态方法)
  3. 访问某个类或接口的类变量,或为该类变量赋值
  4. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。例如代码:Class.forName(“Person”)
    如果系统还未初始化Person类,则这行代码将会导致该Person类被初始化,并返回Person类对应的java.lang.class对象
  5. 初始化某个类的子类。当初始化某个类的子类时,该子类的所有父类都会被初始化。
  6. 使用java命令运行某个主类。当运行某个主类时,程序会先初始化该主类。

类加载器

类加载器简介

类加载器负责将.class 文件(可能在磁盘上,也可能在网络上)加载到内存中,并为之生成对应的java.lang.Class对象。
一旦一个类被载入JVM中,同一个类就不会被再次载入了。
正如一个对象有一个唯一的标志一样,一个载入JVM的类也有一个唯一的标识。在java中,一个类用其全限定类名(包括包名和类名)
作为标志;但在JVM中,一个类用全限定类名和其类加载器作为唯一标识。这意味着两个类加载器加载的同名类(Person、pg、k1)和
(Person、pg、k2)是不同的、他们加载的类也是完全不同、互不兼容
当JVM启动时,会形成有3个类加载器组成的初始类加载器层次结构

Bootstrap ClassLoader:

根类加载器,负责加载java的核心类在执行java.exe命令时,使用-Xbootclasspath选项或使用-D选项指定sun.boot.class.path系统属性值可以指定加载附加的类。根加载器不是java.lang.ClassLoader的子类,而是由JVM自身实现的。

Extension ClassLoader:

扩展类加载器,负责加载JRE的扩展目录(%JAVA_HOME%/jre/lib/ext 或者 由java.ext.dirs系统属性指定的目录)中jar包的类(自己开发中要用到的类可以放到该目录中,让jvm自动加载,为java扩展核心类以外的新功能)

System ClassLoader:

系统类加载器,也称为应用加载器,负责在jvm启动时加载来自java命令的-classpath选项、java.class.path系统属性或CLASSPATH环境变量所指定的JAR包和类路径。程序可以通过ClassLoader的静态方法getSystemClassloader()来获取系统类加载器。用户自定义的类加载器如果没有指定父加载器,则以系统类加载器为父加载器。

类加载机制

  1. 全盘负责
    当一个类加载器负责加载某个Class时,该Class所依赖的和引用
    的其他Class也将有该类加载器负责载入,除非显式使用另外一个
    类加载器来载入。
  2. 双亲委托
    所谓父类委托,则是先让parent类加载器视图加载该Class,只有在
    父类加载器无法加载该类时才尝试从自己的类路径中加载类。
    类加载器之间的父子关系并不是类继承上的父子关系.这里的父子关系
    是类加载实例之间的关系。
  3. 缓存机制
    缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用
    某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区不存在
    该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成
    Class对象,存入缓存区中。当修改了一个类后,必须重新启动jvm,让jvm
    重新将二进制数据加载到内存中转换为Class对象后修改才能生效。
    不重启jvm,jvm总是使用缓存中的旧的Class对象。

系统类加载器是AppClassLoader的实例
扩展类加载器是ExtClassLoaderr的实例
AppClassLoader和ExtClassLoader都是URLClassLoader的实例

类加载器加载Class大致要经过如下8个步骤:
1. 检测此Class是否载入过(即在缓存区中是否有此Class),若有直接
进入第8步,否则执行第2步.
2. 如果父类加载器不存在(如果没有父类加载器,要么parent是根类加载器,
要么本身就是根类加载器),跳到第4步执行;如果父类存在,则跳到第3步。
3. 请求使用父类加载器去载入目标类,如果载入成功则跳到第8步,否则跳到
第7步。
4. 请求使用根类加载器来载入目标类,如果成功载入则跳到第8步,否则接着执行第
5步。
5. 当前类加载器尝试寻找Class文件(从此ClassLoader相关的类路径中寻找),如果
找到则执行第6步,如果找不到则跳到第7步。
6. 从文件中载入Class,成功载入后跳到第8步。
7. 抛出ClassNotFoundException异常。
8. 返回对应的java.lang.Class对象

第5、6步允许重写ClassLoader的findClass()方法来实现自己的载入策略,甚至重写
loadClass()方法来实现自己的载入过程

创建并使用自定义的类加载器

JVM中除根类之外的所有类加载器都是ClassLoader子类的实例,开发者可以通过
扩展ClassLoader的子类,并重写该ClassLoader所包含的方法俩实现自定义的类加载
器。
ClassLoader中包含了大量的protected方法,即这些方法可以被子类重写。
ClassLoader类有如下两个关键方法:

  • loadClass(String name,boolean resolve)
    该方法为ClassLoader的入口点,loadClass()方法的执行步骤如下:
    1. 用findLoadedClass(String)来检查是否已经加载类,如果已经加载则直接返回
    2. 在父类加载器上调用loadClass()。如果父类加载器为null,则使用根加载器
      来加载
    3. 调用findClass(String)方法查找类
  • findClass(String)

如果需要实现自定义ClassLoader,一般重写findClass()方法,避免覆盖默认类加载器
的父类委托、缓冲机制两种策略

ClassLoader的另一个核心方法:
defineClass(String name,byte[] b,int off,int len)
该方法负责将指定类的字节码文件(即Class文件)读入字节数组byte[] b内,并转换
为Class对象,该字节码文件可以来源于文件系统、网络等

ClassLoader 的其他普通方法:

  • findSystemClass(String name):从本地文件系统装入文件。它在本地文件系统中寻找
    类文件,如果存在,就使用defineClass(String name)方法将原始字节转换成Class对象
    将该文件转换成类
  • static getSystemClassLoader():这是一个静态方法,用于返回系统类加载器
  • getParent():获取该类加载器的父类加载器
  • resovleClass(Class

URLClassloader

URlClassLoader classLoader= new URLClassLoader(urls);
创建URLClassLoader时传入一个URL数组参数,该ClassLoader就可以从
这系列URL指定的资源中加载指定类。
url以file:为前缀,表明从本地文件系统加载
url以http:为前缀,表明从互联网通过http协议来加载
url以ftp:为前缀,表明从互联网通过ftp访问来加载

通过反射查看类信息

在运行时发现对象和类的真实信息的两种做法:
1. 先用instanceof运算符进行判断,在利用强制类型转换将其转换成
运行时类型的变量

  1. 用反射来获取对象和类的真实信息

获取Class对象的三种方式

  1. 使用Class类的forName(String clazzName)静态方法。
    参数是类的全限定类名
  2. 类的class属性
  3. 对象的getClass()方法。该方法是java.lang.Object类中的一个方法

第2种方式的好处:
* 在编译阶段而不是在运行阶段赋值,有编译器检查,代码更安全
* 性能更好,不需要在运行时调用方法,在编译器就得出值

从Class中获取信息

取Class对应类所包含的构造器

 Constructor<T> getConstructor(Class<?> ...parameterTypes)

返回此Class对象对应类的、带指定形参列表(parameterTypes)的public构造器

Constructor<?>[] getConstructors()

返回此Class对象对应类的所有public构造器

 Constructor<T> getDeclaredConstructor(Class<?> ...parameterTypes)

返回此Class对象对应类、带指定形参列表的构造器,与构造器的访问权限无关

Constructor<?>[] getDeclaredConstructors()

返回此Class对象对应类的所有构造器,与构造器的访问权限无关

获取Class对应类所包含的方法

Method getMethod(String name,Class<?> ...parameterTypes)

返回此Class对象对应类的、带指定形参的public

Method getMethods()

返回此Class对象所表示的类的所有public方法

Method getDeclaredMethod(String name,Class<?> ...parameterTypes)

返回此Class对象所对应类的、带指定形参列表的方法,与方法的访问权限无关

Method getDeclaredMethods()

返回Class对象对应类的全部方法,与方法的访问权限无关

获取Class对应类包含的成员变量

Field getField(String name)

返回此Class对象对应类的、指定名称的public成员变量

Field[] getFields()

返回此Class对象所有的public成员变量

Field getDeclaredField(String name)

返回Class对象对应类的、指定名称的成员变量,与成员变量的访问权限无关

Field[] getDeclaredFields()

返回此Class对象对应类的全部成员变量,与成员变量的访问权限无关。

访问Class对应类上所包含的Annotation

<A extends Annotation> A getAnnotation(Class<A> annotationClass)

尝试获取该Class对象对应类上存在的、指定类型的Annotation: 如果该类型的
注解不存在,则返回null

<A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass)

尝试获取直接修饰该Class对象对应的类的存在的指定类型的
(直接修饰应该指的是不是继承来的注解和通过实现接口得到的注解)

Annotation[] getAnnotations()

返回修饰该Class对象对应类上存在的所有Annotation

Annotation[] getDeclaredAnnotations()

返回直接修饰该Class对象对应类上存在的所有Annotation

<A extends Annotation> A[] getAnnotationsByType(Class<A> annotationClass)

由于java8增加了重复注解功能,因此需要使用该方法获取直接修饰该类的、指定
类型的多个Annotation

<A extends Annotation> A[] getDeclaredAnnotationsByType(Class<A> annotationClass)

由于java8增加了重复注解功能,因此需要使用该方法获取直接修饰该类的、指定
类型的多个Annotation

访问内部类

Class<?> getDeclaredClasses()

返回该Class对象对应类里包含的全部内部类

访问外部类

Class<?> getDeclaringClass()

返回该Class对象对应类所在的外部类

Class<?>[] getInterfaces()

返回该Class对象对应类所实现的全部接口

访问该Class对象对应类所继承的父类

Class<? super T > getSuperclass()

获得Class对象对应类的修饰符、所在包、类名

int getModifiers()

获取类或接口的修饰符

Package getPackage()

获取类的包

String getName()

以字符串形式返回此Class对象所表示的类的简称

String getSimpleName()

返回此Class对象所表示的类的简称

判断该类是否为接口、枚举、注解类型

boolean isAnnotation()

返回此Class对象是否表示一个注解类型(由@interface定义)

boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)

判断此Class对象是否使用了Annotation修饰

boolean isAnonymousClass()

返回此Class对象是否是一个匿名类

boolean isArray()

返回此Class对象是否表示一个数组

boolean isEnum()

返回此Class对象是否表示一个枚举

boolean isInterface()

返回此Class对象是否表示一个接口

boolean isInstance(Object obj)

判断obj是否是此Class对象的实例,该方法可以完全代替instanceof操作符