前言
SpringBoot的官方文档中关于Jersey的介绍并不是很全面: 27.3 JAX-RS and Jersey,SpringBoot-Sample项目里面也只有非常基础的代码,对于一些复杂的常用需求,这个文档给不了任何帮助。
为了使用Jersey提供的Restful API完成文件上传功能,今天我花了不少时间查阅文档资料,遇到了一些问题,然后不断地踩坑尝试,其中一些坑还是参照Stack Overflow的解决方案,甚至是框架官方文档的说明而碰到的。主要问题就是,SpringBoot和Jersey的官方文档没有给出更详细的内容,Stack Overflow针对的问题很片面,不能适用于所有的情况,所以我打算将搭建项目的过程从头到尾写下来,以便有一个方便参照的教程。
项目搭建
我使用了Spring发布的Spring Tool Suit(STS)来创建项目,因为这个IDE使用SpringBoot十分方便,可以在创建项目时引入一些技术栈。
我使用的Java版本是JDK 8,后面会用到CURL这个命令行工具来测试相关的接口,大家可以先准备好,以便学习过程连贯。
这个项目命名为demo-app
,默认包为org.demo
,我在创建时仅添加了Jersey的支持,SpringBoot版本是1.5.10。
如果没有STS,也可以用Eclipse创建一个Maven项目,Pom文件配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.demo</groupId>
<artifactId>demo-app</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
项目创建完成后,需要添加一个jersey-media-multipart
的依赖,在pom中dependencies
标签中添加:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>
</dependency>
这个依赖不需要版本号,因为在spring-boot-starter-parent
中已经定义了版本,我们只需要添加一个dependency在具体项目中即可。
编写代码
项目创建好时,已经存在一个带有main方法的入口类DemoAppApplication
,我们不需要改动它。
Jersey的官方文档中将Restful API调用的入口称作Resources
,而在SpringBoot的示例代码中将其命名为Endpoint
,其实指的是同一个东西。因为使用了SpringBoot,为了风格统一我使用了Endpoint
的命名规则,这不是强制的,大家也可以自定义命名规则。但建议从这两者中选择一种,以便大家方便理解。
首先增加一个HelloEndpoint
类:
package org.demo;
import java.io.IOException;
import java.io.InputStream;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
import org.apache.tomcat.util.http.fileupload.ByteArrayOutputStream;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;
import org.springframework.stereotype.Component;
@Component
@Path("/file")
public class FileUploadEndpoint {
@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
public String upload(@FormDataParam("file") InputStream fis,
@FormDataParam("file") FormDataContentDisposition fileDisposition) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
IOUtils.copy(fis, baos);
String content = new String(baos.toByteArray());
return content;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
它十分类似于SpringMVC的Controller
,但是拥有更规范更严格的REST风格,而且它不能像SpringMVC一样通过返回一个视图名称指向某个视图。
其次是JerseyConfig
类:
package org.demo;
import javax.ws.rs.ApplicationPath;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.stereotype.Component;
@Component
@ApplicationPath("/rest/demo")
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(MultiPartFeature.class);
register(FileUploadEndpoint.class);
}
}
至此,一个文件上传的服务端接口已经编写完成。关于表单页面这里不多做说明,因为这个项目不是一个Web项目,而是Web Service的服务端,不能在项目中直接访问静态Web资源。也不建议使用特殊方法满足这种需要,我们应当保持项目的纯净。
需要注意@ApplicationPath
这个注解,它决定了所有Endpoint
的基础路径。
运行测试
下面我们来测试一下这个Restful API是否能正常工作,运行DemoAppApplication
,等待项目部署。
接下来准备使用CURL测试,CURL可以到官网下载操作系统对应的版本。创建一个demo.txt
文件保存到任意目录,文件内容写入Test my restful api with curl.
,使用英文是为了避免CMD命令行中的中文出现乱码的情况,这里不用过多在意,我们只要关注结果。
进入demo.txt
文件所在的目录,按住Shift
打开CMD或者PowerShell(Linux系统下打开终端定位到该目录),执行:curl -X POST -F "file=@demo.txt" http://localhost:8080/rest/demo/file
如果得到Test my restful api with curl.
的回显,说明Restful API部署成功,并且能够接收上传的文件。
注意问题
在实现利用Jersey完成文件上传的过程中,我遇到的一些问题需要大家特别关注:
- SpringBoot没有默认添加
jersey-media-multipart
依赖,仅预先定义了需要的版本,如果未引入这个包,将无法使用@FormDataParam
注解和Multipart相关的类,无法对Multipart内容进行解析; -
JerseyConfig
中需要注册MultiPartFeature.class
,否则会出现报错,无法正确注入文件输入流对象。报错的时机根据是否延迟加载决定,如果application.properties定义了spring.jersey.servlet.load-on-startup=1
,会在项目启动时报错;否则会在首次上传文件,初始化FileUploadEndpoint
时报错。 - 关于
FileUploadEndpoint.upload()
方法,有些文档和教程中,fileDisposition
对象使用了ContentDisposition
类来定义,尽管它是FormDataContentDisposition
的父类,但仍然会报错,原因未知。建议直接使用FormDataContentDisposition