SpringBoot整合Jersey2.x实现文件上传API

前言

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。

《SpringBoot整合Jersey2.x实现文件上传API》

如果没有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
    原文作者:博博当时就震精了
    原文地址: https://segmentfault.com/a/1190000013132307
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞