java类加载器与反射学习笔记
来源:互联网 发布:农村淘宝服务站申请表 编辑:程序博客网 时间:2024/05/21 00:47
概要:
- java类加载机制
Java类加载器除了根类加载器外,其他累加器都是使用Java语言编写的,因此程序员完全可以开发自己的类加载器,通过使用自定义类
加载器
,可以完成一些特定的功能。 - java反射机制
重点介绍java.lang.reflect包下的接口和类,包括Class、Method、Field、Constructor和Array等,这些类分别代表类、方法、成员变量
、构造器和数组。java程序可以通过这些反射工具类动态地获取某个对象、某个类的运行时信息,可以动态地创建Java对象,动态地调用Java
方法,访问并修改指定对象的成员变量值。 - 使用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类。当程序主动使用任何一个类时,系统会保证该类
以及所有父类(包括直接父类和间接父类)都会被初始化
类初始化的时机
- 创建类的实例
包括用new操作符来创建实例,通过反射来创建实例,通过反序列化的方式来创建实例 - 调用某个类的类方法(静态方法)
- 访问某个类或接口的类变量,或为该类变量赋值
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。例如代码:Class.forName(“Person”)
如果系统还未初始化Person类,则这行代码将会导致该Person类被初始化,并返回Person类对应的java.lang.class对象 - 初始化某个类的子类。当初始化某个类的子类时,该子类的所有父类都会被初始化。
- 使用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()来获取系统类加载器。用户自定义的类加载器如果没有指定父加载器,则以系统类加载器为父加载器。
类加载机制
- 全盘负责
当一个类加载器负责加载某个Class时,该Class所依赖的和引用
的其他Class也将有该类加载器负责载入,除非显式使用另外一个
类加载器来载入。 - 双亲委托
所谓父类委托,则是先让parent类加载器视图加载该Class,只有在
父类加载器无法加载该类时才尝试从自己的类路径中加载类。
类加载器之间的父子关系并不是类继承上的父子关系.这里的父子关系
是类加载实例之间的关系。 - 缓存机制
缓存机制将会保证所有加载过的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()方法的执行步骤如下:- 用findLoadedClass(String)来检查是否已经加载类,如果已经加载则直接返回
- 在父类加载器上调用loadClass()。如果父类加载器为null,则使用根加载器
来加载 - 调用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运算符进行判断,在利用强制类型转换将其转换成
运行时类型的变量
- 用反射来获取对象和类的真实信息
获取Class对象的三种方式
- 使用Class类的forName(String clazzName)静态方法。
参数是类的全限定类名 - 类的class属性
- 对象的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操作符
- java类加载器与反射学习笔记
- Java类加载器与反射
- 类的加载机制与反射学习笔记
- java反射学习笔记之 动态加载类
- Java类加载与反射
- Java类加载机制与反射 jvm学习
- java反射学习笔记(2)----java中的静态加载类和动态加载类
- 反射 学习笔记之动态加载类
- Java SE学习笔记-基础加强之类加载机制与反射
- 反射与类加载器
- java反射类学习笔记
- java [反射] [类加载器]
- java [反射] [类加载器]
- java反射---类加载器
- JavaSE学习笔记之类的加载机制与反射
- Java反射、类加载与垃圾回收
- Java中类加载机制与反射
- Java中类加载机制与反射
- 1001. A+B Format (20)
- 【后台开发拾遗】异步代码同步化
- 显著性检测基础知识
- 预测的两类核心算法
- CodeForces
- java类加载器与反射学习笔记
- Codeforces 868D (Codeforces Round #438 D) Huge Strings 分治+哈希
- C语言、Java学习笔记(三)---几种简单的排序算法
- 大数据学习18:Hive在mysql的元数据表的关系和含义
- 可重入内核 & 可重入函数
- 102. Binary Tree Level Order Traversal
- CodeForces
- PAT备考
- java之流程控制与数组