血泪的 Jetty ClassLoader

最近在写ILogcms的启动器的使用,主要是嫌Tomcat 复杂,当然主要是配置了,现在启动我也优化了一把Tomcat 已经比较简单了,如下结构:

本来以为比较简单了,但是最近冒了个想法,就把他做成一个进程类,以后就只需要启动一个进程就OK了,没有必要配置一堆的xml,如server.xml 等等。简化处理。正好遇到一些问题,我就处理起来如classloader ,我也简单的说下;

什么是ClassLoader

大家都知道,当我们写好一个Java程序之后,不是管是CS还是BS应用,都是由若干个.class文件组织而成的一个完整的Java应用程序,当程序在运行时,即会调用该程序的一个入口函数来调用系统的相关功能,而这些功能都被封装在不同的class文件当中,所以经常要从这个class文件中要调用另外一个class文件中的方法,如果另外一个文件不存在的,则会引发系统异常。而程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,通过Java的类加载机制(ClassLoader)来动态加载某个class文件到内存当中的,从而只有class文件被载入到了内存之后,才能被其它class所引用。所以ClassLoader就是用来动态加载class文件到内存当中用的。

常见 Classloader

  • 启动类加载器(Bootstrap ClassLoader):主要负责加载\lib目录中或者-Xbootclasspath中指定的,并且被虚拟机识别的类库加载到VM中。这个加载器是JVM自身的一部分,用本地代码实现的(openjdk中源码位于hotspot/src/share/vm/classfile/classLoader.cpp中),无法直接被java代码引用。
  • 扩展类加载器(Extension ClassLoader):主要负责加载jdk扩展类库\lib\ext或者java.ext.dirs系统属性指定的目录中jar文件,由sun.misc.Launcher$ExtClassLoader实现
  • 系统类加载器(System ClassLoader):用于加载CLASSPATH中指定的类,由sun.misc.Launcher$AppClassLoader实现。该类即ClassLoader.getSystemLoader()的返回值,是应用程序默认的类加载器。
  • 自定义加载器(User ClassLoader):用户可以自定义自己的类加载器
          Bootstrap ClassLoader
                   |
          Extension ClassLoader
                   |
          System ClassLoader
              /           \
      User1 ClassLoader   User2 ClassLoader

Jetty中的ClassLoader

jetty,tomcat等web容器通常都会对classloader做扩展,因为一个正常的容器至少要保证其内部运行的多个webapp之间:私有的类库不受影响,并且公有的类库可以共享。这正好发挥classloader的层级划分优势。 jetty中有一个org.eclipse.jetty.webapp.WebAppClassLoader,负责加载一个webapp context中的应用类,WebAppClassLoader以系统类加载器作为parent,用于加载系统类。不过servlet规范使得web容器的classloader比正常的classloader委托模型稍稍复杂,servlet规范要求:

WEB-INF/lib 和 WEB-INF/classes优先于父容器中的类加载,比如WEB-INF/classes下有个XYZ类,CLASSPATH下也有个XYZ类,jetty中优先加载的是WEB-INF/classes下的,这与正常的父加载器优先相反。
系统类比如java.lang.String不遵循第一条, WEB-INF/classes或WEB-INF/lib下的类不能替换系统类。不过规范中没有明确规定哪些是系统类,jetty中的实现是按照类的全路径名判断。
Server的实现类不被应用中的类引用,即Server的实现类不能被人和应用类加载器加载。不过,同样的,规范里没有明确规定哪些是Server的实现类,jetty中同样是按照类的全路径名判断。
为了处理上述三个问题,jetty的应用类加载器(
org.eclipse.jetty.webapp.WebAppClassLoader
)做了些特殊处理。

WebAppClassLoader

Constructor Method

看看构造函数,主要申明父Class loader ,以及某些指派加载器,涉及模式,双亲委派。

/* ------------------------------------------------------------ */  
/** Constructor. 
 */  
//这里可以看出,如果在构建的时候没有提供父亲classLoader,那么将会默认将当前的线程classLoader作为当前的父classLoader  
   public WebAppClassLoader(ClassLoader parent, Context context)
        throws IOException
    {
        super(new URL[]{},parent!=null?parent
                :(Thread.currentThread().getContextClassLoader()!=null?Thread.currentThread().getContextClassLoader()
                        :(WebAppClassLoader.class.getClassLoader()!=null?WebAppClassLoader.class.getClassLoader()
                                :ClassLoader.getSystemClassLoader())));
        _parent=getParent();
        _context=context;
        if (_parent==null)
            throw new IllegalArgumentException("no parent classloader!");

        _extensions.add(".jar");
        _extensions.add(".zip");

        // TODO remove this system property
        String extensions = System.getProperty(WebAppClassLoader.class.getName() + ".extensions");
        if(extensions!=null)
        {
            StringTokenizer tokenizer = new StringTokenizer(extensions, ",;");
            while(tokenizer.hasMoreTokens())
                _extensions.add(tokenizer.nextToken().trim());
        }

        if (context.getExtraClasspath()!=null)
            addClassPath(context.getExtraClasspath());
    }

LoadClass Method;

Loadclass按照:

  • findLoadedClass(name)-检查类是否已经加载
  • 判断该类是否为系统类或server类
  • 如果该类未加载且父加载器不为空且设置了父加载器优先或类类为系统类,且该类不是server类,则尝试使用父加载器加载该类
  • 如果不是父加载器优先或者父加载器未加载到该类,使用WebAppClassLoader加载该类
  • 如果是不是父加载器优先,并且WebAppClassLoader未加载到该类,尝试使用父加载器加载该类
  • 找到则返回,否则抛出ClassNotFoundException
/* ------------------------------------------------------------ */
    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name))
        {            
            ClassNotFoundException ex= null;
            Class<?> parent_class = null;
            Class<?> webapp_class = null;

            // Has this loader loaded the class already?
            webapp_class = findLoadedClass(name);
            if (webapp_class != null)
            {
                if (LOG.isDebugEnabled())
                    LOG.debug("found webapp loaded {}",webapp_class);
                return webapp_class;
            }
            ///如果父类加载器优先,或者这里加载的类型是系统类型,例如"java.","javax.servlet.","javax.xml.",那么由父类加载器来加载  
    //不过webapp的代码父类加载器肯定是加载不倒的  
            // Should we try the parent loader first?
            if (_context.isParentLoaderPriority())
            {
                // Try the parent loader
                try
                {
                    parent_class = _parent.loadClass(name); ////由父类加载,一些系统代码,例如hashMap,set啥的都在这里加载,还有jetty定义的也在这里加载,例如NoJspServlet.class

                    // If the webapp is allowed to see this class
                    if (Boolean.TRUE.equals(__loadServerClasses.get()) || !_context.isServerClass(parent_class))
                    {
                        if (LOG.isDebugEnabled())
                            LOG.debug("PLP parent loaded {}",parent_class);
                        return parent_class;
                    }
                }
                catch (ClassNotFoundException e)
                {
                    // Save it for later
                    ex = e;
                }

                // Try the webapp loader
                try
                {
                    // If found here then OK to use regardless of system or server classes
                    // If it is a system class, we've already tried to load from parent, so
                    // would have returned it.
                    // If it is a server class, doesn't matter as we have loaded it from the 
                    // webapp
                    webapp_class = this.findClass(name);////这里是从当前classLoader的路径下面去加载  
                    resolveClass(webapp_class);
                    if (LOG.isDebugEnabled())
                        LOG.debug("PLP webapp loaded {}",webapp_class);
                    return webapp_class;
                }
                catch (ClassNotFoundException e)
                {
                    if (ex==null)
                        ex = e;
                    else
                        ex.addSuppressed(e);
                }

                throw ex;
            }
            else
            {
                // Not parent loader priority, so...

                // Try the webapp classloader first
                // Look in the webapp classloader as a resource, to avoid 
                // loading a system class.
                String path = name.replace('.', '/').concat(".class");
                URL webapp_url = findResource(path);

                if (webapp_url!=null && !_context.isSystemResource(name,webapp_url))
                {
                    webapp_class = this.foundClass(name,webapp_url);
                    resolveClass(webapp_class);
                    if (LOG.isDebugEnabled())
                        LOG.debug("WAP webapp loaded {}",webapp_class);
                    return webapp_class;
                }

                // Try the parent loader
                try
                {
                    parent_class = _parent.loadClass(name); 

                    // If the webapp is allowed to see this class
                    if (Boolean.TRUE.equals(__loadServerClasses.get()) || !_context.isServerClass(parent_class))
                    {
                        if (LOG.isDebugEnabled())
                            LOG.debug("WAP parent loaded {}",parent_class);
                        return parent_class;
                    }
                }
                catch (ClassNotFoundException e)
                {
                    ex=e;
                }

                // We couldn't find a parent class, so OK to return a webapp one if it exists 
                // and we just couldn't see it before 
                if (webapp_url!=null)
                {
                    webapp_class = this.foundClass(name,webapp_url);
                    resolveClass(webapp_class);
                    if (LOG.isDebugEnabled())
                        LOG.debug("WAP !server webapp loaded {}",webapp_class);
                    return webapp_class;
                }

                throw ex==null?new ClassNotFoundException(name):ex;
            }
        }
    }

isParentLoaderPriority

上述过程涉及一个加载器优先级的概念,这也是针对前述第一条规范中WEB-INF/lib和WEB-INF/classes类优先的处理。jetty中父加载器优先的配置项可以通过环境变量

org.eclipse.jetty.webapp.WebAppContext.setParentLoaderPriority(boolean)方法来设置

context.setParentLoaderPriority(true);

优于该配置默认是false,因此在load class过程中优先使用WebAppClassLoader加载WEB-INF/lib和WEB-INF/classes中的类。 当将该配置项设为true时需要确认类加载顺序没有问题。

简单就聊到这,开始来说说我的悲痛历史;

上两张图,大家是否发现有个问题,就是两Table.class 编号不一样,并且加载器也不一样,所以出现业务混乱的问题。

折腾半天发现:


默认情况下如位配置情况下:setParentLoaderPriority造成;

找了一下午;发现的问题,血泪史啊;

最终启动代码;

       EnumSet all = EnumSet.of(DispatcherType.ASYNC, DispatcherType.ERROR,
                DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.REQUEST);
        final Server server = new Server(888);
        try {
            WebAppContext context = new WebAppContext("src\\main\\webapp", "/");
           /* FilterHolder filter = new FilterHolder(new JFinalFilter());
            filter.setInitParameter("configClass",Config.class.getName());
            context.addFilter(filter, "*//*", all);*/
            context.setParentLoaderPriority(true);
            server.setHandler(context);
            //  context.start();
            server.start();
            server.dumpStdErr();
            server.join();
        } catch (Exception e) {
            e.printStackTrace();
        }

除特别注明外,本站所有文章均为duzhi原创,转载请注明出处来自https://www.duzhi.me/article/5580.html

联系我们

******

在线咨询:点击这里给我发消息

邮件:ashang.peng#aliyun.com

QR code