起因:使用脚本启动,脚本设置了classpath的路径为当前路径。但是从tomcat日志打印来看,扫描的不仅仅是当前路径,而是主目录下所有的文件夹都被扫描。导致项目启动异常缓慢。classpath设置如下:
ini
复制代码
set CLASSPATH=.;
当项目启动,查看APPClassLoader的url。按理来说“.”代表当前目录,也就是脚本执行的目录,是在bin目录下面。但是APPClassLoader的url却是主目录。我百思不得其解,只能翻JDK的源码看看AppClassLoader怎么加载它的URL。那么下面就跟我一起带着问题来看看答案吧。
以JDK 8为例,AppClassLoader的源码位于sun.misc.Launcher#AppClassLoader。
scala
复制代码
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)是把"."转化为路径的关键代码。那么接下看看这个方法干了啥吧。
ini
复制代码
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; }
scss
复制代码
static URL getFileURL(File file) { //在这个方法中路径“.”,发生了变化。 file = file.getCanonicalFile(); return ParseUtil.fileToEncodedURL(file); }
代码一层一层的绕,为了方便阅读,我搞点伪代码。下方是伪代码示例:
arduino
复制代码
public File getCanonicalFile() throws IOException { //好吧url的处理来到getCanonicalPath方法 String canonPath = getCanonicalPath(); return new File(canonPath, fs.prefixLength(canonPath)); }
arduino
复制代码
public String getCanonicalPath() throws IOException { //fs.resolve(this)这是重点关注对象 return fs.canonicalize(fs.resolve(this)); }
现在从fs.resolve(this)看看url的变化
scss
复制代码
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()的实现吧
typescript
复制代码
private String getUserPath() { return normalize(System.getProperty("user.dir")); }
意思就是如果我们的classpath=.;那么如果我们在环境变量设置user.dir的值。classpath的值就变成了user.dir的值。很不巧我们的启动脚本找到关于user.dir的赋值:set -Duser.dir="主目录"。
这就解释了为什么classpath设置了“.”而APPClassLoader的url却变成了主目录。
作者:山城小辣椒
链接:https://juejin.cn/post/7239953755897823289