起因:使用脚本启动,脚本设置了 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 却变成了主目录。
评论