A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

自定义类加载器

1。浅析类加载器工作机制
    ClassLoader顾名思义就是类加载器,负责将Class字节码文件加载到JVM中。
2。如何加载class文件
    ClassLoader加载一个class文件到JVM时需要经过如下步骤:


  • 第一阶段是找到.class文件并把这个文件包含的字节码加载到内存中;
  • 第二阶段又可以分为三个步骤,分别是字节码验证、Class类数据结构分析以及相应的内存分配和最后的符号表的链接;
  • 第三阶段是类中静态属性和初始化赋值,以及静态块的执行等。

3,加载字节码到内存
    通常情况下,ClassLoader加载类时,会首先调用findClass方法Class<?> findClass(String name),找到我们的类的字节码文件,让后将这个文件包含的字节码加载到内存。但是在我们的ClassLoader抽象类中并没有定义如何去加载,即如何去找到指定的类并且把它的字节码加载到内存中。这个在它的子类URLClassLoader中重写了findClass()方法,在URLClassLoader中通过一个URLClassPath类帮助我们得到需要加载的class文件字节流,而这个URLClassPath定义了到哪里去找这个class文件,如果找到了这个class文件,在读取它的byte字节流,通过调用defineClass()方法来创建Class类对象。

我们在看看URLClassLoader类的构造函数,必须要传入一个URLClassPath对象,也就是必须要指定这个ClassLoader默认到哪个目录下去查找class文件。从URLClassPath的名字中可以发现,它是通过URL的形式来表示ClassPath路径的,也就是我们加载类的路径。

那么如何设置不同ClassLoader 的类加载路径的呢?如下图所示是BootStrapClassLoader、ExtClassLoader、AppClassLoader通过参数的形式指定类加载路径的:

<table>
  <tr>
    <td rowspan=3>BootStrapClassLoader</td>
    <td bgcolor="#eeeeee"> -Xbootclasspath/p: </td>
    <td bgcolor="#eeeeee">向前追加BootStrap ClassLoader的搜索路径</td>
  </tr>
  <tr>
    <td bgcolor="#eeeeee"> -Xbootclasspath: </td>
    <td bgcolor="#eeeeee">设置BootStrap ClassLoader的搜索路径</td>
  <tr>
    <td bgcolor="#eeeeee"> -Xbootclasspath/a:</td>
    <td bgcolor="#eeeeee">向后追加BootStrap ClassLoader的搜索路径</td>
  </tr>
<tr>
    <td > ExtClassLoader </td>
    <td bgcolor="#eeeeee"> -Djava.ext.dirs= </td>
    <td bgcolor="#eeeeee">设置ExtClassLoader的搜索路径</td>
  </tr>
<tr>
    <td >AppClassLoader</td>
    <td bgcolor="#eeeeee"> -Djava.class.path=
    </td>
    <td bgcolor="#eeeeee">设置AppClassLoader的搜索路径</td>
  </tr>
</table>

4,验证与解析,
  • 字节码验证,类加载器需要对类的字节码进行需要检测,以确保格式正确、行为正确。
  • 类准备,这个阶段用于准备类中定义的字段、方法和实现的接口所必须的数据结构。
  • 解析,在这个阶段类加载器,加载类所引用(import)的其他所有类,可以用许多方式来引用类,如extends、implements、定义属性、方法类型、方法中使用的变量类型等等。


5,初始化Class对象,
    类中包含的静态代码块都会被执行,在这个阶段的末尾静态字段会被初始化成默认值。


6,ClassLoader类结构分析,


我们经常会用到或扩展ClassLoader,主要会用到如下几个方法,以及他们的重载方法:

**Class<?> defineClass(String name, byte[] b, int off, int len)**

用于将byte字节流解析成JVM能够识别的Class对象,有了这个方法意味着我们不仅仅能够通过class文件实例化对象,还可以通过其他方式如我们通过网络传输一个类的字节码流,拿到这个字节流我们可以创建类的Class对象,然后通过Class对象实例化类对象。

**Class<?> findClass(String name)**

defineClass通常会和findClass一起使用,我们通常会直接覆盖ClassLoader父类的findClass方法来实现类的加载规则,从而取得要加载类的字节码。然后调用defineClass来生成类的Class对象。

**resolveClass(Class<?> c)**

如果你想要类被加载到JVM中时就被链接(Linking上文有描述),那么可以主动的调用resolveClass方法。当然我们也可以选择让JVM来决定何时链接这个类。

**Class<?> loadClass(String name)**

如果你不想重新定义类的加载规则,也没有复杂的处理逻辑,只想在运行时加载自己指定的一个类而已,那么我们此时就可以直接调用ClassLoader的loadClass方法以获取类的Class对象。这个loadClass也有重载方法,我们同样可以决定在什么时候解析这个类。

ClassLoader是一个抽象类,我们想要定义自己的ClassLoader,一般不会继承ClassLoader,而是继承URLClassLoader,因为这个类已经帮助我们实现了大部分工作。

7,ClassLoader的等级加载机制,
JVM在加载类时默认采用的是双亲委派(或全盘委托)机制。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

整个JVM中提供了三个ClassLoader类,这个三个ClassLoader可以分为两种类型:BootStrapClassLoader属于一类,其他的属于一类。

**BootStrap ClassLoader**

它主要加载JVM自身工作需要的类,这个ClassLoader完全是自己控制的,需要加载哪个类,怎么加载都由JVM自己控制,别人也访问不到这个类,所以这个ClassLoader是不遵循上文说的加载规则的,它仅仅就是一个类加载器而已,即没有父加载器,也没有子加载器。

**ExtClassLoader**

这个类加载器有点特殊,它是JVM自身的一部分,但是它的血统不是很纯正,它并不是JVM亲自实现的。它加载的特定类在System.getProperties(“java.ext.dirs”)目录下。

**AppClassLoader**

这个类加载器就是用来专门加载应用类的,它的父类是ExtClassLoader。所有在System.getProperties(“java.class.path”)目录下的所有的类都由它来加载,这个目录就是我们常用的classpath。

注意我们如果要实现自定义的类加载器,不管你是直接继承ClassLoader,还是继承URLClassLoader类,或者其他子类,它的父加载器都是AppClassLoader,因为不管调用哪个父构造器,创建的对象都必须最终调用getSystemClassLoader()来作为我们的父类加载器,而该方法获得的恰好就是AppClassLoader。

注意:很多文章在介绍ClassLoader的时候,将BootStrapClassLoader列为我们ExtClassLoader的父加载器,其实BootStrapClassLoader并不属于JVM的类加载器等级层次,因为它并没有遵守类加载规则,另外它也没有子加载器。

8,自定义类加载器
理解了我们的类加载机制后,接下来我们开发一个“类加载器”来加载我们制定包下的所有类,比如使用了某些注解的类,或者实现了某些接口的类,在或者继承了某父类的所有类等。
## 编写ClassUtil工具类 ##
该工具类提供与类操作相关的方法,比如获取类加载器、加载类、获取制定包下的所有类等,代码如下:
        
            /**
             * 类加载器工具类
             * @author think
             *
             */
            public class ClassUtil {
            private static final Logger LOGGER = LoggerFactory.getLogger(ClassUtils.class);
            
            /**
             * 获取类加载器
             * @return
             */
            public static ClassLoader getClassLoader(){
                。。。。。。
            }
            
            /**
             * 加载指定的类
             * @param className
             * @param isInitialized
             * @return
             */
            public static Class<?> loadClass(String className,boolean isInitialized){
                。。。。。。
            }
            
            /**
             * 获取指定包下的所有的类
             * @param packageName
             * @return
             */
            public static Set<Class<?>> getClassSet(String packageName){
                。。。。。。
            }

## 获取类加载器 ##
获取类加载器实现起来最为简单,只需要获取当前线程中的ClassLoader即可,该方法具体实现如下:

        /**
         * 获取类加载器
         * @return
         */
        public static ClassLoader getClassLoader(){
            return Thread.currentThread().getContextClassLoader();
        }

## 加载指定类 ##
加载类需要提供类名与是否初始化的标志,这里提供的初始化是指是否执行类的静态代码块,该方法具体实现如下:

        /**
         * 加载指定的类
         * @param className
         * @param isInitialized
         * @return
         */
        public static Class<?> loadClass(String className,boolean isInitialized){
            Class<?> cls = null;
            try {
                cls = Class.forName(className,isInitialized,getClassLoader());
            } catch (ClassNotFoundException e) {
                LOGGER.error("load class failure!",e);
                e.printStackTrace();
            }
            return cls;
        }
为了提高类的加载性能,可将loadClass方法isInitialized参数设置为false。

## 获取制定包名下所有类 ##
最为复杂的是获取制定包名下的所有类,我们需要根据包名并将其转换为文件路径,读取class文件或jar包,获取指定的类名去加载类,该方法的具体实现如下:

        /**
         * 获取指定包下的所有的类
         * @param packageName
         * @return
         */
        public static Set<Class<?>> getClassSet(String packageName){
            Set<Class<?>> classSet = new HashSet<Class<?>>();
            try {
                Enumeration<URL> urls = getClassLoader().getResources(packageName.replace(".", "/"));
                if(null != urls){
                    while(urls.hasMoreElements()){
                        URL url = urls.nextElement();
                        if(null != url){
                            String protocol = url.getProtocol();
                            if("file".equals(protocol)){
                                String packagePath = url.getPath().replaceAll("%20", " ");
                                addClass(classSet,packagePath,packageName);
                            }else if("jar".equals(protocol)){
                                JarURLConnection urlConnection = (JarURLConnection) url.openConnection();
                                if(null != urlConnection){
                                    JarFile jarFile = urlConnection.getJarFile();
                                    if(null != jarFile){
                                        Enumeration<JarEntry> entries = jarFile.entries();
                                        while(entries.hasMoreElements()){
                                            JarEntry jarEntry = entries.nextElement();
                                            String jarName = jarEntry.getName();
                                            if(jarName.endsWith(".class")){
                                                String className = jarName.substring(0, jarName.lastIndexOf(".")).replaceAll("/", ".");
                                                doAddClass(classSet,className);
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
               
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            return classSet;
        }

        /**
         * 加载给的类全路径的类
         * @param sets
         * @param className
         * @throws ClassNotFoundException
         */
        private static void doAddClass(Set<Class<?>> sets,String className) throws ClassNotFoundException{
            Class<?> cls = Class.forName(className);
            sets.add(cls);
        }

        /**
         * 加载指定包下的类
         * @param sets
         * @param packagePath
         * @param packageName
         * @throws ClassNotFoundException
         */
        private static void addClass(Set<Class<?>> sets,String packagePath,String packageName) throws ClassNotFoundException{
            File[] files = new File(packagePath).listFiles(new FileFilter(){
                public boolean accept(File pathname) {
                    return pathname.isFile()&&pathname.getName().endsWith(".class")||pathname.isDirectory();
                }});
            if(files != null && files.length > 0){
                for(File file:files){
                    String fileName = file.getName();
                    if(file.isFile()){
                        String className = fileName.substring(0, fileName.lastIndexOf("."));
                        if(!StringUtil.isEmpty(className)){
                            className = packageName+"."+className;
                            doAddClass(sets,className);
                        }
                    }else{
                        String subPackagePath = fileName;
                        if(!StringUtil.isEmpty(subPackagePath)){
                            subPackagePath=packagePath+"/"+subPackagePath;
                        }
                        String subPackageName = fileName;
                        if(!StringUtil.isEmpty(subPackageName)){
                            subPackageName = packageName+"."+subPackageName;
                        }
                        addClass(sets,subPackagePath,subPackageName);
                    }
                }
            }
        }


1 个回复

倒序浏览
我来占层楼啊   
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马