如何在生成文件时处理 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:/");
}
我实现了一个 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:/");
}