应用getResourceAsStream()获取为null的问题。

5 篇文章 0 订阅
3 篇文章 0 订阅

笔者最近在一次上线过程中,遇到服务器迁移部署失败的问题,根据定位是jar中A.class.getClassloader().getResourceAsStream("/request.xml")。一开始笔者没把这个当回事,觉得是正常的。

先说解决方案:1.升级tomcat8以上版本2.修改应用代码A.class.getClassloader().getResourceAsStream("/request.xml");中request.xml去掉/。

至于原因,下面我们来具体谈谈:

在应用中该request.xml在jar中的根目录下,而类也在该jar中,纵所周知tomcat的应用是有WebappClassLoaderBase去加载,于是去复现该问题时相对虚拟路径在tomcat8之前都是null值,导致了应用的资源获取失败。

至于为什么tomcat8前是null值,我们来对比下tomcat8前的实现,以tomcat7.0.65为例。相较于tomcat  A.class.getClassloader()获取的当前classloader为WebappClassLoaderBase,而对于为什么是WebappClassLoaderBase加载的后续在整关于tomcat的classloader树结构来表述。我们直接看tomcat7.0.65的代码:

 根据代码:配置delegate为true时才会执行(1)方法,用于将委托给父类去加载资源。delegate默认值为false,即子优先,纵使配置了delegate为true,他能加载到jar中的资源文件,parent.getResourceAsStream(name)也是调用jdk原生的ClassLoader,原生的ClassLoader.class实现参考本篇博客最下面的代码原理一样。那么显然执行的是(2)中的findResource(name);由于该xml是在jar中,在所以调用了 entry = findResourceInternal(name, name, false);最后调试可见 JarEntry jarEntry = jarFiles[i].getJarEntry(name);而问题就是出现在这里jdk的getJarEntry(name);该方法不支持/request.xml资源获取。因此返回了null,如果是request.xml则可以获取jar中对应的资源。因此问题查明。建议升级tomcat至8以上版本,或者getResourceAsStream("request.xml")不要带有/即可。
 

Tomcat 7.0.65 WebappClassLoader.class  
   public InputStream getResourceAsStream(String name) {
      
        if (log.isDebugEnabled())
            log.debug("getResourceAsStream(" + name + ")");
        InputStream stream = null;

        // (0) Check for a cached copy of this resource
        stream = findLoadedResource(name);
        if (stream != null) {
            if (log.isDebugEnabled())
                log.debug("  --> Returning stream from cache");
            return (stream);
        }

        // (1) Delegate to parent if requested 
        if (delegate) {
            if (log.isDebugEnabled())
                log.debug("  Delegating to parent classloader " + parent);
            stream = parent.getResourceAsStream(name);
            if (stream != null) {
                // FIXME - cache???
                if (log.isDebugEnabled())
                    log.debug("  --> Returning stream from parent");
                return (stream);
            }
        }

        // (2) Search local repositories
        if (log.isDebugEnabled())
            log.debug("  Searching local repositories");
        URL url = findResource(name);
        if (url != null) {
            // FIXME - cache???
            if (log.isDebugEnabled())
                log.debug("  --> Returning stream from local");
            stream = findLoadedResource(name);
            try {
                if (hasExternalRepositories && (stream == null))
                    stream = url.openStream();
            } catch (IOException e) {
                // Ignore
            }
            if (stream != null)
                return (stream);
        }


    public URL findResource(final String name) {

        if (log.isDebugEnabled())
            log.debug("    findResource(" + name + ")");

        URL url = null;

        if (hasExternalRepositories && searchExternalFirst)
            url = super.findResource(name);

        if (url == null) {
            ResourceEntry entry = resourceEntries.get(name);
            if (entry == null) {
                if (securityManager != null) {
                    PrivilegedAction<ResourceEntry> dp =
                        new PrivilegedFindResourceByName(name, name, false);
                    entry = AccessController.doPrivileged(dp);
                } else {
                    entry = findResourceInternal(name, name, false);
                }
            }
            if (entry != null) {
                url = entry.source;
            }
        }

        if ((url == null) && hasExternalRepositories && !searchExternalFirst)
            url = super.findResource(name);

        if (log.isDebugEnabled()) {
            if (url != null)
                log.debug("    --> Returning '" + url.toString() + "'");
            else
                log.debug("    --> Resource not found, returning null");
        }
        return (url);

    }

 

至于tomcat8后为什么jar中A.class.getClassloader().getResourceAsStream("/request.xml");同理我们来查看tomcat8之后的webappClassLoaderBase.class的实现。主要是tomcat8增加了WebResource的接口,判断和实现机制发生了改变,因此可以正常获取到输入流。

Tomcat 8.0.50 WebappClassLoaderBase.class    
    // (2) Search local repositories
        if (log.isDebugEnabled())
            log.debug("  Searching local repositories");
        String path = nameToPath(name);
        WebResource resource = resources.getClassLoaderResource(path);
        if (resource.exists()) {
            stream = resource.getInputStream();
            trackLastModified(path, resource);
        }

Tomcat 8.0.50 StandardRoot.class
    @Override
    public WebResource getClassLoaderResource(String path) {
        return getResource("/WEB-INF/classes" + path, true, true);
    }

    protected WebResource getResource(String path, boolean validate,
            boolean useClassLoaderResources) {
        if (validate) {
            path = validate(path);
        }

        if (isCachingAllowed()) {
            return cache.getResource(path, useClassLoaderResources);
        } else {
            return getResourceInternal(path, useClassLoaderResources);
        }
    }

应用部署在服务器上,一般服务器的自己的classloader都会重写getResourceAsStream()方法,前面我们也讲到了要想直接调用jdk的classloader,在tomcat中配置delegate为true即可具体配置在tomcat的context.xml中配置<Loader delegate="true" />即可,即向上委托,所有的类优先交由上层的classloader去加载。

那么这个案例直接在idea中写测试demo的调用jdk自身的classloader会咋样呢,调用JDK自己的classloader也可发现差异。

InputStream classInputStream = HashMapDemo.class.getResourceAsStream("/request.xml");  //能获取到资源

InputStream classloaderInputStream = HashMapDemo.class.getClassLoader().getResourceAsStream("/request.xml"); //null

原生JDK的ClassLoader.class 和Class.class的实现

对比下面2块可以清晰的看出问题所在,在Class的getResourceAsStream方法中,调用的resolveName(name)的方法,对name以/开头的相对虚拟路径进行了截取操作,得到的name就变成了request.xml,其实后面Class.class的执行逻辑就和Classloader.class一致了,直接看下面的代码可知,调用了ClassLoader的getResourceAsStream(name)方法。

InputStream classInputStream = HashMapDemo.class.getResourceAsStream("/request.xml");
JDK1.8.0_121 Class.class源码
   public InputStream getResourceAsStream(String name) {
        name = resolveName(name);
        ClassLoader cl = getClassLoader0();
        if (cl==null) {
            // A system class.
            return ClassLoader.getSystemResourceAsStream(name);
        }
        return cl.getResourceAsStream(name);
    }
  

    private String resolveName(String name) {
        if (name == null) {
            return name;
        }
        if (!name.startsWith("/")) {
            Class<?> c = this;
            while (c.isArray()) {
                c = c.getComponentType();
            }
            String baseName = c.getName();
            int index = baseName.lastIndexOf('.');
            if (index != -1) {
                name = baseName.substring(0, index).replace('.', '/')
                    +"/"+name;
            }
        } else {
            name = name.substring(1);
        }
        return name;
    }
InputStream classloaderInputStream = HashMapDemo.class.getClassLoader().getResourceAsStream("/request.xml");
JDK1.8.0_121 ClassLoader.class   
 public InputStream getResourceAsStream(String name) {
        URL url = getResource(name);
        try {
            return url != null ? url.openStream() : null;
        } catch (IOException e) {
            return null;
        }
    }

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值