sofastack / sofa-ark

SOFAArk is a light-weight,java based classloader isolation framework.
https://www.sofastack.tech/projects/sofa-boot/sofa-ark-readme/
Apache License 2.0
1.57k stars 500 forks source link

AbstractClasspathClassloader. getResource()会导致NPE #140

Closed jiangyunpeng closed 5 years ago

jiangyunpeng commented 6 years ago

Describe the bug

在sofa-ark 运行中,

XXXClass.getResource("/").getPath()

会出现NPE。

原因在Class的实现中/会被解析成空字符串:

 public java.net.URL getResource(String name) {
        name = resolveName(name);
        ClassLoader cl = getClassLoader0();
        if (cl==null) {
            // A system class.
            return ClassLoader.getSystemResource(name);
        }
        return cl.getResource(name);
    }

AbstractClasspathClassloader. getResource 在方法开头做了检测,当为空字符串直接返回null导致NPE.

    public URL getResource(String name) {
        if (StringUtils.isEmpty(name)) {
            return null;
        }
        Handler.setUseFastConnectionExceptions(true);
        try {
            return getResourceInternal(name);
        } finally {
            Handler.setUseFastConnectionExceptions(false);
        }
    }

Steps to reproduce

测试代码

package me.qlong.tech;

import com.alipay.sofa.ark.support.startup.SofaArkBootstrap;

public class SampleArk {

    public static void main(String[] args) {
        SofaArkBootstrap.launch(args);

        new Account();
        System.out.println("ok");
    }

    private static class Account {

        public Account() {
            System.out.println("load by " + this.getClass().getClassLoader().getClass().getName());
            System.out.println(this.getClass().getResource("/").getPath());
        }
    }
}

输出:

2018-10-11 10:48:38,708 INFO  main                             - Begin to start biz: Startup In IDE
load by com.alipay.sofa.ark.container.service.classloader.BizClassLoader
2018-10-11 10:48:38,725 ERROR main                             - Start biz: Startup In IDE meet error
java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.alipay.sofa.ark.bootstrap.MainMethodRunner.run(MainMethodRunner.java:48)
    at com.alipay.sofa.ark.container.model.BizModel.start(BizModel.java:179)
    at com.alipay.sofa.ark.container.service.biz.DefaultBizDeployer.deploy(DefaultBizDeployer.java:52)
    at com.alipay.sofa.ark.container.service.biz.BizDeployServiceImpl.deploy(BizDeployServiceImpl.java:55)
    at com.alipay.sofa.ark.container.pipeline.DeployBizStage.process(DeployBizStage.java:44)
    at com.alipay.sofa.ark.container.pipeline.StandardPipeline.process(StandardPipeline.java:73)
    at com.alipay.sofa.ark.container.ArkContainer.start(ArkContainer.java:113)
    at com.alipay.sofa.ark.container.ArkContainer.main(ArkContainer.java:74)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.alipay.sofa.ark.bootstrap.MainMethodRunner.run(MainMethodRunner.java:48)
    at com.alipay.sofa.ark.bootstrap.AbstractLauncher.launch(AbstractLauncher.java:108)
    at com.alipay.sofa.ark.bootstrap.AbstractLauncher.launch(AbstractLauncher.java:76)
    at com.alipay.sofa.ark.support.startup.SofaArkBootstrap.remain(SofaArkBootstrap.java:81)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.alipay.sofa.ark.support.thread.LaunchRunner.run(LaunchRunner.java:61)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.NullPointerException
    at me.qlong.tech.SampleArk$Account.<init>(SampleArk.java:25)
    at me.qlong.tech.SampleArk.main(SampleArk.java:17)

Minimal yet complete reproducer code (or GitHub URL to code)

Environment

QilongZhang commented 6 years ago

@jiangyunpeng 感谢反馈. 确认在执行 Class.getResource("/") 返回 NULL. 本质是 BizClassLoader 和 PluginClassLoader 在处理 getResource("") 返回结果和 ClassLoader.getResource("") 不一致. 如果方便,能否 PR 解决下,感谢。

jiangyunpeng commented 6 years ago

另外BizClassLoader和PluginClassLoader的getResourcesInternal是否存在问题? 如果plugin中声明了export resource,是否应该优先使用plugin的,做一下去重。

protected Enumeration<URL> getResourcesInternal(String name) throws IOException {
        List<Enumeration<URL>> enumerationList = new ArrayList<>();

        // 1. find exported resources
        enumerationList.add(getExportResources(name));

        // 2. find local resources
        enumerationList.add(getLocalResources(name));

        return new CompoundEnumeration<>(
            enumerationList.toArray((Enumeration<URL>[]) new Enumeration<?>[0]));

    }
QilongZhang commented 6 years ago

@jiangyunpeng 应该是没有问题,Plugin导出的资源是希望其他 Plugin 和 Biz 能加载到的。

是否应该优先使用plugin的,做一下去重。

去重的意义在哪?有时就是希望获取所有同名资源

jiangyunpeng commented 6 years ago

假设biz被动依赖了一个0.8的dubbo,同时biz也显示依赖了一个1.0的dubbo-plugin,通过export "com.alibaba.dubbo.*"可以让jvm忽略掉0.8的dubbo只加载1.0中的dubbo classes,但是resource现在同时load 了biz和plugin两份资源就会有冲突, 把resource也当成class同等对待就好理解了。

QilongZhang commented 6 years ago

@jiangyunpeng resource 是比较特殊的存在,诸如 dubbo 和 sofa-rpc 提供很多扩展机制。有些扩展提供了默认的扩展,放在SPI文件中,但是允许应用自定义覆盖。这个时候如果按你说的去重,那么用户定义的文件就无效了。

jiangyunpeng commented 6 years ago

不会吧,用户定义的资源的jar路径和dubbo官方不一样,我说的去重是针对同一个jar不同版本中的resource。

QilongZhang commented 6 years ago

@jiangyunpeng 通过getResources 的加载资源,再根据路径判断版本号去重,并不是很好的方式,你可以使用getResource替代,如果你仅仅想加载plugin导出的资源。 通过最后url的路径格式解析版本是一个很hack的方式,也不准。

jiangyunpeng commented 6 years ago

getResource()的调用不是我能控制的,是各种库在用,比如spring 加载META-INF/spring.factories 就是getResources()。

QilongZhang commented 6 years ago

@jiangyunpeng 你引入多个spring 版本,getResources(), 也是不会做去重的。getResources() 是为了获取classpath所有的指定路径的资源,框架内部尝试做去重不认为是合适的。另外正如上一条回复,也没法有一个很好的去重策略,从承担的风险和解决的问题来看,不值得这么做。 或许你可以给出遇到的具体问题。 上面你提到的多版本dubbo资源只是现象,可以具体说下因为此遇到的具体问题。