Spring MVC: Jak zwrócić obraz w @ResponseBody?

142

Otrzymuję dane obrazu (jako byte[]) z DB. Jak zwrócić ten obraz w @ResponseBody?

EDYTOWAĆ

Zrobiłem to bez @ResponseBodyużycia HttpServletResponsejako parametru metody:

@RequestMapping("/photo1")
public void photo(HttpServletResponse response) throws IOException {
    response.setContentType("image/jpeg");
    InputStream in = servletContext.getResourceAsStream("/images/no_image.jpg");
    IOUtils.copy(in, response.getOutputStream());
}

Używanie @ResponseBodyz zarejestrowanym org.springframework.http.converter.ByteArrayHttpMessageConverterkonwerterem, jak powiedział @Sid, nie działa dla mnie :(.

@ResponseBody
@RequestMapping("/photo2")
public byte[] testphoto() throws IOException {
    InputStream in = servletContext.getResourceAsStream("/images/no_image.jpg");
    return IOUtils.toByteArray(in);
}
marioosh
źródło

Odpowiedzi:

97

jeśli używasz Spring w wersji 3.1 lub nowszej, możesz określić „produkuje” w @RequestMappingadnotacji. Przykład poniżej działa dla mnie po wyjęciu z pudełka. Nie ma potrzeby konwertera rejestrów ani niczego innego, jeśli masz włączoną funkcję Web Mvc ( @EnableWebMvc).

@ResponseBody
@RequestMapping(value = "/photo2", method = RequestMethod.GET, produces = MediaType.IMAGE_JPEG_VALUE)
public byte[] testphoto() throws IOException {
    InputStream in = servletContext.getResourceAsStream("/images/no_image.jpg");
    return IOUtils.toByteArray(in);
}
michal.kreuzman
źródło
78

W Spring 4.1 i nowszych możesz zwrócić prawie wszystko (na przykład obrazy, pliki PDF, dokumenty, pliki jar, zipy itp.) Po prostu bez żadnych dodatkowych zależności. Na przykład następująca może być metoda zwracania obrazu profilu użytkownika z MongoDB GridFS:

@RequestMapping(value = "user/avatar/{userId}", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<InputStreamResource> downloadUserAvatarImage(@PathVariable Long userId) {
    GridFSDBFile gridFsFile = fileService.findUserAccountAvatarById(userId);

    return ResponseEntity.ok()
            .contentLength(gridFsFile.getLength())
            .contentType(MediaType.parseMediaType(gridFsFile.getContentType()))
            .body(new InputStreamResource(gridFsFile.getInputStream()));
}

Warto zwrócić uwagę:

  • ResponseEntity z InputStreamResource jako typem zwracanym

  • Tworzenie stylu kreatora ResponseEntity

Dzięki tej metodzie nie musisz się martwić o automatyczne okablowanie w HttpServletResponse, wyrzucanie wyjątku IOException lub kopiowanie danych strumieniowych.

Jaymes Bearden
źródło
1
Powoduje to zgłoszenie następującego wyjątku, w jaki sposób serializujesz MyInputStream ?: Nie można zapisać zawartości: Nie znaleziono serializatora dla klasy com.mongodb.gridfs.GridFSDBFile $ MyInputStream
Nestor Ledon
Chociaż jest to głównie przykład tego, co mógłbyś zrobić, Mongo-Java-Driver 3.0.3 z GridFsDBFile.getInputStream () nie zwraca anonimowej klasy o nazwie MyInputStream. Sprawdziłbym wasze wersje - może aktualizację?
Jaymes Bearden
4
Podoba mi się, jak to przesyła plik strumieniowo, zamiast kopiować całość do pamięci. Zobacz także stackoverflow.com/questions/20333394/…
Wim Deblauwe
60

Oprócz zarejestrowania domeny ByteArrayHttpMessageConvertermożesz użyć ResponseEntityzamiast @ResponseBody. U mnie działa następujący kod:

@RequestMapping("/photo2")
public ResponseEntity<byte[]> testphoto() throws IOException {
    InputStream in = servletContext.getResourceAsStream("/images/no_image.jpg");

    final HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.IMAGE_PNG);

    return new ResponseEntity<byte[]>(IOUtils.toByteArray(in), headers, HttpStatus.CREATED);
}
Siggen
źródło
16

Używając Spring 3.1.x i 3.2.x, powinieneś to zrobić w następujący sposób:

Metoda kontrolera:

@RequestMapping("/photo2")
public @ResponseBody byte[] testphoto() throws IOException {
    InputStream in = servletContext.getResourceAsStream("/images/no_image.jpg");
    return IOUtils.toByteArray(in);
}

Oraz adnotacja mvc w pliku servlet-context.xml:

<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>
Peymankh
źródło
13

Oprócz kilku odpowiedzi tutaj kilka wskazówek (wiosna 4.1).

O ile nie masz żadnych konwerterów wiadomości skonfigurowanych w swoim WebMvcConfig, posiadanie ResponseEntitywewnątrz @ResponseBodydziała dobrze.

Jeśli tak, to znaczy masz MappingJackson2HttpMessageConverterskonfigurowany (jak ja) za pomocą ResponseEntityzwrotów a org.springframework.http.converter.HttpMessageNotWritableException.

Jedynym działającym rozwiązaniem w tym przypadku jest owinięcie byte[]w @ResponseBodynastępujący sposób:

@RequestMapping(value = "/get/image/{id}", method=RequestMethod.GET, produces = MediaType.IMAGE_PNG_VALUE)
public @ResponseBody byte[] showImageOnId(@PathVariable("id") String id) {
    byte[] b = whatEverMethodUsedToObtainBytes(id);
    return b;
}

W takim przypadku pamiętaj o prawidłowym skonfigurowaniu konwerterów komunikatów (i dodaj a ByteArrayHttpMessageConverer) w swoim WebMvcConfig, na przykład:

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

@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    converter.setObjectMapper(objectMapper);
    return converter;
}

@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;
}
s.ijpma
źródło
6

W kontekście aplikacji zadeklaruj AnnotationMethodHandlerAdapter i registerByteArrayHttpMessageConverter:

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
  <property name="messageConverters">
    <util:list>
      <bean id="byteArrayMessageConverter" class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
    </util:list>
  </property>
</bean> 

również w metodzie obsługi ustaw odpowiedni typ treści dla swojej odpowiedzi.

Sid
źródło
@jsinghfoss odnosi się do pierwszej odpowiedzi.
Peymankh
6
 @RequestMapping(value = "/get-image",method = RequestMethod.GET)
public ResponseEntity<byte[]> getImage() throws IOException {
    RandomAccessFile f = new RandomAccessFile("/home/vivex/apache-tomcat-7.0.59/tmpFiles/1.jpg", "r");
    byte[] b = new byte[(int)f.length()];
    f.readFully(b);
    final HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.IMAGE_PNG);


    return new ResponseEntity<byte[]>(b, headers, HttpStatus.CREATED);



}

Pracował dla mnie.

Vivex
źródło
5

Preferuję ten:

private ResourceLoader resourceLoader = new DefaultResourceLoader();

@ResponseBody
@RequestMapping(value = "/{id}",  produces = "image/bmp")
public Resource texture(@PathVariable("id") String id) {
    return resourceLoader.getResource("classpath:images/" + id + ".bmp");
}

Zmień typ mediów na jaki kiedykolwiek masz format obrazu.

Tarion
źródło
1
Dobre wezwanie ResourceLoader, ale utworzenie ścieżki z zewnętrznego wejścia, jak w twoim przykładzie, to zły pomysł: cwe.mitre.org/data/definitions/22.html
qerub
3

To dla mnie praca na Spring 4.

@RequestMapping(value = "/image/{id}", method = RequestMethod.GET)
public void findImage(@PathVariable("id") String id, HttpServletResponse resp){

        final Foto anafoto = <find object>
        resp.reset();
        resp.setContentType(MediaType.IMAGE_JPEG_VALUE);
        resp.setContentLength(anafoto.getImage().length);

        final BufferedInputStream in = new BufferedInputStream(new ByteArrayInputStream(anafoto.getImageInBytes()));

        try {
            FileCopyUtils.copy(in, resp.getOutputStream());
            resp.flushBuffer();
        } catch (final IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

}
Furetto
źródło
2

Żadna z odpowiedzi nie działała dla mnie, więc udało mi się to zrobić w ten sposób:

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType("your content type here"));
headers.set("Content-Disposition", "attachment; filename=fileName.jpg");
headers.setContentLength(fileContent.length);
return new ResponseEntity<>(fileContent, headers, HttpStatus.OK);

Ustawienie Content-Dispositionnagłówka Udało mi się pobrać plik z @ResponseBodyadnotacją dotyczącą mojej metody.

Paulius Matulionis
źródło
2

W odpowiedzi należy określić typ nośnika. Używam adnotacji @GetMapping z producentem = MediaType.IMAGE_JPEG_VALUE. @RequestMapping będzie działać tak samo.

@GetMapping(value="/current/chart",produces = MediaType.IMAGE_JPEG_VALUE)
@ResponseBody
public byte[] getChart() {
    return ...;
}

Bez typu multimediów trudno odgadnąć, co faktycznie jest zwracane (w tym każdy, kto czyta kod, przeglądarkę i oczywiście samą Spring). Bajt [] po prostu nie jest określony. Jedynym sposobem określenia typu multimediów na podstawie bajtu [] jest węszenie i zgadywanie.

Podanie typu multimediów to tylko najlepsza praktyka

coseos
źródło
U mnie działa w Spring Boot 2.x. Dziękuję za udostępnienie.
attacomsian
1

Oto jak robię to z Spring Boot i Guava:

@RequestMapping(value = "/getimage", method = RequestMethod.GET, produces = MediaType.IMAGE_JPEG_VALUE)
public void getImage( HttpServletResponse response ) throws IOException
{
    ByteStreams.copy( getClass().getResourceAsStream( "/preview-image.jpg" ), response.getOutputStream() );
}
Wim Deblauwe
źródło
0

Wiosną 4 jest to bardzo łatwe, nie trzeba wprowadzać żadnych zmian w fasoli. Zaznacz typ zwrotu tylko w @ResponseBody.

Przykład:-

@RequestMapping(value = "/image/{id}")
    public @ResponseBody
    byte[] showImage(@PathVariable Integer id) {
                 byte[] b;
        /* Do your logic and return 
               */
        return b;
    }
Ankit Katiyar
źródło
1
Problem polega na tym, że typ zawartości jest nieprawidłowo ustawiony.
ETL,
0

Myślę, że może potrzebujesz usługi do przechowywania przesyłania plików i pobierania tego pliku. Sprawdź więcej szczegółów tutaj

1) Utwórz usługę magazynu

@Service
public class StorageService {

Logger log = LoggerFactory.getLogger(this.getClass().getName());
private final Path rootLocation = Paths.get("upload-dir");

public void store(MultipartFile file) {
    try {
        Files.copy(file.getInputStream(), this.rootLocation.resolve(file.getOriginalFilename()));
    } catch (Exception e) {
        throw new RuntimeException("FAIL!");
    }
}

public Resource loadFile(String filename) {
    try {
        Path file = rootLocation.resolve(filename);
        Resource resource = new UrlResource(file.toUri());
        if (resource.exists() || resource.isReadable()) {
            return resource;
        } else {
            throw new RuntimeException("FAIL!");
        }
    } catch (MalformedURLException e) {
        throw new RuntimeException("FAIL!");
    }
}

public void deleteAll() {
    FileSystemUtils.deleteRecursively(rootLocation.toFile());
}

public void init() {
    try {
        Files.createDirectory(rootLocation);
    } catch (IOException e) {
        throw new RuntimeException("Could not initialize storage!");
    }
}
}

2) Utwórz Rest Controller, aby przesłać i pobrać plik

@Controller
public class UploadController {

@Autowired
StorageService storageService;

List<String> files = new ArrayList<String>();

@PostMapping("/post")
public ResponseEntity<String> handleFileUpload(@RequestParam("file") MultipartFile file) {
    String message = "";
    try {
        storageService.store(file);
        files.add(file.getOriginalFilename());

        message = "You successfully uploaded " + file.getOriginalFilename() + "!";
        return ResponseEntity.status(HttpStatus.OK).body(message);
    } catch (Exception e) {
        message = "FAIL to upload " + file.getOriginalFilename() + "!";
        return      ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(message);
    }
}

@GetMapping("/getallfiles")
public ResponseEntity<List<String>> getListFiles(Model model) {
    List<String> fileNames = files
            .stream().map(fileName -> MvcUriComponentsBuilder
                    .fromMethodName(UploadController.class, "getFile", fileName).build().toString())
            .collect(Collectors.toList());

    return ResponseEntity.ok().body(fileNames);
}

@GetMapping("/files/{filename:.+}")
@ResponseBody
public ResponseEntity<Resource> getFile(@PathVariable String filename) {
    Resource file = storageService.loadFile(filename);
    return ResponseEntity.ok()
            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getFilename() + "\"")
            .body(file);
}

}

Long Nguyen
źródło