在浏览器中点击 PDF 文件的链接:
- 在 A 网站点击 a.pdf,浏览器自动下载 a.pdf
- 在 B 网站点击 b.pdf,浏览器直接打开 b.pdf
被访问都是 PDF 文件,为啥在网站 A 和在网站 B 访问时,浏览器的行为不一样,是什么东西影响它在浏览器中的行为呢?答案就是浏览器会根据响应的 Content-Type 来决定下载还是打开它们 (当然 Content-Type 的值只是一个 hit,具体的操作还是要看浏览器的实现)。
文件的类型非常多,怎么获取文件的 Content-Type 是什么呢?Java 1.7 提供 java.nio.file.Files.probeContentType(Path path)
用于尝试获取文件的 Content-Type,但发现支持的文件类型不够全面,查看方法 probeContentType 的帮助文档:
This method uses the installed FileTypeDetector implementations to probe the given file to determine its content type. Each file type detector’s probeContentType is invoked, in turn, to probe the file type. If the file is recognized then the content type is returned. If the file is not recognized by any of the installed file type detectors then a system-default file type detector is invoked to guess the content type.
A given invocation of the Java virtual machine maintains a system-wide list of file type detectors. Installed file type detectors are loaded using the service-provider loading facility defined by the ServiceLoader class. Installed file type detectors are loaded using the system class loader. If the system class loader cannot be found then the extension class loader is used; If the extension class loader cannot be found then the bootstrap class loader is used. File type detectors are typically installed by placing them in a JAR file on the application class path or in the extension directory, the JAR file contains a provider-configuration file named java.nio.file.spi.FileTypeDetector in the resource directory META-INF/services, and the file lists one or more fully-qualified names of concrete subclass of FileTypeDetector that have a zero argument constructor. If the process of locating or instantiating the installed file type detectors fails then an unspecified error is thrown. The ordering that installed providers are located is implementation specific.
从文档中可以知道,只需要实现接口 FileTypeDetector,并以 SPI 的方式配置好,打成 jar 包放到项目的 classpath 中,调用 Files.probeContentType
时会先使用自定义的 FileTypeDetector 尝试获取文件的 Content-Type,如果获取不到返回 null 才会使用系统默认自带的 FileTypeDetector 继续尝试获取。
创建项目 content-type-prober 来介绍具体实现,项目的目录结构为:
1 | content-type-prober |
其中:
- main/java/xtuer/ContentTypeProber 是 FileTypeDetector 接口的实现类,核心代码
- META-INF/services/java.nio.file.spi.FileTypeDetector 用于 SPI 的配置
- config/content-type.properties 是文件类型与 Content-Type 的映射关系
- ContentTypeProberTest 是测试类
下面就逐个文件的进行介绍。
ContentTypeProber.java
主要逻辑为项目启动时自动使用 SPI 机制加载类 ContentTypeProber 到 JVM,在静态代码块里把 content-type.properties 的内容加载到 Properties 对象 CONTENT_TYPE_PROPS 中。
调用 Files.probeContentType(path)
获取文件的 Content-Type 时会先调用 ContentTypeProber.probeContentType(path)
进行获取,在其中使用文件的后缀名从 CONTENT_TYPE_PROPS 中查询对应的 Content-Type。
1 | package xtuer; |
java.nio.file.spi.FileTypeDetector
文件 java.nio.file.spi.FileTypeDetector 为 SPI 的配置文件,需要放到 META-INF/services 目录里,其内容为接口 java.nio.file.spi.FileTypeDetector 的实现类的全路径名 (fully-qualified name):
1 | xtuer.ContentTypeProber |
content-type.properties
文件类型与 Content-Type 的映射关系使用 Java properties 格式: 后缀名 = Content-Type。内容过长,以下部分仅作样例展示,可点击下载:
1 | 3dm = x-world/x-3dmf |
由于使用 classLoader.getResourceAsStream(CONTENT_TYPE_PROPS_PATH)
加载 content-type.properties 文件,会优先在项目的 classpath 里搜索,搜索不到再从 classpath 的 jar 中进行搜索,当上面提供的 Content-Type 不满足需求时,可以在项目的 config/content-type.properties 里配置具体需要的 Content-Type,会覆盖上面提供的 Content-Type (注意是覆盖,不是合并),不需要去修改 ContentTypeProber 所在的 jar 包。
ContentTypeProberTest.java
测试自定义的 ContentTypeProber 是否生效,执行下面的程序不报异常则说明生效。
1 | import org.junit.Assert; |