原来user.dir竟然会影响classpath的值

864 阅读2分钟

起因:使用脚本启动,脚本设置了classpath的路径为当前路径。但是从tomcat日志打印来看,扫描的不仅仅是当前路径,而是主目录下所有的文件夹都被扫描。导致项目启动异常缓慢。classpath设置如下:

set CLASSPATH=.;

当项目启动,查看APPClassLoader的url。按理来说“.”代表当前目录,也就是脚本执行的目录,是在bin目录下面。但是APPClassLoader的url却是主目录。我百思不得其解,只能翻JDK的源码看看AppClassLoader怎么加载它的URL。那么下面就跟我一起带着问题来看看答案吧。

以JDK 8为例,AppClassLoader的源码位于sun.misc.Launcher#AppClassLoader。

static class AppClassLoader extends URLClassLoader {

    static {
        ClassLoader.registerAsParallelCapable();
    }

    public static ClassLoader getAppClassLoader(final ClassLoader extcl)
        throws IOException
    {
        //在脚本中设置的值,在这个方法中可以拿到。此时s=".";
        final String s = System.getProperty("java.class.path");
        final File[] path = (s == null) ? new File[0] : getClassPath(s);

       
        return AccessController.doPrivileged(
            new PrivilegedAction<AppClassLoader>() {
                public AppClassLoader run() {
                //设置url的关键代码,也就是此时的重点关注对象
                URL[] urls =
                    (s == null) ? new URL[0] : pathToURLs(path);
                return new AppClassLoader(urls, extcl);
            }
        });
    }

从上方的代码可以看到pathToURLs(path)是把"."转化为路径的关键代码。那么接下看看这个方法干了啥吧。

private static URL[] pathToURLs(File[] path) {
    URL[] urls = new URL[path.length];
    for (int i = 0; i < path.length; i++) {
       //好吧,关键代码又来到了getFileURL这个方法
        urls[i] = getFileURL(path[i]);
    }
    return urls;
}
static URL getFileURL(File file) {
        //在这个方法中路径“.”,发生了变化。
        file = file.getCanonicalFile();
       return ParseUtil.fileToEncodedURL(file);
}

代码一层一层的绕,为了方便阅读,我搞点伪代码。下方是伪代码示例:

public File getCanonicalFile() throws IOException {
      //好吧url的处理来到getCanonicalPath方法
    String canonPath = getCanonicalPath();
    return new File(canonPath, fs.prefixLength(canonPath));
}
public String getCanonicalPath() throws IOException {
      //fs.resolve(this)这是重点关注对象
    return fs.canonicalize(fs.resolve(this));
}

现在从fs.resolve(this)看看url的变化

public String resolve(File f) {
    //此时这个path的值是“.”
    String path = f.getPath();
    //pl的结果是0,那就跳到对应的if去吧
    int pl = f.getPrefixLength();
    if ((pl == 2) && (path.charAt(0) == slash))
        return path;                        /* UNC */
    if (pl == 3)
        return path;                        /* Absolute local */
        //转化为路径的关键代码出现了,getUserPath()
    if (pl == 0)
        return getUserPath() + slashify(path); /* Completely relative */
}

来看看上方getUserPath()的实现吧

private String getUserPath() {
    return normalize(System.getProperty("user.dir"));
}

意思就是如果我们的classpath=.;那么如果我们在环境变量设置user.dir的值。classpath的值就变成了user.dir的值。很不巧我们的启动脚本找到关于user.dir的赋值:set -Duser.dir="主目录"。这就解释了为什么classpath设置了“.”而APPClassLoader的url却变成了主目录。