我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Illegal char <:> at index 4

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Illegal char <:> at index 4

一、现象

Java11环境下项目启动时报错:java.nio.file.InvalidPathException: Illegal char <:> at index 4

但项目能正常启动、运行。

二、解决办法

方法1

方法2

项目路径\.idea\workspace.xml中的PropertiesComponent节点下新增配置:

三、原因

异常在WindwosPathParser:182被抛出,提示存在非法字符冒号':'

阅读该方法源码:

private static String normalize(StringBuilder sb, String path, int off) {    int len = path.length();    off = nextNonSlash(path, off, len);    int start = off;    char lastC = 0;    while (off < len) {        char c = path.charAt(off);        if (isSlash(c)) {            if (lastC == ' ')                throw new InvalidPathException(path,                   "Trailing char <" + lastC + ">",                   off - 1);            sb.append(path, start, off);            off = nextNonSlash(path, off, len);            if (off != len)   //no slash at the end of normalized path                sb.append('\\');            start = off;        } else {            if (isInvalidPathChar(c))                throw new InvalidPathException(path,                   "Illegal char <" + c + ">",                   off);            lastC = c;            off++;        }    }    if (start != off) {        if (lastC == ' ')            throw new InvalidPathException(path,               "Trailing char <" + lastC + ">",               off - 1);        sb.append(path, start, off);    }    return sb.toString();}

上述normalize方法代码块中line19会进行一次判断,若满足条件,抛出InvalidPathException异常,其判断条件isInvalidPathChar方法源码如下:

private static final String reservedChars = "<>:\"|?*";private static final boolean isInvalidPathChar(char ch) {    return ch < '\u0020' || reservedChars.indexOf(ch) != -1;}

isInvalidPathChar方法会判断字符是否小于Unicode空格'\u0020',并且是否是<>:\"|?*中的字符。

所以若出现该异常,无非是路径中存在特殊字符,通过debug,获取了当时引发问题的路径值,normalize方法中的path入参:“file:/D:/Code/Java/foo/web/target/test-classes/”,off入参值为0。遍历到"file:/"中':'字符时,抛出异常,导致应用启动出现异常,但被catch住不影响主流程。

路径以"file:/”开头,看上去像文件传输协议,但协议应该为“file://"是双斜杠,"file:/”是什么呢?url - What is the difference between file:/, file://, file:/// - Stack Overflow StackOverflow中高赞回答称"file:/“是无效的,但将上述路径复制到windows文件管理器中可以正常访问,可能因为Windows兼容性太好?

想了解为什么传入了这个以"file:/"开头的path,off为0,需查看该方法的调用方:

private static Result parse(String input, boolean requireToNormalize) {    String root = "";    WindowsPathType type = null;     int len = input.length();    int off = 0;    if (len > 1) {        char c0 = input.charAt(0);        char c1 = input.charAt(1);        char c = 0;        int next = 2;        if (isSlash(c0) && isSlash(c1)) {            // UNC: We keep the first two slash, collapse all the            // following, then take the hostname and share name out,            // meanwhile collapsing all the redundant slashes.            type = WindowsPathType.UNC;            off = nextNonSlash(input, next, len);            next = nextSlash(input, off, len);            if (off == next)                throw new InvalidPathException(input, "UNC path is missing hostname");            String host = input.substring(off, next);  //host            off = nextNonSlash(input, next, len);            next = nextSlash(input, off, len);            if (off == next)                throw new InvalidPathException(input, "UNC path is missing sharename");            root = "\\\\" + host + "\\" + input.substring(off, next) + "\\";            off = next;        } else {            if (isLetter(c0) && c1 == ':') {                char c2;                if (len > 2 && isSlash(c2 = input.charAt(2))) {                    // avoid concatenation when root is "D:\"                    if (c2 == '\\') {                        root = input.substring(0, 3);                    } else {                        root = input.substring(0, 2) + '\\';                    }                    off = 3;                    type = WindowsPathType.ABSOLUTE;                } else {                    root = input.substring(0, 2);                    off = 2;                    type = WindowsPathType.DRIVE_RELATIVE;                }            }        }    }    if (off == 0) {        if (len > 0 && isSlash(input.charAt(0))) {            type = WindowsPathType.DIRECTORY_RELATIVE;            root = "\\";        } else {            type = WindowsPathType.RELATIVE;        }    }     if (requireToNormalize) {        StringBuilder sb = new StringBuilder(input.length());        sb.append(root);        return new Result(type, root, normalize(sb, input, off));    } else {        return new Result(type, root, input);    }}

其中line60调用了normalize方法。

parse方法入参input即上文中提到的路径,方法通过多个if语句来判断路径类型,并初始化off偏移量。(我们常见的路径无非是相对路径、绝对路径,Java在sun.nio.fs.WindowsPathType中枚举出了Windows系统下所有的路径格式,Windows文件路径格式参考微软文档Windows 系统中的文件路径格式 | Microsoft Learn

该方法line12开始判断:若路径首字符、第二个字符都是斜杠,属于WindowsPathType.UNC类型路径,然后计算出host名和root名;若首字符、第二个字符不是斜杠且首字符是字母,第二个字符是冒号':',路径字符长度大于2且第三个字符是斜杠,属于WindowsPathType.ABSOLUTE路径,比如”C:/foo"就是绝对路径,否则就是WindowsPathType.DRIVE_RELATIVE如"C:foo1/foo2"是基于驱动器的相对路径...其他判断类似,可自行阅读代码。

就路径"file:/D:/Code/Java/foo/web/target/test-classes/"来说,它既不是UNC路径,不是绝对路径,不是驱动器相对路径、不是目录相对路径,所以Java源码认为这个路径是相对路径,相对路径的off从0开始。显然,这个路径不是相对路径,off从0开始,后续字符肯定包含冒号':'特殊字符。

继续追踪堆栈,观察这个路径创建的位置,可以找到FSInfo#getJarClassPath方法:

public List getJarClassPath(Path file) throws IOException {    Path parent = file.getParent();    try (JarFile jarFile = new JarFile(file.toFile())) {        Manifest man = jarFile.getManifest();        if (man == null)            return Collections.emptyList();         Attributes attr = man.getMainAttributes();        if (attr == null)            return Collections.emptyList();         String path = attr.getValue(Attributes.Name.CLASS_PATH);        if (path == null)            return Collections.emptyList();         List list = new ArrayList<>();         for (StringTokenizer st = new StringTokenizer(path);             st.hasMoreTokens(); ) {            String elt = st.nextToken();            Path f = FileSystems.getDefault().getPath(elt);            if (!f.isAbsolute() && parent != null)                f = parent.resolve(f).toAbsolutePath();            list.add(f);        }         return list;    }}

line21中getPath方法中会调用上述parse方法,抛出异常。变量elt就是路径。

getJarClassPath方法会获取jar包的classPath。94行中使用try-with-resources方式将Path转换为File,然后传入File对象创建JarFile对象。拿到JarFile后,获取Jar包中的manifest文件,获取manifest中所有属性,获取Class-Path属性的值,可以发现以“file:/foo"开头的路径来自于jar包中manifest中的Class-Path属性。getJarClassPath入参file的路径值为“D:\Users\user\AppData\Local\Temp\classpath756628492.jar”,可能是这个jar包导致的问题。解压jar包,部分内容截图如下:

Class-Path属性的值全是以"file:/"开头,看来报错和这个jar包有关。查看Created-By属性,值为”IntelliJ IDEA“,那么问题来了:

a). 为什么Java应用会引入IDEA动态生成的jar包?

b). 以"file:/"开头的路径是不是有效路径?

针对问题a),定位到了jar包的生成路径,查询了相关博客,发现是IDEA中一个设置问题。启动项目会提示命令过长无法启动,之前我都是修改运行配置如下图:

但博客中并没有说明导致错误的原因。我猜测这个配置用来充当桥梁,集成了Java应用所包含包的classPath,然后IDEA启动Java应用时引入了IDEA动态生成的Jar包。

针对问题b),暂未查找到文献表明"file:/"是有效协议,但Windows文件管理器确实能处理该格式路径。上文介绍了Java11处理路径的相关代码,表明Java11不支持该格式路径,会抛出异常,所以对Java11来说,"file:/"是无效路径。

但为什么JDK8没这个问题?

于是我研究了下为什么运行时无法Debug到JavacFileManager类中,或者为什么直接编写JavacFileManager这个类爆红,但全网找了找都没找到答案。难道是CompileTime没有,RunTime才有?JavacFileManager实现了StandardJavaFileManager接口,JDK8代码中可以直接跳转到StandardJavaFileManager接口,但找不到这个接口的任何实现类,说明compileTime中只有接口但没有实现类,而运行时能获取到实现类JavacFileManager,说明实现类在RunTime是存在的。联想到门面模式,应用应该依赖接口而不是依赖实现,比如Java的数据库驱动,利用SPI的特性,各个数据库厂商提供底层实现,但开发者不用关心底层实现,只依赖JDBC中提供的接口。为了避免开发者拿到数据库厂商的底层实现,可以将数据库驱动的maven依赖scope定义为runtime,这样在代码编写的过程中,开发者没法拿到接口的实现类,只能使用JDBC接口,代码运行中自动获取接口的实现类,从而做到解耦。上述内容都是我个人的猜测,也可能是其他原因导致,真实原因就不得而知了。

如果我的猜想正确,那我maven引入这个类所在的jar包就行,于是我翻了翻Java文档,看这个类在哪儿。文档显示这个类在com.sun.tools.javac.file包中,这个包在JDK8路径的/lib/tools.jar包中有,maven依赖如下:

    com.sun    tools    1.8.0    system    ${java.home}/../lib/tools.jar

我们回归问题本质,来看看JDK8下为什么不会报错。JDK11报错是在getJarClassPath中调用getPath抛出异常,我们直接来看JDK8的getJarClassPath方法:

public List getJarClassPath(File var1) throws IOException {    String var2 = var1.getParent();    JarFile var3 = new JarFile(var1);     List var6;    try {        Manifest var4 = var3.getManifest();        if (var4 == null) {            List var14 = Collections.emptyList();            return var14;        }         Attributes var5 = var4.getMainAttributes();        if (var5 != null) {            String var15 = var5.getValue(Name.CLASS_PATH);            if (var15 == null) {                List var16 = Collections.emptyList();                return var16;            }             ArrayList var7 = new ArrayList();            StringTokenizer var8 = new StringTokenizer(var15);             while(var8.hasMoreTokens()) {                String var9 = var8.nextToken();                File var10 = var2 == null ? new File(var9) : new File(var2, var9);                var7.add(var10);            }             ArrayList var17 = var7;            return var17;        }         var6 = Collections.emptyList();    } finally {        var3.close();    }     return var6;}

由于引入的是jar包,都是class字节码文件,所以源码不太好理解。总的来说,JDK8中代码整体流程和JDK11类似,也是通过jar包的文件路径获取manifest中的Class-Path属性,遍历Class-Path属性中的classPath生成classPath路径对应的File对象,我们直接定位到24行开始看,在24行中的while循环中,利用classPath路径生成File对象,将对象加入列表中,列表中存放是File,看到这里是不是感觉有什么不对劲?查看该方法的签名,JDK8的方法返回List,我们再看JDK11中的实现,JDK11中getJarClassPath方法返回List,差异出在这里。将String类型路径传入File类的构造器创建File对象,别说传"file:/foo",传个"FUCK"都没问题,只不过拿不到真实的文件,所以JDK8中不会出现异常。

来源地址:https://blog.csdn.net/IOT_player/article/details/129033459

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Illegal char <:> at index 4

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录