Annotation注解
注解入门
内置注解
自定义注解、元注解
什么是注解?
• Annotation是从JDK5.0开始引入的新技术。
• Annotation的作用:
– 不是程序本身,可以对程序作出解释。(这一点,跟注释没什么区别)
– 可以被其他程序(比如:编译器等)读取。(注解信息处理流程,是注解和注释的重大区别 。如果没有注解信息处理流程,则注解毫无意义)
比如说:可以对一个程序写一写hibernate的注解,写完以后可以被hibernate读到
• Annotation的格式:
– 注解是以“@注释名”在代码中存在的,还可以添加一些参数值,例如: @SuppressWarnings(value=“unchecked”)。
• Annotation在哪里使用?
– 可以附加在package, class, method, field等上面,相当于给它们添加了额外的辅助信 息,我们可以通过反射机制编程实现对这些元数据的访问。
包、类、方法、属性
内置注解
• @Override
– 定义在java.lang.Override中,此注释只适用于修辞方法,表示一个方法声明打算重写超类中的另一个方法声明。
对我们这个代码做了额外的解释,然后编译器又读取了它
• @Deprecated
– 定义在java.lang.Deprecated中,此注释可用于修辞方法、属性、类 ,表示不鼓励程序员使用这样的元素,通常是因为它很危险或存在更 好的选择。
• @SuppressWarnings
– 定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息 。 可以修饰类、方法、属性…
– 与前两个注释有所不同,你需要添加一个参数才能正确使用,这些参数值都是已经定义好了的,我们选择性的使用就好了,参数如下:
– @SuppressWarnings(“unchecked”)
– @SuppressWarnings(value={“unchecked”, “deprecation”})
自定义注解
• 使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口
• 要点:
– @interface用来声明一个注解
• 格式为:
– public @interface 注解名 {定义体}
– 其中的每一个方法实际上是声明了一个配置参数。
– 方法的名称就是参数的名称
– 返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)
– 可以通过default来声明参数的默认值。
– 如果只有一个参数成员,一般参数名为value
• 注意:
注解元素必须要有值。我们定义注解元素时,经常使用空字符串、0作为默认值。
也经常使用负数(比如:-1)表示不存在的含义
这个注解即可以修饰方法又可以修饰类
要加内容的时候,包含一些参数的信息
再定义一简单的注解(如果注解中只有一个参数,一般把它定义为value )
元注解
• 元注解的作用就是负责注解其他注解。 Java定义了4个标准的 meta-annotation类型,它们被用来提供对其它 annotation 类型作说明。
• 这些类型和它们所支持的类在java.lang.annotation包中可以 找到
– @Target
– @Retention
– @Documented
– @Inherited
– @Target
• 作用: – 用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
– @Target(value=ElementType.TYPE)
– @Retention
• 作用: – 表示需要在什么级别保存该注释信息,用于描述注解的生命周期
注解仅仅是定义是没有意义的,还要再写注解的类的解析,这样才有意义。
注解信息处理流程。
使用反射机制读取注解信息
• 如上我们只讲解了注解的定义。我们必须再将注解的读取学会 才能轰然一体,彻底搞定注解。
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();
}
注解作业
ORM对象关系映射(Object Relationship Mapping)
类和表结构对应
属性和字段对应
对象和记录对应
使用注解完成类和表结构的映射关系
学习了反射机制后,我们可以定义注解处理流程读取这些注解,实现更加复杂的功能。
定义注解
这个注解用来修饰类
只有一个参数,把它和哪个表来对应
列名、类型、长度
定义一个新的注解,用来说明属性的一些特性
用来修饰属性
列名是谁、类型是什么、长度是多少
第一步:定义注解本身
第二步:在类中使用注解
第三步:写注解解析程序,把这些注解读出来进行吧相关的处理
首先加载类,返回一个class对象,包含了这个类的全部信息。包含类名、属性、注解信息
获得这个类的所有注解
获得类的制定注解
获得类的属性的注解
根据获得的
反射
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对象),这个对象就包含了完整的类的结构信息。 我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过 这个镜子看到类的结构,所以,我们形象的称之为:反射。
一个类只有这么一个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语法
反射机制的常见作用
• 动态加载类、动态获取类的信息(属性、方法、构造器)
• 动态构造对象
• 动态调用类和对象的任意方法、构造器
• 动态调用和处理属性
• 获取泛型信息
• 处理注解
应用反射的API,获取类的信息(类的名字、属性、方法、构造器)
获取类的名字(全路径包名+类名、只是类名)
获取属性信息
返回公共属性
获取所有的属性
获取指定的属性
获取方法信息
为什么有参数的方法要传入参数类型?
为了区分重载的方法,重载的方法不能光凭名字进行区分
获取构造器信息
分别获取无参构造器和有参构造器
传递不同的参数类型,获取不同的构造器
构造对象
其实是调用了user的无参构造方法
如果我们把user的无参构造器给删掉,就发现初始化异常,所以javabean必须要有无参构造方法。很多开源框架中就要大量的使用反射来构造对象。
调用有参的构造器
调用普通方法
实现了动态调用
操作属性
不能访问私有的属性
通过反射是有办法操作私有属性的,setAccessible这个属性不用做安全检查了,可以直接访问
上面是通过调用对象的方法获取到的值,下面是通过反射来调用到对象的属性
通过反射直接写属性的值、通过反射直接读属性的值
反射操作泛型generic
• Java采用泛型擦除的机制来引入泛型。Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换的麻烦。但是,一旦编译完成,所有的和泛型有关的类型全部擦除。
• 为了通过反射操作这些类型以迎合实际开发的需要,Java就新增了ParameterizedType, GenericArrayType,TypeVariable 和WildcardType几种类型来代表不能被归一到Class 类中的类型但是又和原始类型齐名的类型。
• ParameterizedType: 表示一种参数化的类型,比如Collection
• GenericArrayType: 表示一种元素类型是参数化类型或者类型变量的数组类型
• TypeVariable: 是各种类型变量的公共父接口
• WildcardType: 代表一种通配符类型表达式,比如?, ? extends Number, ? super Integer【 wildcard是一个单词:就是“通配符”】
如果它是参数类型,强制转化为带泛型的参数类型
反射操作注解(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());
反射是在运行期间有效的,所以这里要设为runtime
反射机制性能问题
• setAccessible
– 启用和禁用访问安全检查的开关
值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。
值为 false 则指示反射的对象应该实施 Java 语言访问检查。并不是为true 就能访问为false就不能访问。
– 禁止安全检查,可以提高反射的运行速度。
• 可以考虑使用:cglib/javaassist字节码操作
比较反射的执行效率
普通方法的调用
用反射调用
不做访问检查的,反射调用
动态编译
• 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表示编译失败
动态运行编译好的类
• 通过Runtime. getRuntime() 运行启动新的进程运行
Runtime run = Runtime.getRuntime();
Process process = run.exec("java -cp d:/myjava HelloWorld");
// Process process = run.exec("java -cp "+dir+" "+classFile);
• 通过反射运行编译好的类
//通过反射运行程序
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();
}
}
脚本引擎执行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
执行js文件
在java文件中执行
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库
AOP ,面向切面编程,
反射,
Javaasist库的API详解
Javaasist库的简单使用
创建一个全新的类
首先要导入Javaasist的jar包,在官网下载
使用XJAD反编译工具查看类,将生成的class文件反编译成Java文件
需要下载XJAD
Javaasist库的API详解
获得已有类的信息
测试产生新的方法
获取已有的方法,对已有的方法做修改
相当于加到了方法体的前面
也可以到后面加内容
在9行加代码
创建新的属性、getset方法
构造器的操作
注解操作
局限性
JVM核心之JVM运行和类加载全过程
先用javac编译 然后java启动虚拟机加载执行类
类加载全过程
• 为什么研究类加载全过程?
– 有助于了解JVM运行过程
– 更深入了解java动态性,(解热部署、动态加载),提高程序的灵活性。
• 类加载机制
– JVM把class文件加载到内存,并对数据进行校验、解析和初始化,最终形成 JVM可以直接使用的Java类型的过程。
三个过程:加载、链接、初始化
– 加载
• 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口。
这个过程需要类加载器参与。
– 链接 将Java类的二进制代码合并到JVM的运行状态之中的过程
• 验证:
– 确保加载的类信息符合JVM规范,没有安全方面的问题。
• 准备:
– 正式为类变量(static变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配
• 解析
– 虚拟机常量池内的符号引用替换为直接引用的过程
– 初始化
• 初始化阶段是 ** 执行类构造器()方法的过程 ** 。类构造器()方法是由编译器自动收集 类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生的。
• 当初始化一个类的时候,如果发现其父类还没有进行过初始化、则需要先出发其父类的初始化
• 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
• 当访问一个java类的静态域时,只有真正声明这个域的类才会被初始化
类加载的过程:先初始化静态变量和静态方法,再初始化类的。
例二:会先初始化父类的
这说明:类的加载和初始化只有这么一次,只需要加载一次即可
• 类的主动引用(一定会发生类的初始化)
– new一个类的对象
– 调用类的静态成员(除了final常量)和静态方法
– 使用java.lang.reflect包的方法对类进行反射调用
– 当虚拟机启动,java Hello,则一定会初始化Hello类。说白了就是先启动main方法所在的类
– 当初始化一个类,如果其父类没有被初始化,则先会初始化他的父类
• 类的被动引用(不会发生类的初始化)
– 当访问一个静态域时,只有真正声明这个域的类才会被初始化
• 通过子类引用父类的静态变量,不会导致子类初始化
– 通过数组定义类引用,不会触发此类的初始化
– 引用常量不会触发此类的初始化(常量在编译阶段就存入调用类的常量池中了)
new一个类的对象
调用类的静态成员(除了final常量)和静态方法
引用常量不会触发此类的初始化(常量在编译阶段就存入调用类的常量池中了)
使用java.lang.reflect包的方法对类进行反射调用
当虚拟机启动,java Hello,则一定会初始化Hello类。说白了就是先启动main方法所在的类
当初始化一个类,如果其父类没有被初始化,则先会初始化他的父类
被动引用
通过数组定义类引用,不会触发此类的初始化
当访问一个静态域时,只有真正声明这个域的类才会被初始化
如上,B并没有被初始化,而A发生了初始化
深入类加载器
内容大纲
01类加载器原理
02类加载器树状结构、双亲委托(代理)机制
03自定义类加载器(文件、网络、加密)
04线程上下文类加载器
05服务器类加载原理和OSGI介绍
类加载器的作用
• 类加载器的作用
– 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class 对象,作为方法区类数据的访问入口。
• 类缓存
• 标准的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 1和com.example.SampleInner等表示方式。
类加载器的层次结构(树状结构)
• 引导类加载器(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 核心库的类型安全。
• 这种机制就保证不会出现用户自己能定义java.lang.Object类的情况。
– 类加载器除了用于加载类,也是安全的最基本的屏障。
• 双亲委托机制是代理模式的一种
– 并不是所有的类加载器都采用双亲委托机制。
– tomcat服务器类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。 这与一般类加载器的顺序是相反的
双亲委派机制,不是加载的我们定义的这个,而是去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 1和com.example.SampleInner等表示方式。
自定义类加载器
• 文件系统类加载器
• 自定义类加载器的流程:
–继承:java.lang.ClassLoader
– 1、首先检查请求的类型是否已经被这个类装载器装载到命名空间中了,如果已经装载,直接返回;否则转入步骤2
– 2、委派类加载请求给父类加载器(更准确的说应该是双亲类加载器,真个虚拟机中各种类加载器最终会呈现树状结构),如果父类加 载器能够完成,则返回父类加载器加载的Class实例;否则转入步骤3
– 3、调用本类加载器的findClass(…)方法,试图获取对应的字节码,如果获取的到,
则调用defineClass(…)导入类型到方法区;
如 果获取不到对应的字节码或者其他原因失败,返回异常给loadClass(…), loadClass(…)转抛异常,
终止加载过程(注意:这里的 异常种类不止一种)。
– 注意:被两个类加载器加载的同一个类,JVM不认为是相同的类。
• 文件类加载器
• 网络类加载器
• 加密解密类加载器(取反操作,DES对称加密解密)
测试一下
就加载到了这个类
可以加载多次
同一个类,同一个类加载器就是同一个对象
同一个类,不同的类加载器就不是同一个对象
变成一个网络类加载器
自定义加密解密类加载器
做异或操作
因为加密了,所以读取不到
所以现在定义一个解密的加载器
线程上下文类加载器
• 双亲委托机制以及默认类加载器的问题
– 一般情况下, 保证同一个类中所关联的其他类都是由当前类的类加载器所加载的.。 比如,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()
为什么设置后还是这个app?因为我们自定义的加载器中设置了父类加载机制
线程上下文的类加载机制
TOMCAT服务器的类加载机制
• 一切都是为了安全!
– TOMCAT不能使用系统默认的类加载器。
• 如果TOMCAT跑你的WEB项目使用系统的类加载器那是相当危险的,你可 以直接是无忌惮是操作系统的各个目录了。
• 对于运行在 Java EE™容器中的 Web 应用来说,类加载器的实现方式与一 般的 Java 应用有所不同。
• 每个 Web 应用都有一个对应的类加载器实例。该类加载器也使用代理模 式(不同于前面说的双亲委托机制),所不同的是它是首先尝试去加载某个 类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的 。但也是为了保证安全,这样核心库就不在查询范围之内。
• 为了安全TOMCAT需要实现自己的类加载器。
我可以限制你只能把类写在指定的地方,否则我不给你加载
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 的值即可。