如何在生成文件时处理 Spring 控制器上的异常

how to deal with exceptions on a Spring controller while generating a file

我实现了一个 Spring 控制器来生成 PDF 文件,使用我的服务层。问题是,当在执行该方法期间抛出某些异常时,即使我使用 try-catch 块获得异常,spring 也会尝试根据当前 URL 解析视图。

老实说,我不知道是什么导致了这种行为。

    @RequestMapping("/cliente")
    public void relatorioCliente(EdicaoMovimentacaoWrapper wrapper, @AuthenticationPrincipal Usuario usuario) {
        try {
            gerarReportService.relatorioParaCliente(wrapper.getContaId(), usuario);

        } catch (Exception e) {
            e.printStackTrace();
            // TODO alguma coisa
        }
    }

relatorioParaCliente 方法生成 PDF 并将其导出到响应的 OutputStream。

预期:异常被捕获,堆栈跟踪被打印,用户没有任何反应。 实际结果:Spring 将用户重定向到 views/cliente.jsp

更新

我尝试更改方法的 return 类型,现在看起来像这样:

    @RequestMapping("/cliente")
    public ModelAndView relatorioCliente(EdicaoCadastroMovimentacaoWrapper wrapper, @AuthenticationPrincipal Usuario usuario) {
        try {
            gerarReportService.relatorioParaCliente(wrapper.getContaId(), usuario);

        } catch (Exception e) {
            e.printStackTrace();
            return new ModelAndView("/contas/" + wrapper.getContaId());
        }
        return null;
    }

但这对我的代码没有影响。 我想这不会影响代码,因为在服务上使用了 outputStream。看看:

@Service
public class ExportarReportPdfService {

    @Autowired
    private HttpServletResponse res;

    public void exportar(List<JasperPrint> jasperPrintList) throws IOException {

        JRPdfExporter exporter = new JRPdfExporter();

        exporter.setExporterInput(SimpleExporterInput.getInstance(jasperPrintList));

        exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(res.getOutputStream()));

        SimplePdfExporterConfiguration configuration = new SimplePdfExporterConfiguration();

        configuration.setCreatingBatchModeBookmarks(true);

        exporter.setConfiguration(configuration);

        res.setContentType("application/x-pdf");
        res.setHeader("Content-disposition", "inline; filename=relatorio.pdf" );


        try {
            exporter.exportReport();
        } catch (Exception e) {
            e.printStackTrace();
            throw new CriacaoReportException("Erro ao exportar para PDF");
        } finally {
            OutputStream out = res.getOutputStream();
            out.flush();
            out.close();

        }
    }

这是我解决问题的方法:

我没有在服务上自动装配响应并从那里导出 pdf,而是生成一个 byteArray 并将其 return 发送到控制器:

@Service
public class ExportarReportPdfService {

    public byte[] exportar(List<JasperPrint> jasperPrintList)  {

        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        JRPdfExporter exporter = new JRPdfExporter();

        exporter.setExporterInput(SimpleExporterInput.getInstance(jasperPrintList));

        exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(outputStream));

        [OMITED CODE]

        try {
            exporter.exportReport();
            return outputStream.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
            throw new CriacaoReportException("Erro ao exportar para PDF");
        }
    }
}

并且控制器将响应作为 ResponseEntity 发送:

        @RequestMapping("/geral")
        public ResponseEntity<InputStreamResource> relatorioAdministrador(EdicaoCadastroMovimentacaoWrapper wrapper, @AuthenticationPrincipal Usuario usuario) {

            byte[] arquivo = gerarReportService.relatorioParaAdmin(wrapper.getContaId(), usuario);

            return ResponseEntity
                    .ok()
                    .contentLength(arquivo.length)
                    .contentType(MediaType.parseMediaType("application/pdf"))
                    .header("Content-Disposition", "attachment; filename=relatorio.pdf")
                    .body(new InputStreamResource(new ByteArrayInputStream(arquivo)));
        }


因此,如果发生任何异常,它将在 ExceptionHandler 中被捕获,正如@cmoetzing 在评论中解释的那样:

    @ExceptionHandler(CriacaoReportException.class)
    public ModelAndView trataGeracaoRelatorio(CriacaoReportException exception) {
        return new ModelAndView("redirect:/");
    }