自定义class loader

原作者文章出处:自定义class loader

自定义class loader_第1张图片

上图为JDK 8中ClassLoader的族谱,可见除了总所周知的AppClassLoader和ExtClassLoader外,JDK中还有很多其它ClassLoader,既然这么多ClassLoader存在,也就不那么神秘了,那么如何自定义ClassLoader了?最简单的方式当然是继承现有的ClassLoader实现类,避免重复发明轮子,所以我们先了解一下ClassLoader类的实现。

findClass方法:这是自定义class loader类必须覆盖的方法,用于告诉class loader到哪里去加载类,比如某个目录或者JAR URL等。参数name为要加载的类全名,如java.lang.String。该方法作为类加载的步骤之一被loadClass()方法调用。

1
2
3
     protected  Class findClass(String name)  throws  ClassNotFoundException {
         throw  new  ClassNotFoundException(name);
     }

loadClass方法:这是classloader加载类的入口方法,觉得方法实现代码写得很够清晰就全贴出来了,附加一张简单的活动图辅助说明方法逻辑。

自定义class loader_第2张图片

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
protected  Class loadClass(String name,  boolean  resolve)
         throws  ClassNotFoundException
     {
         synchronized  (getClassLoadingLock(name)) {
             // First, check if the class has already been loaded
             Class c = findLoadedClass(name);
             if  (c ==  null ) {
                 long  t0 = System.nanoTime();
                 try  {
                     if  (parent !=  null ) {
                         c = parent.loadClass(name,  false );
                     else  {
                         c = findBootstrapClassOrNull(name);
                     }
                 catch  (ClassNotFoundException e) {
                     // ClassNotFoundException thrown if class not found
                     // from the non-null parent class loader
                 }
 
                 if  (c ==  null ) {
                     // If still not found, then invoke findClass in order
                     // to find the class.
                     long  t1 = System.nanoTime();
                     c = findClass(name);
 
                     // this is the defining class loader; record the stats
                     sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                     sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                     sun.misc.PerfCounter.getFindClasses().increment();
                 }
             }
             if  (resolve) {
                 resolveClass(c);
             }
             return  c;
         }
     }

getParent方法:用于获取class loader的parent,没有返回null。

1
public  final  ClassLoader getParent()


findLoadedClass方法:返回已经加载的类。该方法直接调用本地方法实现。

1
protected  final  Class findLoadedClass(String name)


resolveClass方法:用于连接一个Class,如果已经连接则什么都不做。该方法直接调用本地方法实现。

1
protected  final  void  resolveClass(Class c)


defineClass方法:将字节码转换为Class实例,即加载.class文件后需要创建一个对应的java.lang.Class对象用于描述该Class。该方法直接调用本地方法实现。

1
protected  final  Class defineClass(String name,  byte [] b,  int  off,  int  len)

借一个图,理解更清晰点:

自定义class loader_第3张图片 自定义class loader_第4张图片

根据以上分析,自定义一个class loader 只需要集成ClassLoader类并覆盖findClass方法即可,我们也自己搞一个看看。

Car接口:

1
2
3
4
5
package  com.stevex.app.classloader;
 
public  interface  Car {
     public  void  run();
}

BMW类:

1
2
3
4
5
6
7
8
9
package  com.stevex.app.classloader;
 
public  class  BMW  implements  Car {
 
     public  void  run() {
         System.out.println( "BMW" );
     }
 
}

SteveClassLoader类:自定义的class loader类

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
package  com.stevex.app.classloader;
 
import  java.io.ByteArrayOutputStream;
import  java.io.IOException;
import  java.io.InputStream;
 
public  class  SteveClassLoader  extends  ClassLoader {
     @Override
     public  Class findClass(String name) {
         byte [] bt = loadClassData(name);
         return  defineClass(name, bt,  0 , bt.length);
     }
 
     private  byte [] loadClassData(String className) {
         // read class
         InputStream is = getClass().getClassLoader().getResourceAsStream(
                 className.replace( "." "/" ) +  ".class" );
         ByteArrayOutputStream byteSt =  new  ByteArrayOutputStream();
         // write into byte
         int  len =  0 ;
         try  {
             while  ((len = is.read()) != - 1 ) {
                 byteSt.write(len);
             }
         catch  (IOException e) {
             e.printStackTrace();
         }
         // convert into byte array
         return  byteSt.toByteArray();
     }
 
}

SteveClassLoaderTest类:测试类,SteveClassLoader默认构造函数会设置System class loader为parent,测试时执行loadClass方法会发现BMW类是委托AppClassLoader加载的,所以AppClassLoader可以访问到,不会出错;

执行findClass2方法就会发生错误,因为我们直接使用SteveClassLoader加载BMW类,而不是委托给parent加载,根据class loader命名空间规则(简单来讲,每个class loader 都有自己唯一的命名空间,每个class loader 只能访问自己命名空间中的类,一个class可以被不同的class loader重复加载,但同一个class只能被同一个class loader加载一次,如果一个类是委托parent加载的,那么加载后,这个类就类似共享的,parent和child都可以访问到这个类,因为parent是不会委托child加载类的,所以child加载的类parent访问不到),子加载器的命名空间包含了parent加载的所有类,反过来则不成立,SteveClassLoaderTest类是AppClassLoader加载的,所以其看不见由SteveClassLoader加载的BMW类,但Car接口是可以访问的,所以赋给Car类型不会出错。

在findClass1方法中,我们直接使用反射调用run方法就没事了。

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
package  com.stevex.app.classloader;
 
import  java.lang.reflect.InvocationTargetException;
import  java.lang.reflect.Method;
 
public  class  SteveClassLoaderTest {
     public  static  void  main(String[] args)  throws  InstantiationException,
             IllegalAccessException, NoSuchMethodException, SecurityException,
             IllegalArgumentException, InvocationTargetException, ClassNotFoundException {
 
         SteveClassLoader loader =  new  SteveClassLoader();
         
         loadClass(loader);
 
         findClass1(loader);
         
         //findClass2(loader);
     }
 
     private  static  void  findClass1(SteveClassLoader loader)  throws  InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
         Class c = loader.findClass( "com.stevex.app.classloader.BMW" );
         System.out.println( "Loaded by :"  + c.getClassLoader());
         Object ob = c.newInstance();
         Method md = c.getMethod( "run" );
         md.invoke(ob);
     }
 
     private  static  void  loadClass(SteveClassLoader loader)
             throws  ClassNotFoundException, InstantiationException,
             IllegalAccessException {
         Class c = loader.loadClass( "com.stevex.app.classloader.BMW" );      
         System.out.println( "Loaded by :"  + c.getClassLoader());
         
         Car car = (Car) c.newInstance();
         car.run();
         
         BMW bmw = (BMW) c.newInstance();
         bmw.run();     
     }
     
     private  static  void  findClass2(SteveClassLoader loader)
             throws  InstantiationException,
             IllegalAccessException {
         Class c = loader.findClass( "com.stevex.app.classloader.BMW" );
         System.out.println( "Loaded by :"  + c.getClassLoader());
         Car car = (Car) c.newInstance();
         car.run();
         
         BMW bmw = (BMW) c.newInstance();
         bmw.run();
     }
}

你可能感兴趣的:(Java)