自定义ClassLoader可以做什么
- 热部署。(在不重启程序的情况下动态替换类的实现)
- 应用的模块化和相互隔离。(不同的ClassLoader可以加载相同的类但是相互隔离、互不影响)
- 从不同的地方灵活加载。(系统默认的ClassLoader一般从本地的.class文件或者jar文件中加载字节码文件,通过自定义的ClassLoader,我们可以从共享的web服务器、数据库、缓存服务器等其他地方加载字节码)
类的基本加载过程和机制
负责加载类的我们称作类加载器,输入的是类名,输出的是Class对象。类加载器不只有一个,一般程序运行时会有三个。
- 启动类加载器(Bootstrap ClassLoader)这个加载器是java虚拟机的一部分,一般是由C++实现,负责加载Java的基础类,主要加载/lib/rt.jar。
- 扩展类加载器(Extension ClassLoader)这个加载器的实现类是sun.misc.Launcher$ExtClassLoader,它负责加载java的扩展类,主要是/lib/ex/*中的jar包。
- 应用程序类加载器(Application ClassLoader)这个加载器的实现类是sun.misc.Launcher$ClassLoade,它负责加载应用程序的类,包括自己写的和第三方类库。
三者的关系:可以认为三者是父子关系:即Application ClassLoader的父类是Extension ClassLoader,Extension ClassLoader的父类是Bootstrap Classloader
但是不是父子继承关系,而是父子委派关系,子ClassLoader有个变量parent指向父ClassLoader,在Class-Loader加载类的时候一般先通过夫ClassLoader加载。
- 判断是否已经加载,如果已经加载过了,直接返回Class对象,一个类只会被一个Class-Loader加载一次。
- 如果没有被加载,先让父ClassLoader去加载,如果加载成功,返回得到的Class对象。
- 在父ClassLoader没有加载成功的前提下,自己尝试加载类。
上述加载过程一般被称作双亲委派模型,也就是优先让父ClassLoader加载。这么做的原因为避免Java类库被覆盖。双亲委派虽然是一般模型,但是也有例外:
- 自定义加载顺序:自定义的ClassLoader可以不遵从“双亲委派”约定,不过即使不遵从也不能加载以java开头的类,这是由java的安全机制保证的。
- 网络加载顺序:在OSGI框架中,类加载器之间的关系是一个网,每个模块都有一个加载器,不同模块之间可能有依赖关系,即加载一个类的时候可能自己加载也可能委派给别的加载器加载。
- 父加载器委派给子加载器加载:例如JDNI服务。
一个程序运行时,一般会创建一个Application ClassLoader,在程序中用到ClassLoader的地方,如果没有指定,那么一般用的就是这个ClassLoader,所以Application ClassLoader也被称作系统类加载器。
理解ClassLoader
类ClassLoader是一个抽象类,Application ClassLoader和Extension ClassLoader的具体实现类分别是sun.misc.Launcher$AppClassLoader和sin.misc.Launcher$ExtClassLoader,BootStrapClassLoader
不是java实现的所以没有对应的类。
每个Class对象多有获取自己的加载器的方法:public ClassLoader getClassLoader()
ClassLoader可以获取父ClassLoader:public final ClassLoader getParent()
如果ClassLoader是Bootstrap ClassLoader返回值为null。1
2
3
4
5
6
7
8
9
10
11
12
13
14public class ClassLoaderDemo{
public static main(String [] args){
ClassLoaer cl1=ClassLoaderDemo.class.getClassLoaer();
while(cl1!=null){
System.out.println(c1.getClass.getName();
c1=cl1.getParent();
}
System.out.println(String.class.getClassLoaer());
}
}
//ruselt
//sun.misc.Launcher$AppClassLoader
//sun.misc.Launcher$ExtClassLoader
//null
ClassLoader可以获取默认的系统类加载器:public static ClassLoader getSystemClassLoader()
ClassLoader用于加载类的主要方法:public Class<?> loadClass(String name) throws ClassNotFoundException
ClassLoader的loadClass方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32public Class<T> loadClass(String name) throws ClassNotFoundException{
return loadClass(name , false);
}
//loadClass(String name,boolean flag)的主要代码
public Class<T> loadClass(String name,boolean resolve) throws ClassNotFoundException{
synchronized(getClassLoadingLock(name)){
//检查是否已经被加载
Class c=findLoadedClass(name);
if(c==null){
//没有被加载,先委派父ClassLoader或者BootStrapLoader加载
try{
if(parent!=null){
//委派父ClassLoader,resolve参数固定为false
c=parent.loadClass(name,false);
}else{
c=findBootStrapClassOrNull(name);
}
}catch(ClassNotFoundException e){
//没找到,捕获异常,然后尝试自己加载
}
if(c==null){
//自己加载,findClass才是当前的ClassLoader真正的加载方法
c=findClass(name);
}
}
if(resolve){
//链接,执行static语句块
resolveClass(c);
}
return c;
}
}
类加载的应用:可配置策略
通常可以通过classLoader的loadClass或者Class.forName自己加载类,但是什么情况下需要自己加载呢?
大多数应用使用面向接口编程,接口的具体实现类可能很多,适用于不同的场景,具体使用那个实现类在配置文件中配置,通过更改配置,不用改变代码,就可以改变程序的行文,在设计模式中,这个算是一种策略模式。
Demo
定义接口TestService1
2
3public interface TestService{
public void action();
}
定义ConfigurableStrategyDemo1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class ConfigurableStrategyDemo{
public static TestService CreateService(){
try{
Properties prop =new Properties();
String fileName="filepath/config.properties";
prop.load(new FileInputStream (fileName));
String className=prop.getProperty("service");
Class<?> cls=Classs.forName(className);
return (TestService)cls.newInstance();
}catchd(Excepiton e){
throw new RuntimeException(e);
}
}
public static void main(String [] args){
TestService service=CreateService();
service.action();
}
}
config.properties的内容service=io.caoxx123.service.TestService
自定义ClassLoader
1 | public ClassLoaderDemo extends ClassLoader{ |
上述代码并没有指定父加载器,默认的是ClassLoaer.getSystemClassLoader()的返回值。如果想要指定父加载器可以重写1
protected ClassLoader(ClassLoader parent)
classLoaderDemo可以拿到同一个类的不同Class对象:1
2
3
4
5
6
7
8
9
10
11
12public class TestClassLoader(){
public static void main(String [] args){
String className="io.caoxx123.service.TestService";
ClassLoaderDemo cl1=new ClassLoaderDemo();
Class<?> class1=cl1.loadClass(className);
ClassLoaderDemo cl2=new ClassLoaderDemo();
Class<?> class2=cl2.loadClass(className);
if(!class1==class2){
System.out.println("different");
}
}
}
上述例子可以看到,虽然class1、class2的类名是一样的,但是却不是同一个对象。通过这种可以实现
- 隔离:一个复杂的程序,内部可能是按模块,不同的模块可能使用的同一个类。
热部署:使用同一个ClassLoader,类只会被加载一次,加载后,即使class文件发生变化,再次加载,得到的还是原来的ClassLoader,而使用ClassLoaderDemo,则可以先创建一个新的ClassLoader,再用它加载Class得到的Class对象就是新的。
热加载的实现:定义接口
1
2
3public interface TestService{
public void sayTest();
}- HotDeployDemo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81public class HotDeployDemo {
private static final String CLASS_NAME = "io.caoxx123.TestImpl";
private static final String FILE_NAME = "test/"
+CLASS_NAME.replaceAll("\\.", "/")+".class";
private static volatile TestService TestService;
public static TestService getTestService() {
if (TestService != null) {
return TestService;
}
synchronized (HotDeployDemo.class) {
if (TestService == null) {
TestService = createTestService();
}
return TestService;
}
}
private static TestService createTestService() {
try {
MyClassLoader cl = new MyClassLoader();
Class<?> cls = cl.loadClass(CLASS_NAME);
if (cls != null) {
return (TestService) cls.newInstance();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void client() {
Thread t = new Thread() {
public void run() {
try {
while (true) {
TestService TestService = getTestService();
TestService.sayHello();
Thread.sleep(1000);
}
} catch (InterruptedException e) {
}
}
};
t.start();
}
public static void monitor() {
Thread t = new Thread() {
private long lastModified = new File(FILE_NAME).lastModified();
public void run() {
try {
while (true) {
Thread.sleep(100);
long now = new File(FILE_NAME).lastModified();
if (now != lastModified) {
lastModified = now;
reloadTestService();
}
}
} catch (InterruptedException e) {
}
}
};
t.start();
}
public static void reloadTestService() {
TestService = createTestService();
}
public static void main(String[] args) {
monitor();
client();
}
}