«1. Обзор

В этом руководстве мы покажем, как возвращать изображения и другие медиафайлы с помощью среды Spring MVC.

Мы обсудим несколько подходов, начиная с непосредственного манипулирования HttpServletResponse и заканчивая подходами, использующими преобразование сообщений, согласование содержимого и абстракцию ресурсов Spring. Мы подробно рассмотрим каждый из них и обсудим их преимущества и недостатки.

2. Использование HttpServletResponse

Самый простой подход к загрузке образа состоит в том, чтобы напрямую работать с объектом ответа и имитировать чистую реализацию сервлета, и это продемонстрировано с помощью следующего фрагмента:

@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());
}

следующий запрос отобразит изображение в браузере:

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

Реализация довольно прямолинейна и проста благодаря IOUtils из пакета org.apache.commons.io. Однако недостатком этого подхода является то, что он неустойчив к возможным изменениям. Тип пантомимы жестко закодирован, и изменение логики преобразования или экстернализация местоположения изображения требуют изменений в коде.

В следующем разделе обсуждается более гибкий подход.

3. Использование HttpMessageConverter

В предыдущем разделе обсуждался базовый подход, который не использует преимущества функций преобразования сообщений и согласования содержимого в Spring MVC Framework. Чтобы запустить эти функции, нам необходимо:

    Аннотировать метод контроллера аннотацией @ResponseBody Зарегистрировать соответствующий преобразователь сообщений на основе типа возвращаемого метода контроллера (например, ByteArrayHttpMessageConverter необходим для правильного преобразования массива байтов в файл изображения)

3.1. Конфигурация

Для демонстрации конфигурации преобразователей мы будем использовать встроенный ByteArrayHttpMessageConverter, который преобразует сообщение всякий раз, когда метод возвращает тип byte[].

ByteArrayHttpMessageConverter зарегистрирован по умолчанию, но конфигурация аналогична любому другому встроенному или пользовательскому конвертеру.

Применение bean-компонента преобразования сообщений требует регистрации соответствующего bean-компонента MessageConverter в контексте Spring MVC и настройки типов мультимедиа, которые он должен обрабатывать. Вы можете определить его через XML, используя тег \u003cmvc:message-converters\u003e.

Этот тег должен быть определен внутри тега \u003cmvc:annotation-driven\u003e, как в следующем примере:

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

Вышеупомянутая часть конфигурации регистрирует ByteArrayHttpMessageConverter для типов содержимого ответа image/jpeg и image/png. Если тег \u003cmvc:message-converters\u003e отсутствует в конфигурации mvc, будет зарегистрирован набор конвертеров по умолчанию.

Также вы можете зарегистрировать конвертер сообщений, используя конфигурацию 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. Реализация

Теперь мы можем реализовать наш метод, который будет обрабатывать запросы на медиа. Как было сказано выше, вам необходимо пометить метод вашего контроллера аннотацией @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);
}

Чтобы протестировать метод, выполните следующий запрос в браузере:

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

С другой стороны, метод ничего не знает о HttpServletResponse, процесс преобразования легко настраивается, начиная от использования доступных преобразователей и заканчивая указанием пользовательского. Тип содержимого ответа не обязательно должен быть жестко закодирован, он будет согласовываться на основе суффикса пути запроса .jpg.

Недостаток этого подхода в том, что вам нужно явно реализовать логику получения изображения из источника данных (локальный файл, внешнее хранилище и т. д.), и вы не имеете контроля над заголовками или кодом состояния ответ.

4. Использование класса ResponseEntity

Вы можете вернуть изображение как byte[], завернутое в сущность ответа. Spring MVC ResponseEntity позволяет управлять не только телом ответа HTTP, но также заголовком и кодом состояния ответа. Следуя этому подходу, вам необходимо определить возвращаемый тип метода как ResponseEntity\u003cbyte[]\u003e и создать возвращаемый объект 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;
}

«

Использование ResponseEntity позволяет настроить код ответа для данного запроса.

Явная установка кода ответа особенно полезна перед лицом исключительного события, например. если образ не найден (FileNotFoundException) или поврежден (IOException). В этих случаях все, что нужно, это установить код ответа, например. новый ResponseEntity\u003c\u003e(null, headers, HttpStatus.NOT_FOUND) в соответствующем блоке catch.

Кроме того, если вам нужно установить какие-то определенные заголовки в вашем ответе, этот подход более прост, чем установка заголовков с помощью объекта HttpServletResponse, который принимается методом в качестве параметра. Это делает сигнатуру метода четкой и целенаправленной.

5. Возврат изображения с помощью класса ресурсов

Наконец, вы можете вернуть изображение в форме объекта Resource.

Интерфейс ресурсов — это интерфейс для абстрагирования доступа к низкоуровневым ресурсам. Он представлен в Spring как более мощная замена стандартному классу java.net.URL. Он обеспечивает легкий доступ к различным типам ресурсов (локальным файлам, удаленным файлам, ресурсам пути к классам) без необходимости писать код, который явно извлекает их.

Чтобы использовать этот подход, тип возвращаемого значения метода должен быть установлен на Resource, и вам необходимо аннотировать метод аннотацией @ResponseBody.

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

5.1. Реализация

@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);
}

или, если мы хотим больше контролировать заголовки ответов:

Используя этот подход, вы рассматриваете изображения как ресурсы, которые можно загрузить с помощью реализации интерфейса ResourceLoader. В таком случае вы абстрагируетесь от точного местоположения вашего изображения, а ResourceLoader решает, откуда оно загружено.

Он обеспечивает общий подход к управлению расположением изображений с помощью конфигурации и устраняет необходимость написания кода для загрузки файлов.

6. Заключение

Среди вышеупомянутых подходов мы начали с базового подхода, а затем использовали подход, который выигрывает от функции преобразования сообщений платформы. Мы также обсудили, как получить набор кода ответа и заголовков ответа без прямой передачи объекта ответа.

Наконец, мы добавили гибкости с точки зрения расположения изображений, потому что то, откуда извлекать изображение, определяется в конфигурации, которую легче изменить на лету.

Загрузка изображения или файла с помощью Spring объясняет, как добиться того же с помощью Spring Boot.