Java编程8注解、反射、字节码、类加载机制

Annotation注解

注解入门
内置注解
自定义注解、元注解

什么是注解?

• Annotation是从JDK5.0开始引入的新技术。

• Annotation的作用:
– 不是程序本身,可以对程序作出解释。(这一点,跟注释没什么区别)
可以被其他程序(比如:编译器等)读取。(注解信息处理流程,是注解和注释的重大区别 。如果没有注解信息处理流程,则注解毫无意义)
比如说:可以对一个程序写一写hibernate的注解,写完以后可以被hibernate读到

• Annotation的格式:
– 注解是以“@注释名”在代码中存在的,还可以添加一些参数值,例如: @SuppressWarnings(value=“unchecked”)。

• Annotation在哪里使用?
– 可以附加在package, class, method, field等上面,相当于给它们添加了额外的辅助信 息,我们可以通过反射机制编程实现对这些元数据的访问
包、类、方法、属性

内置注解

• @Override
– 定义在java.lang.Override中,此注释只适用于修辞方法表示一个方法声明打算重写超类中的另一个方法声明。
Java编程8注解、反射、字节码、类加载机制_第1张图片
Java编程8注解、反射、字节码、类加载机制_第2张图片
对我们这个代码做了额外的解释,然后编译器又读取了它

• @Deprecated
– 定义在java.lang.Deprecated中,此注释可用于修辞方法、属性、类 ,表示不鼓励程序员使用这样的元素,通常是因为它很危险或存在更 好的选择。
Java编程8注解、反射、字节码、类加载机制_第3张图片
在这里插入图片描述

• @SuppressWarnings
– 定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息 。 可以修饰类、方法、属性…
– 与前两个注释有所不同,你需要添加一个参数才能正确使用,这些参数值都是已经定义好了的,我们选择性的使用就好了,参数如下:
Java编程8注解、反射、字节码、类加载机制_第4张图片
– @SuppressWarnings(“unchecked”)
– @SuppressWarnings(value={“unchecked”, “deprecation”})

Java编程8注解、反射、字节码、类加载机制_第5张图片
Java编程8注解、反射、字节码、类加载机制_第6张图片
Java编程8注解、反射、字节码、类加载机制_第7张图片
在这里插入图片描述

自定义注解

• 使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口

• 要点:
– @interface用来声明一个注解

• 格式为:
– public @interface 注解名 {定义体}
– 其中的每一个方法实际上是声明了一个配置参数。
– 方法的名称就是参数的名称
– 返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)
– 可以通过default来声明参数的默认值。
– 如果只有一个参数成员,一般参数名为value

• 注意:
注解元素必须要有值。我们定义注解元素时,经常使用空字符串、0作为默认值。
也经常使用负数(比如:-1)表示不存在的含义
Java编程8注解、反射、字节码、类加载机制_第8张图片
Java编程8注解、反射、字节码、类加载机制_第9张图片
Java编程8注解、反射、字节码、类加载机制_第10张图片
Java编程8注解、反射、字节码、类加载机制_第11张图片
Java编程8注解、反射、字节码、类加载机制_第12张图片
Java编程8注解、反射、字节码、类加载机制_第13张图片
这个注解即可以修饰方法又可以修饰类
Java编程8注解、反射、字节码、类加载机制_第14张图片
要加内容的时候,包含一些参数的信息
Java编程8注解、反射、字节码、类加载机制_第15张图片
Java编程8注解、反射、字节码、类加载机制_第16张图片
Java编程8注解、反射、字节码、类加载机制_第17张图片
再定义一简单的注解(如果注解中只有一个参数,一般把它定义为value )
Java编程8注解、反射、字节码、类加载机制_第18张图片
Java编程8注解、反射、字节码、类加载机制_第19张图片
Java编程8注解、反射、字节码、类加载机制_第20张图片
Java编程8注解、反射、字节码、类加载机制_第21张图片

元注解

• 元注解的作用就是负责注解其他注解。 Java定义了4个标准的 meta-annotation类型,它们被用来提供对其它 annotation 类型作说明。
• 这些类型和它们所支持的类在java.lang.annotation包中可以 找到
– @Target
– @Retention
– @Documented
– @Inherited

– @Target
• 作用: – 用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
Java编程8注解、反射、字节码、类加载机制_第22张图片
– @Target(value=ElementType.TYPE)

– @Retention
• 作用: – 表示需要在什么级别保存该注释信息,用于描述注解的生命周期
Java编程8注解、反射、字节码、类加载机制_第23张图片

注解仅仅是定义是没有意义的,还要再写注解的类的解析,这样才有意义。
注解信息处理流程。

使用反射机制读取注解信息

• 如上我们只讲解了注解的定义。我们必须再将注解的读取学会 才能轰然一体,彻底搞定注解。

 try {             
	 Class clazz = Class.forName("com.bjsxt.test.annotation.SxtStudent");                           
	 //获得类的所有有效注解             
	 Annotation[] annotations=clazz.getAnnotations();             
	 for (Annotation a : annotations) {                 
	 	System.out.println(a);             
	 }             
	 //获得类的指定的注解             
	 SxtTable st = (SxtTable) clazz.getAnnotation(SxtTable.class);             
	 System.out.println(st.value());                           
	 //获得类的属性的注解             
	 Field f = clazz.getDeclaredField("studentName");             
	 SxtField sxtField = f.getAnnotation(SxtField.class);             
	 System.out.println(sxtField.columnName()+"--"+sxtField.type()+"--"+sxtField.length());                           
	 //根据获得的表名、字段的信息,拼出DDL语句,然后,使用JDBC执行这个SQL,在数据库中生成相关的表                       
 } catch (Exception e) {             
	 e.printStackTrace();         
 }

注解作业

Java编程8注解、反射、字节码、类加载机制_第24张图片
ORM对象关系映射(Object Relationship Mapping)
类和表结构对应
属性和字段对应
对象和记录对应

使用注解完成类和表结构的映射关系
学习了反射机制后,我们可以定义注解处理流程读取这些注解,实现更加复杂的功能。

Java编程8注解、反射、字节码、类加载机制_第25张图片
定义注解
Java编程8注解、反射、字节码、类加载机制_第26张图片
这个注解用来修饰类
只有一个参数,把它和哪个表来对应
Java编程8注解、反射、字节码、类加载机制_第27张图片
Java编程8注解、反射、字节码、类加载机制_第28张图片
列名、类型、长度
定义一个新的注解,用来说明属性的一些特性
Java编程8注解、反射、字节码、类加载机制_第29张图片
用来修饰属性
列名是谁、类型是什么、长度是多少
Java编程8注解、反射、字节码、类加载机制_第30张图片
Java编程8注解、反射、字节码、类加载机制_第31张图片

第一步:定义注解本身
第二步:在类中使用注解
第三步:写注解解析程序,把这些注解读出来进行吧相关的处理

首先加载类,返回一个class对象,包含了这个类的全部信息。包含类名、属性、注解信息
获得这个类的所有注解
Java编程8注解、反射、字节码、类加载机制_第32张图片
获得类的制定注解
Java编程8注解、反射、字节码、类加载机制_第33张图片
获得类的属性的注解
Java编程8注解、反射、字节码、类加载机制_第34张图片
根据获得的
在这里插入图片描述

反射

Java动态性之:反射reflection

Java动态性

• 反射机制
• 动态编译
• 动态执行javascript代码
• 动态字节码操作

动态语言

• 动态语言
程序运行时,可以改变程序结构或变量类型
典型的语言: • Python、ruby、javascript等。 • 如下javascript代码:
在这里插入图片描述
• C, C++, JAVA不是动态语言,JAVA可以称之为“准动态语言”。但是JAVA有一定的动态性,我们可以利用反射机制、 字节码操作获得类似动态语言的特性。
• JAVA的动态性让编程的时候更加灵活!

反射机制reflection

• 反射机制
– 指的是可以于运行时加载、探知、使用编译期间完全未知的类

– 程序在运行状态中,可以动态加载一个只有名称的类
对于任意一个已加载的类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;
Class c = Class. forName (“com.bjsxt.test.User”);

– 加载完类之后,在堆内存中,就产生了一个 Class 类型的对象(一个类只有一个 Class对象),这个对象就包含了完整的类的结构信息。 我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过 这个镜子看到类的结构,所以,我们形象的称之为:反射。
Java编程8注解、反射、字节码、类加载机制_第35张图片
Java编程8注解、反射、字节码、类加载机制_第36张图片
Java编程8注解、反射、字节码、类加载机制_第37张图片
Java编程8注解、反射、字节码、类加载机制_第38张图片
Java编程8注解、反射、字节码、类加载机制_第39张图片
Java编程8注解、反射、字节码、类加载机制_第40张图片
Java编程8注解、反射、字节码、类加载机制_第41张图片
一个类只有这么一个class对象

Class类介绍

• java.lang.Class类十分特殊,用来表示java中类型 (class/interface/enum/annotation/primitive type/void)本 身。
– Class类的对象包含了某个被加载类的结构。一个被加载的类对应一个 Class对象。
– 当一个class被加载,或当加载器(class loader)的defineClass()被 JVM调用,JVM 便自动产生一个Class 对象。
• Class类是Reflection的根源。 – 针对任何您想动态加载、运行的类,唯有先获得相应的Class 对象

Class类的对象如何获取?

方法一:运用getClass()
方法二:运用Class.forName()
方法三:运用.class语法
Java编程8注解、反射、字节码、类加载机制_第42张图片
Java编程8注解、反射、字节码、类加载机制_第43张图片
Java编程8注解、反射、字节码、类加载机制_第44张图片
Java编程8注解、反射、字节码、类加载机制_第45张图片

Java编程8注解、反射、字节码、类加载机制_第46张图片

反射机制的常见作用

• 动态加载类、动态获取类的信息(属性、方法、构造器)
• 动态构造对象
• 动态调用类和对象的任意方法、构造器
• 动态调用和处理属性
• 获取泛型信息
• 处理注解

应用反射的API,获取类的信息(类的名字、属性、方法、构造器)

获取类的名字(全路径包名+类名、只是类名)

Java编程8注解、反射、字节码、类加载机制_第47张图片

获取属性信息

返回公共属性
Java编程8注解、反射、字节码、类加载机制_第48张图片
获取所有的属性
Java编程8注解、反射、字节码、类加载机制_第49张图片
获取指定的属性

获取方法信息

在这里插入图片描述
为什么有参数的方法要传入参数类型?
Java编程8注解、反射、字节码、类加载机制_第50张图片
为了区分重载的方法,重载的方法不能光凭名字进行区分
Java编程8注解、反射、字节码、类加载机制_第51张图片
Java编程8注解、反射、字节码、类加载机制_第52张图片

获取构造器信息

Java编程8注解、反射、字节码、类加载机制_第53张图片
Java编程8注解、反射、字节码、类加载机制_第54张图片
分别获取无参构造器和有参构造器
传递不同的参数类型,获取不同的构造器

构造对象

其实是调用了user的无参构造方法
Java编程8注解、反射、字节码、类加载机制_第55张图片
如果我们把user的无参构造器给删掉,就发现初始化异常,所以javabean必须要有无参构造方法。很多开源框架中就要大量的使用反射来构造对象。
Java编程8注解、反射、字节码、类加载机制_第56张图片
调用有参的构造器

Java编程8注解、反射、字节码、类加载机制_第57张图片

调用普通方法

Java编程8注解、反射、字节码、类加载机制_第58张图片
实现了动态调用

操作属性

Java编程8注解、反射、字节码、类加载机制_第59张图片
不能访问私有的属性
通过反射是有办法操作私有属性的,setAccessible这个属性不用做安全检查了,可以直接访问
Java编程8注解、反射、字节码、类加载机制_第60张图片
Java编程8注解、反射、字节码、类加载机制_第61张图片
上面是通过调用对象的方法获取到的值,下面是通过反射来调用到对象的属性

通过反射直接写属性的值、通过反射直接读属性的值

反射操作泛型generic

• Java采用泛型擦除的机制来引入泛型。Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换的麻烦。但是,一旦编译完成,所有的和泛型有关的类型全部擦除。

• 为了通过反射操作这些类型以迎合实际开发的需要,Java就新增了ParameterizedType, GenericArrayType,TypeVariable 和WildcardType几种类型来代表不能被归一到Class 类中的类型但是又和原始类型齐名的类型。

• ParameterizedType: 表示一种参数化的类型,比如Collection
• GenericArrayType: 表示一种元素类型是参数化类型或者类型变量的数组类型
• TypeVariable: 是各种类型变量的公共父接口
• WildcardType: 代表一种通配符类型表达式,比如?, ? extends Number, ? super Integer【 wildcard是一个单词:就是“通配符”】
Java编程8注解、反射、字节码、类加载机制_第62张图片
如果它是参数类型,强制转化为带泛型的参数类型
Java编程8注解、反射、字节码、类加载机制_第63张图片
Java编程8注解、反射、字节码、类加载机制_第64张图片

反射操作注解(annotation)

• 可以通过反射API:getAnnotations, getAnnotation获得相关的注解信息

//获得类的所有有效注解 
Annotation[] annotations=clazz.getAnnotations(); 
for (Annotation a : annotations) {    
System.out.println(a);
} 
//获得类的指定的注解 
SxtTable st = (SxtTable) clazz.getAnnotation(SxtTable.class); 
System.out.println(st.value());
//获得类的属性的注解 
Field f = clazz.getDeclaredField("studentName"); 
SxtField sxtField = f.getAnnotation(SxtField.class); 
System.out.println(sxtField.columnName()+"-"+sxtField.type()+"--"+sxtField.length());

Java编程8注解、反射、字节码、类加载机制_第65张图片
Java编程8注解、反射、字节码、类加载机制_第66张图片
反射是在运行期间有效的,所以这里要设为runtime

Java编程8注解、反射、字节码、类加载机制_第67张图片
Java编程8注解、反射、字节码、类加载机制_第68张图片

反射机制性能问题

• setAccessible
– 启用和禁用访问安全检查的开关
值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。
值为 false 则指示反射的对象应该实施 Java 语言访问检查。并不是为true 就能访问为false就不能访问。
– 禁止安全检查,可以提高反射的运行速度。
• 可以考虑使用:cglib/javaassist字节码操作

比较反射的执行效率
普通方法的调用
Java编程8注解、反射、字节码、类加载机制_第69张图片
用反射调用

不做访问检查的,反射调用
Java编程8注解、反射、字节码、类加载机制_第70张图片
在这里插入图片描述

动态编译

• JAVA 6.0引入了动态编译机制。
• 动态编译的应用场景:
– 可以做一个浏览器端编写java代码,上传服务器编译和运行的在线评测系统。
– 服务器动态加载某些类文件进行编译

• 动态编译的两种做法:
– 通过Runtime调用javac,启动新的进程去操作
Runtime run = Runtime.getRuntime();
Process process = run.exec(“javac -cp d:/myjava/ HelloWorld.java”);

– 通过JavaCompiler动态编译

• 通过JavaCompiler动态编译

public static int compileFile(String sourceFile){      
	//动态编译     
	JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();     
	int result = compiler.run(null, null, null,sourceFile);     
	System.out.println(result==0?" 编译成功 ":" 编译失败 ");     
	return result; 
}

• 第一个参数:为java编译器提供参数
• 第二个参数:得到 Java 编译器的输出信息
• 第三个参数:接收编译器的 错误信息
• 第四个参数:可变参数(是一个String数组)能传入一个或多个Java 源文件
• 返回值:0表示编译成功,非0表示编译失败

Java编程8注解、反射、字节码、类加载机制_第71张图片
Java编程8注解、反射、字节码、类加载机制_第72张图片
Java编程8注解、反射、字节码、类加载机制_第73张图片
Java编程8注解、反射、字节码、类加载机制_第74张图片

动态运行编译好的类

• 通过Runtime. getRuntime() 运行启动新的进程运行

Runtime run = Runtime.getRuntime();          
Process process = run.exec("java -cp  d:/myjava    HelloWorld");    
//      Process process = run.exec("java -cp "+dir+" "+classFile); 

Java编程8注解、反射、字节码、类加载机制_第75张图片
Java编程8注解、反射、字节码、类加载机制_第76张图片

• 通过反射运行编译好的类
//通过反射运行程序

public static void runJavaClassByReflect(String dir,String classFile) throws Exception{ 
    try {              
    	URL[] urls = new URL[] {new URL("file:/"+dir)};              
    	URLClassLoader loader = new URLClassLoader(urls);              
    	Class c = loader.loadClass(classFile);              
    	//调用加载类的main方法              
    	c.getMethod("main",String[].class).invoke(null, (Object)new String[]{});          
    } catch (Exception e) {              
    	e.printStackTrace();          
    } 
}

Java编程8注解、反射、字节码、类加载机制_第77张图片
Java编程8注解、反射、字节码、类加载机制_第78张图片
Java编程8注解、反射、字节码、类加载机制_第79张图片
Java编程8注解、反射、字节码、类加载机制_第80张图片

脚本引擎执行javascript代码

• JAVA脚本引擎是从JDK6.0之后添加的新功能。
• 脚本引擎介绍:
– 使得 Java 应用程序可以通过一套固定的接口与各种脚本引擎交互,从而达到在 Java 平台上调用各种脚本语言的目的。
– Java 脚本 API 是连通 Java 平台脚本语言的桥梁。
– 可以把一些复杂异变的业务逻辑交给脚本语言处理,这又大大提高了 开发效率。

• 获得脚本引擎对象
//获得脚本引擎对象
ScriptEngineManager sem = new ScriptEngineManager();
ScriptEngine engine = sem.getEngineByName(“javascript”);

• Java 脚本 API 为开发者提供了如下功能:
– 获取脚本程序输入,通过脚本引擎运行脚本并返回运行结果,这是最 核心的接口。
• 注意是:接口。Java可以使用各种不同的实现,从而通用的调用js、 groovy、python等脚本。
– Js使用了:Rhino (犀牛)
Rhino 是一种使用 Java 语言编写的 JavaScript 的开源实现,原先由Mozilla开发 ,现在被集成进入JDK 6.0。
在这里插入图片描述
– 通过脚本引擎的运行上下文在脚本和 Java 平台间交换数据。
– 通过 Java 应用程序调用脚本函数。

Rhino介绍

• Rhino 是一种使用 Java 语言编写的 JavaScript 的开源实现,原先由 Mozilla开发,现在被集成进入JDK 6.0
• 官方首页: – https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino

Java编程8注解、反射、字节码、类加载机制_第81张图片
Java编程8注解、反射、字节码、类加载机制_第82张图片

Java编程8注解、反射、字节码、类加载机制_第83张图片

Java编程8注解、反射、字节码、类加载机制_第84张图片

执行js文件
Java编程8注解、反射、字节码、类加载机制_第85张图片
在java文件中执行
Java编程8注解、反射、字节码、类加载机制_第86张图片

Java字节码操作

Java动态性的两种常见实现方式:
1.字节码操作
2.反射

运行时操作字节码可以让我们实现如下功能:
1.动态生成新的类
2.动态改变某个类的结构(添加、删除、修改 新的属性、方法)

优势:
比反射开销小,性能高
Javaasist性能高于反射,低于ASM

常见的字节码操作类库

BCEL
Byte Code Engineering Library,这是Apache Software Foundation的Jakarta项目的一部分。BCEL是Java classworking广泛使用的一个框架,它可以让您深入JVM汇编语言进行类操作的细节。BCEL与Javaasist有不同的处理字节码的方法,BCEL在实际的JVM指令层次上进行操作(BCEL拥有丰富的JVM指令级支持),而Javaasist所强调的是源代码级别的工作。

ASM
是一个轻量级Java字节码操作框架,直接涉及到JVM底层的操作和指令。

CGLIB
Code Generation Library,是一个强大的、高性能、高质量的Code生成类库,基于ASM实现。

Javaasist
是一个开源的分析、编辑、创建Java字节码的类库,性能较ASM差,跟cglib差不多,但是使用简单,很多开源框架都在使用它。

Javaasist库

Java编程8注解、反射、字节码、类加载机制_第87张图片
AOP ,面向切面编程,
反射,

Javaasist库的API详解

Java编程8注解、反射、字节码、类加载机制_第88张图片

Javaasist库的简单使用

创建一个全新的类

首先要导入Javaasist的jar包,在官网下载
Java编程8注解、反射、字节码、类加载机制_第89张图片
Java编程8注解、反射、字节码、类加载机制_第90张图片
在这里插入图片描述
在这里插入图片描述
Java编程8注解、反射、字节码、类加载机制_第91张图片

使用XJAD反编译工具查看类,将生成的class文件反编译成Java文件

需要下载XJAD
Java编程8注解、反射、字节码、类加载机制_第92张图片

Java编程8注解、反射、字节码、类加载机制_第93张图片
Java编程8注解、反射、字节码、类加载机制_第94张图片

Javaasist库的API详解

Java编程8注解、反射、字节码、类加载机制_第95张图片

获得已有类的信息
Java编程8注解、反射、字节码、类加载机制_第96张图片

Java编程8注解、反射、字节码、类加载机制_第97张图片
测试产生新的方法
Java编程8注解、反射、字节码、类加载机制_第98张图片
Java编程8注解、反射、字节码、类加载机制_第99张图片
Java编程8注解、反射、字节码、类加载机制_第100张图片

获取已有的方法,对已有的方法做修改
Java编程8注解、反射、字节码、类加载机制_第101张图片
Java编程8注解、反射、字节码、类加载机制_第102张图片
在这里插入图片描述
相当于加到了方法体的前面

也可以到后面加内容
Java编程8注解、反射、字节码、类加载机制_第103张图片
在9行加代码
Java编程8注解、反射、字节码、类加载机制_第104张图片
创建新的属性、getset方法
Java编程8注解、反射、字节码、类加载机制_第105张图片

构造器的操作
Java编程8注解、反射、字节码、类加载机制_第106张图片
注解操作
在这里插入图片描述
Java编程8注解、反射、字节码、类加载机制_第107张图片
Java编程8注解、反射、字节码、类加载机制_第108张图片

局限性

Java编程8注解、反射、字节码、类加载机制_第109张图片

JVM核心之JVM运行和类加载全过程

先用javac编译 然后java启动虚拟机加载执行类

类加载全过程

• 为什么研究类加载全过程?
– 有助于了解JVM运行过程
– 更深入了解java动态性,(解热部署、动态加载),提高程序的灵活性。

• 类加载机制
– JVM把class文件加载到内存,并对数据进行校验、解析和初始化,最终形成 JVM可以直接使用的Java类型的过程。
Java编程8注解、反射、字节码、类加载机制_第110张图片
三个过程:加载、链接、初始化

– 加载
• 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口。
这个过程需要类加载器参与。
Java编程8注解、反射、字节码、类加载机制_第111张图片
– 链接 将Java类的二进制代码合并到JVM的运行状态之中的过程
• 验证:
– 确保加载的类信息符合JVM规范,没有安全方面的问题。
• 准备:
– 正式为类变量(static变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配
• 解析
– 虚拟机常量池内的符号引用替换为直接引用的过程

– 初始化
• 初始化阶段是 ** 执行类构造器()方法的过程 ** 。类构造器()方法是由编译器自动收集 类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生的。
• 当初始化一个类的时候,如果发现其父类还没有进行过初始化、则需要先出发其父类的初始化
• 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
• 当访问一个java类的静态域时,只有真正声明这个域的类才会被初始化

Java编程8注解、反射、字节码、类加载机制_第112张图片
Java编程8注解、反射、字节码、类加载机制_第113张图片

Java编程8注解、反射、字节码、类加载机制_第114张图片
类加载的过程:先初始化静态变量和静态方法,再初始化类的。
Java编程8注解、反射、字节码、类加载机制_第115张图片
Java编程8注解、反射、字节码、类加载机制_第116张图片

例二:会先初始化父类的
在这里插入图片描述
Java编程8注解、反射、字节码、类加载机制_第117张图片
Java编程8注解、反射、字节码、类加载机制_第118张图片
Java编程8注解、反射、字节码、类加载机制_第119张图片
这说明:类的加载和初始化只有这么一次,只需要加载一次即可

• 类的主动引用(一定会发生类的初始化)
– new一个类的对象
– 调用类的静态成员(除了final常量)和静态方法
– 使用java.lang.reflect包的方法对类进行反射调用
– 当虚拟机启动,java Hello,则一定会初始化Hello类。说白了就是先启动main方法所在的类
– 当初始化一个类,如果其父类没有被初始化,则先会初始化他的父类

• 类的被动引用(不会发生类的初始化)
– 当访问一个静态域时,只有真正声明这个域的类才会被初始化
• 通过子类引用父类的静态变量,不会导致子类初始化
– 通过数组定义类引用,不会触发此类的初始化
– 引用常量不会触发此类的初始化(常量在编译阶段就存入调用类的常量池中了)

new一个类的对象
Java编程8注解、反射、字节码、类加载机制_第120张图片
调用类的静态成员(除了final常量)和静态方法
Java编程8注解、反射、字节码、类加载机制_第121张图片
引用常量不会触发此类的初始化(常量在编译阶段就存入调用类的常量池中了)
Java编程8注解、反射、字节码、类加载机制_第122张图片
使用java.lang.reflect包的方法对类进行反射调用
Java编程8注解、反射、字节码、类加载机制_第123张图片

当虚拟机启动,java Hello,则一定会初始化Hello类。说白了就是先启动main方法所在的类
当初始化一个类,如果其父类没有被初始化,则先会初始化他的父类

被动引用
通过数组定义类引用,不会触发此类的初始化
Java编程8注解、反射、字节码、类加载机制_第124张图片
当访问一个静态域时,只有真正声明这个域的类才会被初始化
Java编程8注解、反射、字节码、类加载机制_第125张图片
如上,B并没有被初始化,而A发生了初始化

深入类加载器

内容大纲
01类加载器原理
02类加载器树状结构、双亲委托(代理)机制
03自定义类加载器(文件、网络、加密)
04线程上下文类加载器
05服务器类加载原理和OSGI介绍

类加载器的作用

• 类加载器的作用
– 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class 对象,作为方法区类数据的访问入口。
Java编程8注解、反射、字节码、类加载机制_第126张图片
• 类缓存
• 标准的Java SE类加载器可以按要求查找类,但一旦某个类被加载到类加载 器中,它将维持加载(缓存)一段时间。不过,JVM垃圾收集器可以回收 这些Class对象。

java.class.ClassLoader类

• 作用:
– java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称, 找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class类的一个实例。
– 除此之外,ClassLoader还负责加载 Java 应用所需的资源,如图像文 件和配置文件等。

• 相关方法
– getParent() 返回该类加载器的父类加载器。
loadClass(String name) 加载名称为 name的类,返回的结果是 java.lang.Class类的实例。
findClass(String name) 查找名称为 name的类,返回的结果是 java.lang.Class类的实例。
findLoadedClass(String name) 查找名称为 name的已经被加载过的类,返回的结果是 java.lang.Class类的实 例。
defineClass(String name, byte[] b, int off, int len) 把字节数组 b中的内容转换成 Java 类,返回的结果是 java.lang.Class类的实例。这个方法被声明为 final的。
resolveClass(Class c) 链接指定的 Java 类。

– 对于以上给出的方法,表示类名称的 name参数的值是类的二进制名称。需要注意的是内部类的表示,如 com.example.Sample 1 和 c o m . e x a m p l e . S a m p l e 1和com.example.Sample 1com.example.SampleInner等表示方式。

Java编程8注解、反射、字节码、类加载机制_第127张图片
Java编程8注解、反射、字节码、类加载机制_第128张图片
Java编程8注解、反射、字节码、类加载机制_第129张图片
Java编程8注解、反射、字节码、类加载机制_第130张图片

类加载器的层次结构(树状结构)

• 引导类加载器(bootstrap class loader)
– 它用来加载 Java 的核心库(JAVA_HOME/jre/lib/rt.jar,或sun.boot.class.path路径下的 内容),是用原生代码来实现的,并不继承自 java.lang.ClassLoader。
– 加载扩展类和应用程序类加载器。并指定他们的父类加载器。

• 扩展类加载器(extensions class loader)
– 用来加载 Java 的扩展库(JAVA_HOME/jre/ext/*.jar,或java.ext.dirs路径下的内容) 。 Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
– 由sun.misc.Launcher$ExtClassLoader实现

• 应用程序类加载器(application class loader)
– 它根据 Java 应用的类路径(classpath, java.class.path 路径下的内容)来加载 Java 类。 一般来说,Java 应用的类都是由它来完成加载的。
– 由sun.misc.Launcher$AppClassLoader实现

• 自定义类加载器
– 开发人员可以通过继承 java.lang.ClassLoader类的方式 实现自己的类加载器,以满足一些特殊的需求。
Java编程8注解、反射、字节码、类加载机制_第131张图片

类加载器的代理模式

• 代理模式
– 交给其他加载器来加载指定的类

• 双亲委托机制
– 就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次追溯,直到最高的爷爷辈的,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载
– 双亲委托机制是为了保证 Java 核心库的类型安全。
• 这种机制就保证不会出现用户自己能定义java.lang.Object类的情况。
– 类加载器除了用于加载类,也是安全的最基本的屏障。

• 双亲委托机制是代理模式的一种
– 并不是所有的类加载器都采用双亲委托机制。
– tomcat服务器类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。 这与一般类加载器的顺序是相反的

Java编程8注解、反射、字节码、类加载机制_第132张图片
Java编程8注解、反射、字节码、类加载机制_第133张图片
双亲委派机制,不是加载的我们定义的这个,而是去jdk中找到父类去加载,能够保证安全性

java.class.ClassLoader类API

• 相关方法
– getParent() 返回该类加载器的父类加载器。

– loadClass(String name) 加载名称为 name的类,返回的结果是 java.lang.Class类的实例。
• 此方法负责加载指定名字的类,首先会从已加载的类中去寻找,如果没有找到;
从parent ClassLoader[ExtClassLoader]中加载;
如果没有加载到,则从Bootstrap ClassLoader中尝试加载(findBootstrapClassOrNull方法),
如果还是加载失败,则自己加载。如果还 不能加载,则抛出异常ClassNotFoundException。
• 如果要改变类的加载顺序可以覆盖此方法;

– findClass(String name) 查找名称为 name的类,返回的结果是 java.lang.Class类的实例。

– findLoadedClass(String name) 查找名称为 name的已经被加载过的类,返回的结果是 java.lang.Class类的实 例。

– defineClass(String name, byte[] b, int off, int len) 把字节数组 b中的内容转换成 Java 类,返回的结果是 java.lang.Class类的实例。这个方法被声明为 final的。

– resolveClass(Class c) 链接指定的 Java 类。

–表示类名称的 name参数的值是类的名称。需要注意的是内部类的表示,如 com.example.Sample 1 和 c o m . e x a m p l e . S a m p l e 1和 com.example.Sample 1com.example.SampleInner等表示方式。

自定义类加载器

• 文件系统类加载器
• 自定义类加载器的流程:
–继承:java.lang.ClassLoader
– 1、首先检查请求的类型是否已经被这个类装载器装载到命名空间中了,如果已经装载,直接返回;否则转入步骤2
– 2、委派类加载请求给父类加载器(更准确的说应该是双亲类加载器,真个虚拟机中各种类加载器最终会呈现树状结构),如果父类加 载器能够完成,则返回父类加载器加载的Class实例;否则转入步骤3
– 3、调用本类加载器的findClass(…)方法,试图获取对应的字节码,如果获取的到,
则调用defineClass(…)导入类型到方法区;
如 果获取不到对应的字节码或者其他原因失败,返回异常给loadClass(…), loadClass(…)转抛异常,
终止加载过程(注意:这里的 异常种类不止一种)。
– 注意:被两个类加载器加载的同一个类,JVM不认为是相同的类。

• 文件类加载器
• 网络类加载器
• 加密解密类加载器(取反操作,DES对称加密解密)

Java编程8注解、反射、字节码、类加载机制_第134张图片
Java编程8注解、反射、字节码、类加载机制_第135张图片
Java编程8注解、反射、字节码、类加载机制_第136张图片
Java编程8注解、反射、字节码、类加载机制_第137张图片
测试一下
Java编程8注解、反射、字节码、类加载机制_第138张图片
Java编程8注解、反射、字节码、类加载机制_第139张图片
Java编程8注解、反射、字节码、类加载机制_第140张图片
就加载到了这个类
可以加载多次
Java编程8注解、反射、字节码、类加载机制_第141张图片
Java编程8注解、反射、字节码、类加载机制_第142张图片
同一个类,同一个类加载器就是同一个对象
同一个类,不同的类加载器就不是同一个对象
Java编程8注解、反射、字节码、类加载机制_第143张图片
Java编程8注解、反射、字节码、类加载机制_第144张图片
Java编程8注解、反射、字节码、类加载机制_第145张图片

变成一个网络类加载器
Java编程8注解、反射、字节码、类加载机制_第146张图片
Java编程8注解、反射、字节码、类加载机制_第147张图片
Java编程8注解、反射、字节码、类加载机制_第148张图片
Java编程8注解、反射、字节码、类加载机制_第149张图片
自定义加密解密类加载器
Java编程8注解、反射、字节码、类加载机制_第150张图片
Java编程8注解、反射、字节码、类加载机制_第151张图片
做异或操作

Java编程8注解、反射、字节码、类加载机制_第152张图片
Java编程8注解、反射、字节码、类加载机制_第153张图片
Java编程8注解、反射、字节码、类加载机制_第154张图片
Java编程8注解、反射、字节码、类加载机制_第155张图片
在这里插入图片描述
Java编程8注解、反射、字节码、类加载机制_第156张图片
因为加密了,所以读取不到

所以现在定义一个解密的加载器

Java编程8注解、反射、字节码、类加载机制_第157张图片
Java编程8注解、反射、字节码、类加载机制_第158张图片
Java编程8注解、反射、字节码、类加载机制_第159张图片
Java编程8注解、反射、字节码、类加载机制_第160张图片
Java编程8注解、反射、字节码、类加载机制_第161张图片

线程上下文类加载器

双亲委托机制以及默认类加载器的问题

– 一般情况下, 保证同一个类中所关联的其他类都是由当前类的类加载器所加载的.。 比如,ClassA本身在Ext下找到,那么他里面new出来的一些类也就只能用Ext去查找了(不会低一个级别),所以有 些明明App可以找到的,却找不到了。

– JDBC API,他有实现的driven部分(mysql/sql server),我们的JDBC API都是由Boot或者Ext来载入的,但是 JDBC driver却是由Ext或者App来载入,那么就有可能找不到driver了。在Java领域中,其实只要分成这种Api+SPI( Service Provide Interface,特定厂商提供)的,都会遇到此问题。

– 常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。这些 SPI 的接口由 Java 核心库来提供,如 JAXP 的 SPI 接口定 义包含在 javax.xml.parsers 包中。SPI 的接口是 Java 核心库的一部分,是由引导类加载器来加载的;SPI 实现的 Java 类一般是由系统类加载器来加载的。引导类加载器是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库 。

• 通常当你需要动态加载资源的时候 , 你至少有三个 ClassLoader 可以选择 :
– 1.系统类加载器或叫作应用类加载器 (system classloader or application classloader)
– 2.当前类加载器
– 3.当前线程类加载器

• 当前线程类加载器是为了抛弃双亲委派加载链模式。
– 每个线程都有一个关联的上下文类加载器。如果你使用new Thread()方式生成新的线程,新线程将继承其父线程的上 下文类加载器。如果程序对线程上下文类加载器没有任何改动的话,程序中所有的线程将都使用系统类加载器作为上 下文类加载器。

• Thread.currentThread().getContextClassLoader()
• Thread.currentThread().setContextClassLoader()

Java编程8注解、反射、字节码、类加载机制_第162张图片
Java编程8注解、反射、字节码、类加载机制_第163张图片
Java编程8注解、反射、字节码、类加载机制_第164张图片
Java编程8注解、反射、字节码、类加载机制_第165张图片
为什么设置后还是这个app?因为我们自定义的加载器中设置了父类加载机制
Java编程8注解、反射、字节码、类加载机制_第166张图片
线程上下文的类加载机制

TOMCAT服务器的类加载机制

• 一切都是为了安全!
– TOMCAT不能使用系统默认的类加载器
• 如果TOMCAT跑你的WEB项目使用系统的类加载器那是相当危险的,你可 以直接是无忌惮是操作系统的各个目录了。
• 对于运行在 Java EE™容器中的 Web 应用来说,类加载器的实现方式与一 般的 Java 应用有所不同。
• 每个 Web 应用都有一个对应的类加载器实例。该类加载器也使用代理模 式(不同于前面说的双亲委托机制),所不同的是它是首先尝试去加载某个 类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的 。但也是为了保证安全,这样核心库就不在查询范围之内。

• 为了安全TOMCAT需要实现自己的类加载器
我可以限制你只能把类写在指定的地方,否则我不给你加载
Java编程8注解、反射、字节码、类加载机制_第167张图片

OSGI原理介绍

• OSGi™是 Java 上的动态模块系统。它为开发人员提供了面向服务和基于组件的运行环境,并提供标准的方式用来管理软件的生命周期。

开放服务网关初始
Open Service Gateway Initative

• OSGi 已经被实现和部署在很多产品上,在开源社区也得到了广泛的支持。Eclipse 就是基于 OSGi 技术来构建的。

• 原理:
– OSGi 中的每个模块(bundle)都包含 Java 包和类

模块可以声明它所依赖的需要导入 (import)的其它模块的 Java 包和类(通过 Import-Package),也可以声明导出( export)自己的包和类,供其它模块使用(通过 Export-Package)。

也就是说需要能 够隐藏和共享一个模块中的某些 Java 包和类。这是通过 OSGi 特有的类加载器机制来 实现的。

OSGi 中的每个模块都有对应的一个类加载器。它负责加载模块自己包含的 Java 包和类。当它需要加载 Java 核心库的类时(以 java开头的包和类),它会代理给 父类加载器(通常是启动类加载器)来完成。

当它需要加载所导入的 Java 类时,它会 代理给导出此 Java 类的模块来完成加载。模块也可以显式的声明某些 Java 包和类,必 须由父类加载器来加载。只需要设置系统属性 org.osgi.framework.bootdelegation 的值即可。

你可能感兴趣的:(Java语言基础)