《jdk8u源码分析》5.SelectVersion

C Language Reference > Parsing C Command-Line Arguments

src/share/bin/java.c::SelectVersion

/*
 * The SelectVersion() routine ensures that an appropriate version of
 * the JRE is running.  The specification for the appropriate version
 * is obtained from either the manifest of a jar file (preferred) or
 * from command line options.
 * The routine also parses splash screen command line options and
 * passes on their values in private environment variables.
 */
static void
SelectVersion(int argc, char **argv, char **main_class)
{
    char    *arg;
    char    **new_argv;
    char    **new_argp;
    char    *operand;
    char    *version = NULL;
    char    *jre = NULL;
    int     jarflag = 0;
    int     headlessflag = 0;
    int     restrict_search = -1;               /* -1 implies not known */
    manifest_info info;
    char    env_entry[MAXNAMELEN + 24] = ENV_ENTRY "=";
    char    *splash_file_name = NULL;
    char    *splash_jar_name = NULL;
    char    *env_in;
    int     res;

    /*
     * If the version has already been selected, set *main_class
     * with the value passed through the environment (if any) and
     * simply return.
     */
    //该环节变量_JAVA_VERSION_SET存储jar中META-INF/MANIFEST.MF文件的Main-Class,避免重复读取
    if ((env_in = getenv(ENV_ENTRY)) != NULL) {
        if (*env_in != '\0')
            *main_class = JLI_StringDup(env_in);
        return;
    }

    /*
     * Scan through the arguments for options relevant to multiple JRE
     * support.  For reference, the command line syntax is defined as:
     *
     * SYNOPSIS
     *      java [options] class [argument...]
     *
     *      java [options] -jar file.jar [argument...]
     *
     * As the scan is performed, make a copy of the argument list with
     * the version specification options (new to 1.5) removed, so that
     * a version less than 1.5 can be exec'd.
     *
     * Note that due to the syntax of the native Windows interface
     * CreateProcess(), processing similar to the following exists in
     * the Windows platform specific routine ExecJRE (in java_md.c).
     * Changes here should be reproduced there.
     */
    //过滤命令行参数保存到新的变量中,最后替换原有变量
    //移除-version, -jre-restrict-search, -no-jre-restrict-search
    //设置version, restrict_search, jarflag, headlessflag, splash_file_name等变量的值
    new_argv = JLI_MemAlloc((argc + 1) * sizeof(char*));
    new_argv[0] = argv[0];
    new_argp = &new_argv[1];
    argc--;
    argv++;
    while ((arg = *argv) != 0 && *arg == '-') {
        if (JLI_StrCCmp(arg, "-version:") == 0) {
            version = arg + 9;
        } else if (JLI_StrCmp(arg, "-jre-restrict-search") == 0) {
            restrict_search = 1;//在版本搜索中包括/排除用户专用JRE(已过时)
        } else if (JLI_StrCmp(arg, "-no-jre-restrict-search") == 0) {
            restrict_search = 0;
        } else {
            //判断是否是从jar包启动
            if (JLI_StrCmp(arg, "-jar") == 0)
                jarflag = 1;
            /* deal with "unfortunate" classpath syntax */
            //argv[7] = -classpath
            //argv[8] = xx1.jar;xx2.jar;...
            //argv[9] = com.johnjoe.study.Test
            //argv[10] = -cp
            //argv[11] = xxx.jar
            //因为argv中-classpath和-cp的特殊处理,参考MSDN《C Language Reference》
            if ((JLI_StrCmp(arg, "-classpath") == 0 || JLI_StrCmp(arg, "-cp") == 0) &&
              (argc >= 2)) {
                *new_argp++ = arg;//存储-classpath | -cp
                //命令行参数自减,参数数组指向下一个元素,即jar包目录
                //赋值给arg, 下面会继续执行:*new_argp++ = arg;
                argc--;
                argv++;
                arg = *argv;
            }

            /*
             * Checking for headless toolkit option in the some way as AWT does:
             * "true" means true and any other value means false
             */
            //Headless模式是系统的一种配置模式。在该模式下,系统缺少了显示设备、键盘或鼠标。
            if (JLI_StrCmp(arg, "-Djava.awt.headless=true") == 0) {
                headlessflag = 1;
            } else if (JLI_StrCCmp(arg, "-Djava.awt.headless=") == 0) {
                headlessflag = 0;
            } else if (JLI_StrCCmp(arg, "-splash:") == 0) {
                splash_file_name = arg+8;
            }
            *new_argp++ = arg;
        }
        argc--;
        argv++;
    }
    //-jar语法:命令与参数之间有一个空格,如果是jar包启动此处可获取到jar的完全路径
    if (argc <= 0) {    /* No operand? Possibly legit with -[full]version */
        operand = NULL;
    } else {
        argc--;
        *new_argp++ = operand = *argv++;
    }
    //处理剩余的参数,即Java main函数的入参,个人觉得有点多余,最后置空
    while (argc-- > 0)  /* Copy over [argument...] */
        *new_argp++ = *argv++;
    *new_argp = NULL;

    /*
     * If there is a jar file, read the manifest. If the jarfile can't be
     * read, the manifest can't be read from the jar file, or the manifest
     * is corrupt, issue the appropriate error messages and exit.
     *
     * Even if there isn't a jar file, construct a manifest_info structure
     * containing the command line information.  It's a convenient way to carry
     * this data around.
     */
    //如果是jar包启动,并且jar路径不为空,读取MANIFEST.MF文件信息到info中
    if (jarflag && operand) {
        if ((res = JLI_ParseManifest(operand, &info)) != 0) {
            if (res == -1)
                //无法访问jar file
                JLI_ReportErrorMessage(JAR_ERROR2, operand);
            else
                //jar file无效或已损坏
                JLI_ReportErrorMessage(JAR_ERROR3, operand);
            exit(1);
        }

        /*
         * Command line splash screen option should have precedence
         * over the manifest, so the manifest data is used only if
         * splash_file_name has not been initialized above during command
         * line parsing
         */
        //根据配置决定是否设置闪屏图片(即启动界面图片,例如:AWT、android)
        //设置窗体查询启动jar
        if (!headlessflag && !splash_file_name && info.splashscreen_image_file_name) {
            splash_file_name = info.splashscreen_image_file_name;
            splash_jar_name = operand;
        }
    } else {
        //非jar包启动置空
        info.manifest_version = NULL;
        info.main_class = NULL;
        info.jre_version = NULL;
        info.jre_restrict_search = 0;
    }

    /*
     * Passing on splash screen info in environment variables
     */
    //根据配置决定是否设置闪屏图片,如果需要,添加环境变量:_JAVA_SPLASH_FILE,变量在JVMInit中的ShowSplashScreen会用到
    if (splash_file_name && !headlessflag) {
        char* splash_file_entry = JLI_MemAlloc(JLI_StrLen(SPLASH_FILE_ENV_ENTRY "=")+JLI_StrLen(splash_file_name)+1);
        JLI_StrCpy(splash_file_entry, SPLASH_FILE_ENV_ENTRY "=");
        JLI_StrCat(splash_file_entry, splash_file_name);
        putenv(splash_file_entry);
    }
    //根据配置决定是否设置窗体查询启动jar,如果需要,添加环境变量:_JAVA_SPLASH_JAR,变量在JVMInit中的ShowSplashScreen会用到
    if (splash_jar_name && !headlessflag) {
        char* splash_jar_entry = JLI_MemAlloc(JLI_StrLen(SPLASH_JAR_ENV_ENTRY "=")+JLI_StrLen(splash_jar_name)+1);
        JLI_StrCpy(splash_jar_entry, SPLASH_JAR_ENV_ENTRY "=");
        JLI_StrCat(splash_jar_entry, splash_jar_name);
        putenv(splash_jar_entry);
    }

    /*
     * The JRE-Version and JRE-Restrict-Search values (if any) from the
     * manifest are overwritten by any specified on the command line.
     */
    //命令行中配置了version,用命令行配置的version重写MANIFEST.MF中的jre version
    if (version != NULL)
        info.jre_version = version;
    //命令行中配置了-jre-restrict-search/-no-jre-restrict-search,用命令行配置的restrict_search重写MANIFEST.MF中的jre_restrict_search
    if (restrict_search != -1)
        info.jre_restrict_search = restrict_search;

    /*
     * "Valid" returns (other than unrecoverable errors) follow.  Set
     * main_class as a side-effect of this routine.
     */
    if (info.main_class != NULL)
        *main_class = JLI_StringDup(info.main_class);

    /*
     * If no version selection information is found either on the command
     * line or in the manifest, simply return.
     */
    //如果-version也未定义或为空,且jar中META-INF/MANIFEST.MF的JRE-Version属性未定义或为空,直接释放内存并返回
    if (info.jre_version == NULL) {
        JLI_FreeManifest();
        JLI_MemFree(new_argv);
        return;
    }

    /*
     * Check for correct syntax of the version specification (JSR 56).
     */
    //校验jre version的有效性:
    //		simple-element  ::= version-id | version-id modifier
    //		modifier        ::= '+' | '*'
    //		version-id      ::= string ( separator  string )*
    //		string          ::= char ( char )*
    //		char            ::= Any ASCII character except a space, an ampersand, a separator or a modifier
    //		separator       ::= '.' | '-' | '_'
    //1) Doesn't contain a space, an ampersand or a modifier.
    //2) Doesn't begin or end with a separator.
    //3) Doesn't contain two adjacent separators.
    //1.不能包含:' ' | '+' | '*'
    //2.不能以:'.' | '-' | '_' 开始或结束
    //3.不能包含两个连续的分隔符:'.' | '-' | '_'
    if (!JLI_ValidVersionString(info.jre_version)) {
        JLI_ReportErrorMessage(SPC_ERROR1, info.jre_version);
        exit(1);
    }

    /*
     * Find the appropriate JVM on the system. Just to be as forgiving as
     * possible, if the standard algorithms don't locate an appropriate
     * jre, check to see if the one running will satisfy the requirements.
     * This can happen on systems which haven't been set-up for multiple
     * JRE support.
     */
    //读取注册表获取最佳jre目录地址
    jre = LocateJRE(&info);
    JLI_TraceLauncher("JRE-Version = %s, JRE-Restrict-Search = %s Selected = %s\n",
        (info.jre_version?info.jre_version:"null"),
        (info.jre_restrict_search?"true":"false"), (jre?jre:"null"));
	//如果jre目录地址为NULL,退出程序
    if (jre == NULL) {
        if (JLI_AcceptableRelease(GetFullVersion(), info.jre_version)) {
            JLI_FreeManifest();
            JLI_MemFree(new_argv);
            return;
        } else {
        	//"Error: Unable to locate JRE meeting specification \"%s\""
            JLI_ReportErrorMessage(CFG_ERROR4, info.jre_version);
            exit(1);
        }
    }

    /*
     * If I'm not the chosen one, exec the chosen one.  Returning from
     * ExecJRE indicates that I am indeed the chosen one.
     *
     * The private environment variable _JAVA_VERSION_SET is used to
     * prevent the chosen one from re-reading the manifest file and
     * using the values found within to override the (potential) command
     * line flags stripped from argv (because the target may not
     * understand them).  Passing the MainClass value is an optimization
     * to avoid locating, expanding and parsing the manifest extra
     * times.
     */
    //MANIFEST.MF中Main-Class不为NULL,且长度小于MAX_PATH,拼接环境变量
    if (info.main_class != NULL) {
    	//MAXNAMELEN最终引用系统的MAX_PATH
    	//windows 260
    	//MAC 256
        if (JLI_StrLen(info.main_class) <= MAXNAMELEN) {
            (void)JLI_StrCat(env_entry, info.main_class);
        } else {
        	//"Error: main-class: attribute exceeds system limits of %d bytes\n" GEN_ERROR
            JLI_ReportErrorMessage(CLS_ERROR5, MAXNAMELEN);
            exit(1);
        }
    }
    //将META-INF/MANIFEST.MF中Main-Class存储到当前用户的环境变量_JAVA_VERSION_SET中在方法开始时调用,防止重复加载
    (void)putenv(env_entry);
    //尝试执行JRE
    ExecJRE(jre, new_argv);
    JLI_FreeManifest();
    JLI_MemFree(new_argv);
    return;
}

src/share/bin/java.h::ENV_ENTRY

#define ENV_ENTRY "_JAVA_VERSION_SET"

猜你喜欢

转载自blog.csdn.net/weixin_37477523/article/details/88121221