Returning Image/Media Data with Spring MVC – 用Spring MVC返回图像/媒体数据

最后修改: 2016年 5月 17日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll illustrate how to return images and other media using the Spring MVC framework.

在本教程中,我们将说明如何使用Spring MVC框架返回图片和其他媒体。

We will discuss several approaches, starting from directly manipulating HttpServletResponse than moving to approaches that benefit from Message Conversion, Content Negotiation and Spring’s Resource abstraction. We’ll take a closer look on each of them and discuss their advantages and disadvantages.

我们将讨论几种方法,从直接操作HttpServletResponse开始,到从消息转换内容协商和Spring的Resource抽象中获益。我们将仔细研究它们中的每一个,并讨论其优点和缺点。

2. Using the HttpServletResponse

2.使用HttpServletResponse

The most basic approach of the image download is to directly work against a response object and mimic a pure Servlet implementation, and its demonstrated using the following snippet:

图片下载的最基本方法是直接针对响应对象工作,并模仿纯Servlet的实现,其演示使用以下片段。

@RequestMapping(value = "/image-manual-response", method = RequestMethod.GET)
public void getImageAsByteArray(HttpServletResponse response) throws IOException {
    InputStream in = servletContext.getResourceAsStream("/WEB-INF/images/image-example.jpg");
    response.setContentType(MediaType.IMAGE_JPEG_VALUE);
    IOUtils.copy(in, response.getOutputStream());
}

Issuing the following request will render the image in a browser:

发出以下请求将在浏览器中呈现图像。

http://localhost:8080/spring-mvc-xml/image-manual-response.jpg

The implementation is fairly straightforward and simple owing to IOUtils from the org.apache.commons.io package. However, the disadvantage of the approach is that it’s not robust against the potential changes. The mime type is hard-coded and the change of the conversion logic or externalizing the image location requires changes to the code.

由于来自org.apache.commons.io包的IOUtils,其实现相当直接和简单。然而,这种方法的缺点是它对潜在的变化不健全。mime类型是硬编码的,转换逻辑的改变或图像位置的外部化需要对代码进行修改。

The following section discusses a more flexible approach.

下一节讨论了一种更灵活的方法。

3. Using the HttpMessageConverter

3.使用HttpMessageConverter

The previous section discussed a basic approach that does not take advantage of the Message Conversion and Content Negotiation features of the Spring MVC Framework. To bootstrap these features we need to:

上一节讨论了一种基本的方法,它没有利用Spring MVC框架的消息转换内容协商功能。为了引导这些功能,我们需要。

  • Annotate the controller method with the @ResponseBody annotation
  • Register an appropriate message converter based on the return type of the controller method (ByteArrayHttpMessageConverter for example needed for correct conversion of bytes array to an image file)

3.1. Configuration

3.1.配置

For showcasing the configuration of the converters, we will use the built-in ByteArrayHttpMessageConverter that converts a message whenever a method returns the byte[] type.

为了展示转换器的配置,我们将使用内置的ByteArrayHttpMessageConverter,只要方法返回byte[]类型,就会转换一个消息。

The ByteArrayHttpMessageConverter is registered by default, but the configuration is analogous for any other built-in or custom converter.

ByteArrayHttpMessageConverter是默认注册的,但是对于任何其他内置或自定义的转换器来说,配置是类似的。

Applying the message converter bean requires registering an appropriate MessageConverter bean inside Spring MVC context and setting up media types that it should handle. You can define it via XML, using <mvc:message-converters> tag.

应用消息转换器Bean需要在Spring MVC上下文中注册一个适当的MessageConverterBean,并设置它应该处理的媒体类型。你可以通过XML定义它,使用<mvc:message-converters> tag。

This tag should be defined inside <mvc:annotation-driven> tag, like in the following example:

这个标签应该被定义在<mvc:annotation-driven>标签里面,就像下面的例子。

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter">
            <property name="supportedMediaTypes">
                <list>
                    <value>image/jpeg</value>
                    <value>image/png</value>
                </list>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

Aforementioned configuration part will register ByteArrayHttpMessageConverter for image/jpeg and image/png response content types. If <mvc:message-converters> tag is not present in the mvc configuration, then the default set of converters will be registered.

前面提到的配置部分将为ByteArrayHttpMessageConverter注册image/jpegimage/png响应内容类型。如果<mvc:message-converters>标签没有出现在mvc配置中,那么将注册默认的转换器集。

Also, you can register the message converter using Java configuration:

此外,你还可以使用Java配置注册消息转换器

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.add(byteArrayHttpMessageConverter());
}

@Bean
public ByteArrayHttpMessageConverter byteArrayHttpMessageConverter() {
    ByteArrayHttpMessageConverter arrayHttpMessageConverter = new ByteArrayHttpMessageConverter();
    arrayHttpMessageConverter.setSupportedMediaTypes(getSupportedMediaTypes());
    return arrayHttpMessageConverter;
}

private List<MediaType> getSupportedMediaTypes() {
    List<MediaType> list = new ArrayList<MediaType>();
    list.add(MediaType.IMAGE_JPEG);
    list.add(MediaType.IMAGE_PNG);
    list.add(MediaType.APPLICATION_OCTET_STREAM);
    return list;
}

3.2. Implementation

3.2.实施

Now we can implement our method that will handle requests for media. As it was mentioned above, you need to mark your controller method with the @ResponseBody annotation and use byte[] as the returning type:

现在我们可以实现我们的方法来处理媒体请求。如上所述,你需要用@ResponseBody注解来标记你的控制器方法,并使用byte[]作为返回类型。

@RequestMapping(value = "/image-byte-array", method = RequestMethod.GET)
public @ResponseBody byte[] getImageAsByteArray() throws IOException {
    InputStream in = servletContext.getResourceAsStream("/WEB-INF/images/image-example.jpg");
    return IOUtils.toByteArray(in);
}

To test the method, issue the following request in your browser:

为了测试这个方法,在你的浏览器中发出以下请求。

http://localhost:8080/spring-mvc-xml/image-byte-array.jpg

On the advantage side, the method knows nothing about the HttpServletResponse, the conversion process is highly configurable, ranging from using the available converters to specifying a custom one. The content type of the response does not have to be hard-coded rather it will be negotiated based on the request path suffix .jpg.

在优势方面,该方法对HttpServletResponse一无所知,转换过程是高度可配置的,从使用可用的转换器到指定一个自定义的转换器。响应的内容类型不需要硬编码,而是将根据请求路径的后缀.jpg进行协商。

The disadvantage of this approach is that you need to explicitly implement the logic for retrieving the image from a data source (local file, external storage, etc.) and you don’t have control over the headers or the status code of the response.

这种方法的缺点是,你需要明确地实现从数据源(本地文件、外部存储等)检索图像的逻辑,而且你不能控制响应的标题或状态代码。

4. Using the ResponseEntity Class

4.使用ResponseEntity

You can return an image as byte[] wrapped in the Response Entity. Spring MVC ResponseEntity enables control not only over the body of the HTTP Response but also the header and the response status code. Following this approach, you need to define the return type of the method as ResponseEntity<byte[]> and create returning ResponseEntity object in the method body.

你可以将图片作为byte[]包裹在Response Entity中返回。Spring MVC的ResponseEntity不仅可以控制HTTP响应的主体,还可以控制头和响应状态代码。按照这种方法,你需要将方法的返回类型定义为ResponseEntity<byte[]>,并在方法主体中创建返回ResponseEntity对象。

@RequestMapping(value = "/image-response-entity", method = RequestMethod.GET)
public ResponseEntity<byte[]> getImageAsResponseEntity() {
    HttpHeaders headers = new HttpHeaders();
    InputStream in = servletContext.getResourceAsStream("/WEB-INF/images/image-example.jpg");
    byte[] media = IOUtils.toByteArray(in);
    headers.setCacheControl(CacheControl.noCache().getHeaderValue());
    
    ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(media, headers, HttpStatus.OK);
    return responseEntity;
}

Using the ResponseEntity allows you to configure a response code for a given request.

使用ResponseEntity,您可以为给定的请求配置响应代码。

Explicitly setting the response code is especially useful in the face of an exceptional event e.g. if the image was not found (FileNotFoundException) or is corrupted (IOException). In these cases, all that is needed is setting the response code e.g. new ResponseEntity<>(null, headers, HttpStatus.NOT_FOUND), in an adequate catch block.

明确设置响应代码在面对特殊事件时特别有用,例如,如果没有找到图像(FileNotFoundException)或被破坏(IOException)。在这些情况下,所需要的是设置响应代码,例如new ResponseEntity<>(null, headers, HttpStatus.NOT_FOUND),在一个适当的catch块中。

In addition, if you need to set some specific headers in your response, this approach is more straightforward than setting headers by means of HttpServletResponse object that is accepted by the method as a parameter. It makes the method signature clear and focused.

此外,如果你需要在响应中设置一些特定的头信息,这种方法比通过被方法接受为参数的HttpServletResponse对象来设置头信息更直接。它使方法的签名清晰而集中。

5. Returning Image Using the Resource Class

5.使用Resource Class返回图像

Finally, you can return an image in the form of the Resource object.

最后,你可以以Resource对象的形式返回一个图像。

The Resource interface is an interface for abstracting access to low-level resources. It is introduced in Spring as a more capable replacement for the standard java.net.URL class. It allows easy access to different types of resources (local files, remote files, classpath resources) without the need to write a code that explicitly retrieves them.

Resource接口是一个用于抽象访问底层资源的接口。它被引入Spring,作为标准java.net.URL类的更有力的替代。它允许轻松访问不同类型的资源(本地文件、远程文件、classpath资源),而不需要编写明确的代码来检索它们。

To use this approach the return type of the method should be set to Resource and you need to annotate the method with the @ResponseBody annotation.

要使用这种方法,方法的返回类型应该被设置为Resource,并且你需要用@ResponseBody注解来注释该方法。

5.1. Implementation

5.1.实施

@ResponseBody
@RequestMapping(value = "/image-resource", method = RequestMethod.GET)
public Resource getImageAsResource() {
   return new ServletContextResource(servletContext, "/WEB-INF/images/image-example.jpg");
}

or, if we want more control over the response headers:

或者,如果我们想对响应头文件有更多的控制。

@RequestMapping(value = "/image-resource", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<Resource> getImageAsResource() {
    HttpHeaders headers = new HttpHeaders();
    Resource resource = 
      new ServletContextResource(servletContext, "/WEB-INF/images/image-example.jpg");
    return new ResponseEntity<>(resource, headers, HttpStatus.OK);
}

Using this approach, you treat images as resources that can be loaded using the ResourceLoader interface implementation. In such case, you abstract from the exact location of your image and ResourceLoader decides from where it is loaded.

使用这种方法,您将图像视为可使用ResourceLoader接口实现加载的资源。在这种情况下,你抽象出你的图像的确切位置,ResourceLoader决定从哪里加载。

It provides a common approach to control the location of images using the configuration, and eliminate the need for writing file loading code.

它提供了一个通用的方法,使用配置来控制图像的位置,并消除编写文件加载代码的需要。

6. Conclusion

6.结论

Among the aforementioned approaches, we started from the basic approach, then using the approach that benefits from the message conversion feature of the framework. We also discussed how to get the set the response code and response headers without handing the response object directly.

在上述方法中,我们从基本方法开始,然后使用受益于框架的消息转换功能的方法。我们还讨论了如何在不直接递送响应对象的情况下获得设置的响应代码和响应头信息。

Finally, we added flexibility from the image locations point of view, because where to retrieve an image from, is defined in the configuration that is easier to change on the fly.

最后,我们从图像位置的角度增加了灵活性,因为从哪里检索图像,是在配置中定义的,更容易即时改变。

Download an Image or a File with Spring explains how to achieve the same thing using Spring Boot.

Download an Image or a File with Spring解释了如何使用Spring Boot实现同样的事情。

The sample code following the tutorial is available at GitHub.

教程之后的示例代码可在GitHub上获得。