Java:类加载器(ClassLoader)

听上去很高端,其实一般自定义类加载器不需要用户去实现解析的过程,只要负责实现获取类对应的.class字节流部分就ok了,摘录深入理解Java虚拟机的一段话

虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”

实现类加载器需要继承ClassLoader这个抽象类,用户只要实现其中的findClass方法即可。在该类的javadoc中给出了这样一个示例:

      class NetworkClassLoader extends ClassLoader {
          String host;
          int port;
 
          public Class findClass(String name) {
              byte[] b = loadClassData(name);
              return defineClass(name, b, 0, b.length);
          }
 
          private byte[] loadClassData(String name) {
              // load the class data from the connection
          }
      }

即用户这个自定义的类加载器的类实现文件在网络的某个位置而不是本地的文件。defineClass是ClassLoader类中的一个函数,底层调用了本地方法用来做真正的类解析工作。用户定义的findClass方法会在已有加载器无法加载指定名称的类时被调用。不同的类加载器即使加载(defineClass执行者不同,如果一个自定义的类加载器最后还是调用了系统的类加载器加载那么和直接使用系统加载器加载的效果是一致的)相同的类在JVM看来也属于不一样。具体在loadClass方法中:

    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;
        }
    }

我们继承的ClassLoader中含有一个parent ClassLoader的成员,如果它不为null,那么类先会让它去尝试着加载,当它找不到指定的类时再调用用户定义的findClass过程来加载。

Parents Delegation Model(双亲委派模型)

上述先让类加载器中存在parent类加载器,并且先让parent类加载器尝试类加载的过程涉及到类加载器的“双亲委派模型”。双亲其实就是一个祖先(parents对应的中文而已)即当前ClassLoader中的parent成员,委派就是先让parent加载器代理类加载过程,不行再用自己的类加载过程。按照深入理解JVM的说法有系统提供的三种类加载器它们的层次如下:

|———————————————|

|    Bootstrap ClassLoader       |

|———————————————|

                  |

|----------------------------------|

|   Extension ClassLoder        |

|----------------------------------|

                  |

|----------------------------------|

|   Application ClassLoder      |

|----------------------------------|

 

Bootstrap ClassLoader: 负责加载存放在JAVA_HOME/lib目录中的或者被-Xbootclasspath参数所指定的,并且是按名称被虚拟机识别的(如rt.jar,名字不符不会识别)

Extension ClassLoader: 负责加载JAVA_HOME/lib/ext目录中的或者被java.ext.dirs系统变量指定的路径中的所有类库

Application ClassLoader: 负责加载用户路径(classpath)上所指定的类库,一般情况下这个就是程序中默认的类加载器

 bootstrap classloader不能被直接获取,如下代码将输出null,代表Integer这个类是由bootstrap加载的,也可以从ClassLoader的loadClass判断过程看出如果parent==null就采用bootstrap去加载。

System.out.println(Integer.class.getClassLoader());

application classloader可以通过ClassLoader.getSystemClassLoader()静态方法获取,如下代码将返回true,klass是一个用户定义的类的class属性

System.out.println(ClassLoader.getSystemClassLoader() == klass.getClassLoader());

自己定义的类加载器默认情况下将ApplicationClassLoader作为parent类加载器。另外线程体也可以设置上下文类加载器:

        Thread.currentThread().setContextClassLoader(cl);
        Thread.currentThread().getContextClassLoader();

应用

如果我们需要将jetty嵌入到已有的Java代码中,在初始化Jetty服务器前先实例化一个全局配置类。如果一个类在启动Jetty前的ClassLoader的搜索目录和Jetty的webroot/inf中都存在(但是对应的资源文件可能只有在前者对应的目录中存在),可以设置优先从parentClassLoader中进行载入。jetty除了碰到一些语言级别的类会从parent类加载器获取,其他都先从自己的类加载器获取(否则会使得各个容器下的app可以相互影响)。当使用嵌入式jetty时,一般就运行一个app所以,可以把这个选项打开,如下:

appContext.setParentLoaderPriority(true);

 下面是jetty的WebAppClassLoader继承了URLClassLoader:

    @Override
    protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
    {
        Class<?> c= findLoadedClass(name);
        ClassNotFoundException ex= null;
        boolean tried_parent= false;
        
        boolean system_class=_context.isSystemClass(name);
        boolean server_class=_context.isServerClass(name);
        
        if (system_class && server_class)
        {
            return null;
        }
        
        if (c == null && _parent!=null && (_context.isParentLoaderPriority() || system_class) && !server_class)
        {
            tried_parent= true;
            try
            {
                c= _parent.loadClass(name);
                if (LOG.isDebugEnabled())
                    LOG.debug("loaded " + c);
            }
            catch (ClassNotFoundException e)
            {
                ex= e;
            }
        }

        if (c == null)
        {
            try
            {
                c= this.findClass(name);
            }
            catch (ClassNotFoundException e)
            {
                ex= e;
            }
        }

        if (c == null && _parent!=null && !tried_parent && !server_class )
            c= _parent.loadClass(name);

        if (c == null)
            throw ex;

        if (resolve)
            resolveClass(c);

        if (LOG.isDebugEnabled())
            LOG.debug("loaded " + c+ " from "+c.getClassLoader());
        
        return c;
    }

其中的serverClass和systemClass见WebAppContext:

    public final static String[] __dftSystemClasses =
    {
        "java.",                            // Java SE classes (per servlet spec v2.5 / SRV.9.7.2)
        "javax.",                           // Java SE classes (per servlet spec v2.5 / SRV.9.7.2)
        "org.xml.",                         // needed by javax.xml
        "org.w3c.",                         // needed by javax.xml
        "org.apache.commons.logging.",      // TODO: review if special case still needed
        "org.eclipse.jetty.continuation.",  // webapp cannot change continuation classes
        "org.eclipse.jetty.jndi.",          // webapp cannot change naming classes
        "org.eclipse.jetty.plus.jaas.",     // webapp cannot change jaas classes
        "org.eclipse.jetty.websocket.WebSocket", // WebSocket is a jetty extension
        "org.eclipse.jetty.websocket.WebSocketFactory", // WebSocket is a jetty extension
        "org.eclipse.jetty.websocket.WebSocketServlet", // webapp cannot change WebSocketServlet
        "org.eclipse.jetty.servlet.DefaultServlet" // webapp cannot change default servlets
    } ;

    // Server classes are classes that are hidden from being
    // loaded by the web application using system classloader,
    // so if web application needs to load any of such classes,
    // it has to include them in its distribution.
    public final static String[] __dftServerClasses =
    {
        "-org.eclipse.jetty.continuation.", // don't hide continuation classes
        "-org.eclipse.jetty.jndi.",         // don't hide naming classes
        "-org.eclipse.jetty.plus.jaas.",    // don't hide jaas classes
        "-org.eclipse.jetty.websocket.WebSocket", // WebSocket is a jetty extension
        "-org.eclipse.jetty.websocket.WebSocketFactory", // WebSocket is a jetty extension
        "-org.eclipse.jetty.websocket.WebSocketServlet", // don't hide WebSocketServlet
        "-org.eclipse.jetty.servlet.DefaultServlet", // don't hide default servlet
        "-org.eclipse.jetty.servlet.listener.", // don't hide useful listeners
        "org.eclipse.jetty."                // hide other jetty classes
    } ;

 不懂啊!待续

posted @ 2015-03-08 20:25  卖程序的小歪  阅读(322)  评论(0编辑  收藏  举报