SetClassPath
JVM在初始化时,会设置一个环境变量java.class.path,用于记录当前classpath中的所有jar包、配置文件等,且是有顺序的。对应源码在java.base/share/native/libjli/java.c文件中,对应的方法为SetClassPath,如下所示:
static void
SetClassPath(const char *s)
{
char *def;
const char *orig = s;
static const char format[] = "-Djava.class.path=%s";
/*
* usually we should not get a null pointer, but there are cases where
* we might just get one, in which case we simply ignore it, and let the
* caller deal with it
*/
if (s == NULL)
return;
s = JLI_WildcardExpandClasspath(s);
if (sizeof(format) - 2 + JLI_StrLen(s) < JLI_StrLen(s))
// s is became corrupted after expanding wildcards
return;
size_t defSize = sizeof(format)
- 2 /* strlen("%s") */
+ JLI_StrLen(s);
def = JLI_MemAlloc(defSize);
snprintf(def, defSize, format, s);
AddOption(def, NULL);
if (s != orig)
JLI_MemFree((char *) s);
_have_classpath = JNI_TRUE;
}
JLI_WildcardExpandClasspath
JLI_WildcardExpandClasspath 是 Java 启动器中用于处理类路径通配符(*)的核心函数,定义在java.base/share/native/libjli/wildcard.c文件中。源码如下:
const char *
JLI_WildcardExpandClasspath(const char *classpath)
{
const char *expanded;
JLI_List fl;
if (JLI_StrChr(classpath, '*') == NULL)
return classpath;
fl = JLI_List_split(classpath, PATH_SEPARATOR);
expanded = FileList_expandWildcards(fl) ?
JLI_List_join(fl, PATH_SEPARATOR) : classpath;
JLI_List_free(fl);
if (getenv(JLDEBUG_ENV_ENTRY) != 0)
printf("Expanded wildcards:\n"
" before: \"%s\"\n"
" after : \"%s\"\n",
classpath, expanded);
return expanded;
}
主要功能有:
(1)展开类路径中的通配符*,将其替换为匹配的JAR文件列表
(2)只匹配以.jar或.JAR结尾的文件
(3)不递归处理子目录
(4)文件匹配顺序不保证特定顺序
FileList_expandWildcards
FileList_expandWildcards 是 Java 启动器中用于展开类路径通配符的核心函数,定义在java.base/share/native/libjli/wildcard.c文件中。源码如下:
static int
FileList_expandWildcards(JLI_List fl)
{
size_t i, j;
int expandedCnt = 0;
for (i = 0; i < fl->size; i++) {
if (isWildcard(fl->elements[i])) {
JLI_List expanded = wildcardFileList(fl->elements[i]);
if (expanded != NULL && expanded->size > 0) {
expandedCnt++;
JLI_MemFree(fl->elements[i]);
JLI_List_ensureCapacity(fl, fl->size + expanded->size);
for (j = fl->size - 1; j >= i+1; j--)
fl->elements[j+expanded->size-1] = fl->elements[j];
for (j = 0; j < expanded->size; j++)
fl->elements[i+j] = expanded->elements[j];
i += expanded->size - 1;
fl->size += expanded->size - 1;
/* fl expropriates expanded's elements. */
expanded->size = 0;
}
JLI_List_free(expanded);
}
}
return expandedCnt;
}
主要功能是:
(1)遍历字符串列表(JLI_List),查找并展开包含通配符(*)的路径元素
(2)将每个通配符路径替换为匹配的JAR文件列表
(3)返回展开操作的数量
wildcardFileList
wildcardFileList 函数中,文件是通过 WildcardIterator_next 逐个读取并添加到列表中的。源码如下:
static JLI_List
wildcardFileList(const char *wildcard)
{
const char *basename;
JLI_List fl = JLI_List_new(16);
WildcardIterator it = WildcardIterator_for(wildcard);
if (it == NULL)
{
JLI_List_free(fl);
return NULL;
}
while ((basename = WildcardIterator_next(it)) != NULL)
if (isJarFileName(basename))
JLI_List_add(fl, wildcardConcat(wildcard, basename));
WildcardIterator_close(it);
return fl;
}
无论是 Windows 的 FindNextFile 还是 Unix 的 readdir,获取的文件顺序都是依赖于文件系统的实现,没有额外的排序逻辑。
classpath顺序说明
在java.base/share/native/libjli/wildcard.c文件中,有这样一段注释:
* The order in which the jar files in a directory are enumerated in
* the expanded class path is not specified and may vary from platform
* to platform and even from moment to moment on the same machine. A
* well-constructed application should not depend upon any particular
* order. If a specific order is required then the jar files can be
* enumerated explicitly in the class path.
也就是说,如果通过通配符加目录的形式指定classpath,JVM会解析这个目录下的所有jar文件,遍历顺序是不固定的,存在偶然性。
如果一个class文件在a.jar和b.jar中,如果在java.class.path中a.jar
JVM在class加载过程中,从java.class.path的jar包按从前往后的顺序加载,加载成功class后,就停止往后继续遍历。