泽西岛:将上传的文件读取为 JSON
Jersey: read uploaded file as JSON
我可以通过以下方式在 jersey 中上传文件:
@POST
@Consumes( MediaType.MULTIPART_FORM_DATA)
public void indexFile(
@FormDataParam("file") InputStream uploadedFile,
@FormDataParam("file") FormDataContentDisposition fileInfo){
System.out.println(fileInfo.getFileName());
DAO.indexFileToSolr(uploadedFile);
}
这给了我文件作为输入流。
此文件包含以下格式的 json 个对象数组:
[
{
id: 1,
title: "someTitle"
}
]
我还有一个 POJO/ Java class 表示这个对象是这样的:
@XmlRootElement
public class MyObj{
private int id;
private String title;
//getters and setters
}
我正在使用 jersey-media-moxy
在传入请求或传出响应时自动在 POJO 和 JSON/XML 之间转换。
(我不需要为此编写任何代码,我只需要指定请求将采用 JSON 格式,或者响应应为 JSON,并且 serialization/deserialization 是由 jersey-media-moxy
处理。)
问题是,我无法在文件上传方法中指定此行为,我看不到告诉 jersey 或 jersey-media-moxy
将文件序列化为 java 对象的方式。
我在 jersey-media-moxy
中找不到任何方法来实际读取 InputStream 并将其序列化为 POJO。
注意:我可能可以使用 jackson
读取 json 文件并创建一个 Java 对象列表,但我不太愿意这样做,首先是因为需要另一个库(如果我能用 jersey-media-mosy 做到这一点就太好了)其次,我不确定是否有一种简单的方法可以告诉 jersey 这个文件包含 Json 个对象,所以,对其进行反序列化。
如果您可以让客户端为 JSON 的单个部分设置 Content-Type
header,那么处理这个问题就很简单了。对于 multipart,每个部分都可以有自己的 Content-Type
。例如,原始多部分请求的一部分可能看起来像
--Boundary_1_1938025186_1463410894758
Content-Type: application/json
Content-Disposition: form-data; name="beans"
[ {"name": "peeskillet"} ]
--Boundary_1_1938025186_1463410894758--
您可以看到对于这个特定部分,Content-Type
设置为 application/json
。如果你可以从客户端得到这个,那么你需要做的就是像普通的 JSON 请求一样有一个 POJO 参数
@POST
@Path("with-content-type")
public List<Bean> post1(@FormDataParam("beans") List<Bean> beans) {
return beans;
}
如果您尝试使用上面的方法,并且客户端没有设置 Content-Type
,那么它将默认为 text/plain
,结果很奇怪。他们并不像预期的那样。根据我的测试,bean 属性 似乎设置为整个原始 JSON。
不过我们可以做的是获取 pre-deserialized body 部分,显式设置 Content-Type
,然后反序列化它。
@POST
@Path("no-content-type")
public Bean[] post2(@FormDataParam("beans") FormDataBodyPart bodyPart) throws IOException {
bodyPart.setMediaType(MediaType.APPLICATION_JSON_TYPE);
Bean[] beans = bodyPart.getEntityAs(Bean[].class);
return beans;
}
请注意,我使用 Bean[]
而不是 List<Bean>
。尝试获得 List<Bean>
的问题在于我们无法做到 bodyPart.getEntityAs(List.class)
,因为 MOXy 需要知道泛型类型。所以我们只是把它作为一个数组来获取。 Bean[]
也可以用作方法参数,而不是第一个示例中的 List<Bean>
,如果客户端要设置 Content-Type
.
下面是一个完整的测试用例,使用Jersey Test Framework。
import java.io.IOException;
import java.util.List;
import java.util.logging.Logger;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.xml.bind.annotation.XmlRootElement;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.filter.LoggingFilter;
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.glassfish.jersey.media.multipart.FormDataParam;
import org.glassfish.jersey.media.multipart.MultiPart;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.moxy.json.MoxyJsonFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.junit.Assert.assertThat;
/**
* Run this like any other JUnit test. Only two required dependencies.
*
* <dependency>
* <groupId>org.glassfish.jersey.test-framework.providers</groupId>
* <artifactId>jersey-test-framework-provider-inmemory</artifactId>
* <version>2.22.1</version>
* <scope>test</scope>
* </dependency>
* <dependency>
* <groupId>org.glassfish.jersey.media</groupId>
* <artifactId>jersey-media-moxy</artifactId>
* <version>2.22.1</version>
* <scope>test</scope>
* </dependency>
*
* @author Paul Samsotha
*/
public class MoxyMultipartTest extends JerseyTest {
@XmlRootElement
public static class Bean {
private String name;
public Bean() {
}
public Bean(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return this.name;
}
}
@Path("test")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.APPLICATION_JSON)
public static class TestResource {
@POST
@Path("with-content-type")
public Bean[] post1(@FormDataParam("beans") Bean[] beans) {
return beans;
}
@POST
@Path("no-content-type")
public Bean[] post2(@FormDataParam("beans") FormDataBodyPart bodyPart) throws IOException {
bodyPart.setMediaType(MediaType.APPLICATION_JSON_TYPE);
Bean[] beans = bodyPart.getEntityAs(Bean[].class);
return beans;
}
}
@Override
public void configureClient(ClientConfig config) {
config.register(MultiPartFeature.class);
config.register(new LoggingFilter(Logger.getAnonymousLogger(), true));
}
@Override
public ResourceConfig configure() {
return new ResourceConfig(TestResource.class)
.register(MultiPartFeature.class)
.register(MoxyJsonFeature.class);
}
final String json = "[ {\"name\": \"peeskillet\"} ]";
@Test
public void testSettingContentType() {
final MultiPart multiPart = new FormDataMultiPart()
.field("beans", json, MediaType.APPLICATION_JSON_TYPE);
final Response response = target("test/with-content-type")
.request().post(Entity.entity(multiPart, MediaType.MULTIPART_FORM_DATA));
assertResponseHasBeans(response);
}
@Test
public void testWithoutSettingContentType() {
final MultiPart multiPart = new FormDataMultiPart()
.field("beans", json); // No Content-Type
final Response response = target("test/no-content-type")
.request().post(Entity.entity(multiPart, MediaType.MULTIPART_FORM_DATA));
assertResponseHasBeans(response);
}
private void assertResponseHasBeans(Response response) {
final List<Bean> beans = response.readEntity(new GenericType<List<Bean>>() {
});
assertThat(beans, is(notNullValue()));
assertThat(beans.isEmpty(), is(false));
assertThat(beans.get(0).getName(), is("peeskillet"));
}
}
我可以通过以下方式在 jersey 中上传文件:
@POST
@Consumes( MediaType.MULTIPART_FORM_DATA)
public void indexFile(
@FormDataParam("file") InputStream uploadedFile,
@FormDataParam("file") FormDataContentDisposition fileInfo){
System.out.println(fileInfo.getFileName());
DAO.indexFileToSolr(uploadedFile);
}
这给了我文件作为输入流。 此文件包含以下格式的 json 个对象数组:
[
{
id: 1,
title: "someTitle"
}
]
我还有一个 POJO/ Java class 表示这个对象是这样的:
@XmlRootElement
public class MyObj{
private int id;
private String title;
//getters and setters
}
我正在使用 jersey-media-moxy
在传入请求或传出响应时自动在 POJO 和 JSON/XML 之间转换。
(我不需要为此编写任何代码,我只需要指定请求将采用 JSON 格式,或者响应应为 JSON,并且 serialization/deserialization 是由 jersey-media-moxy
处理。)
问题是,我无法在文件上传方法中指定此行为,我看不到告诉 jersey 或 jersey-media-moxy
将文件序列化为 java 对象的方式。
我在 jersey-media-moxy
中找不到任何方法来实际读取 InputStream 并将其序列化为 POJO。
注意:我可能可以使用 jackson
读取 json 文件并创建一个 Java 对象列表,但我不太愿意这样做,首先是因为需要另一个库(如果我能用 jersey-media-mosy 做到这一点就太好了)其次,我不确定是否有一种简单的方法可以告诉 jersey 这个文件包含 Json 个对象,所以,对其进行反序列化。
如果您可以让客户端为 JSON 的单个部分设置 Content-Type
header,那么处理这个问题就很简单了。对于 multipart,每个部分都可以有自己的 Content-Type
。例如,原始多部分请求的一部分可能看起来像
--Boundary_1_1938025186_1463410894758
Content-Type: application/json
Content-Disposition: form-data; name="beans"
[ {"name": "peeskillet"} ]
--Boundary_1_1938025186_1463410894758--
您可以看到对于这个特定部分,Content-Type
设置为 application/json
。如果你可以从客户端得到这个,那么你需要做的就是像普通的 JSON 请求一样有一个 POJO 参数
@POST
@Path("with-content-type")
public List<Bean> post1(@FormDataParam("beans") List<Bean> beans) {
return beans;
}
如果您尝试使用上面的方法,并且客户端没有设置 Content-Type
,那么它将默认为 text/plain
,结果很奇怪。他们并不像预期的那样。根据我的测试,bean 属性 似乎设置为整个原始 JSON。
不过我们可以做的是获取 pre-deserialized body 部分,显式设置 Content-Type
,然后反序列化它。
@POST
@Path("no-content-type")
public Bean[] post2(@FormDataParam("beans") FormDataBodyPart bodyPart) throws IOException {
bodyPart.setMediaType(MediaType.APPLICATION_JSON_TYPE);
Bean[] beans = bodyPart.getEntityAs(Bean[].class);
return beans;
}
请注意,我使用 Bean[]
而不是 List<Bean>
。尝试获得 List<Bean>
的问题在于我们无法做到 bodyPart.getEntityAs(List.class)
,因为 MOXy 需要知道泛型类型。所以我们只是把它作为一个数组来获取。 Bean[]
也可以用作方法参数,而不是第一个示例中的 List<Bean>
,如果客户端要设置 Content-Type
.
下面是一个完整的测试用例,使用Jersey Test Framework。
import java.io.IOException;
import java.util.List;
import java.util.logging.Logger;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.xml.bind.annotation.XmlRootElement;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.filter.LoggingFilter;
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.glassfish.jersey.media.multipart.FormDataParam;
import org.glassfish.jersey.media.multipart.MultiPart;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.moxy.json.MoxyJsonFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.junit.Assert.assertThat;
/**
* Run this like any other JUnit test. Only two required dependencies.
*
* <dependency>
* <groupId>org.glassfish.jersey.test-framework.providers</groupId>
* <artifactId>jersey-test-framework-provider-inmemory</artifactId>
* <version>2.22.1</version>
* <scope>test</scope>
* </dependency>
* <dependency>
* <groupId>org.glassfish.jersey.media</groupId>
* <artifactId>jersey-media-moxy</artifactId>
* <version>2.22.1</version>
* <scope>test</scope>
* </dependency>
*
* @author Paul Samsotha
*/
public class MoxyMultipartTest extends JerseyTest {
@XmlRootElement
public static class Bean {
private String name;
public Bean() {
}
public Bean(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return this.name;
}
}
@Path("test")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.APPLICATION_JSON)
public static class TestResource {
@POST
@Path("with-content-type")
public Bean[] post1(@FormDataParam("beans") Bean[] beans) {
return beans;
}
@POST
@Path("no-content-type")
public Bean[] post2(@FormDataParam("beans") FormDataBodyPart bodyPart) throws IOException {
bodyPart.setMediaType(MediaType.APPLICATION_JSON_TYPE);
Bean[] beans = bodyPart.getEntityAs(Bean[].class);
return beans;
}
}
@Override
public void configureClient(ClientConfig config) {
config.register(MultiPartFeature.class);
config.register(new LoggingFilter(Logger.getAnonymousLogger(), true));
}
@Override
public ResourceConfig configure() {
return new ResourceConfig(TestResource.class)
.register(MultiPartFeature.class)
.register(MoxyJsonFeature.class);
}
final String json = "[ {\"name\": \"peeskillet\"} ]";
@Test
public void testSettingContentType() {
final MultiPart multiPart = new FormDataMultiPart()
.field("beans", json, MediaType.APPLICATION_JSON_TYPE);
final Response response = target("test/with-content-type")
.request().post(Entity.entity(multiPart, MediaType.MULTIPART_FORM_DATA));
assertResponseHasBeans(response);
}
@Test
public void testWithoutSettingContentType() {
final MultiPart multiPart = new FormDataMultiPart()
.field("beans", json); // No Content-Type
final Response response = target("test/no-content-type")
.request().post(Entity.entity(multiPart, MediaType.MULTIPART_FORM_DATA));
assertResponseHasBeans(response);
}
private void assertResponseHasBeans(Response response) {
final List<Bean> beans = response.readEntity(new GenericType<List<Bean>>() {
});
assertThat(beans, is(notNullValue()));
assertThat(beans.isEmpty(), is(false));
assertThat(beans.get(0).getName(), is("peeskillet"));
}
}