1. Overview

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

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.

2. Using the 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:

@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.

The following section discusses a more flexible approach.

3. Using the 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:

  • 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

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.

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

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.

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

<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.

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

@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

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:

@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.

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

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.

@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.

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.

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.

5. Returning Image Using the Resource Class

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

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.

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.

5.1. Implementation

@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.

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

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.

The sample code following the tutorial is available at GitHub.


« 上一篇: Spring WebSockets 介绍