起因:使用脚本启动,脚本设置了 classpath 的路径为当前路径。但是从 tomcat 日志打印来看,扫描的不仅仅是当前路径,而是主目录下所有的文件夹都被扫描。导致项目启动异常缓慢。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 却变成了主目录。
评论