配置Spring MVC控制器将文件发送到客户端

我认为我的情景很常见。 我有一个数据库,我希望我的Spring MVC应用程序接受控制器中的请求,调用数据库服务来获取数据并将该数据作为CSV文件发送到客户端。 我正在使用此处的JavaCSV库来协助此过程: http : //sourceforge.net/projects/javacsv/

我发现了一些人做类似事情的例子,并拼凑出看似正确的东西。 但是,当我尝试这种方法时,并没有真正发生任何事情。

我认为将数据写入HttpServletResponse的outputStream就足够了,但显然,我遗漏了一些东西。

这是我的控制器代码:

 @RequestMapping(value="/getFullData.html", method = RequestMethod.GET) public void getFullData(HttpSession session, HttpServletRequest request, HttpServletResponse response) throws IOException{ List allRecords = compReqServ.getFullDataSet((String)session.getAttribute("currentProject")); response.setContentType("data:text/csv;charset=utf-8"); response.setHeader("Content-Disposition","attachment; filename=\yourData.csv\""); OutputStream resOs= response.getOutputStream(); OutputStream buffOs= new BufferedOutputStream(resOs); OutputStreamWriter outputwriter = new OutputStreamWriter(buffOs); CsvWriter writer = new CsvWriter(outputwriter, '\u0009'); for(int i=1;i <allRecords.size();i++){ CompositeRequirement aReq=allRecords.get(i); writer.write(aReq.toString()); } outputwriter.flush(); outputwriter.close(); }; 

我在这里错过了什么一步? 基本上,净效应是……没有。 我原本以为设置标题和内容类型会导致我的浏览器接收响应并触发文件下载操作。

这似乎是因为您的Content-type设置不正确,它应该是response.setContentType("text/csv;charset=utf-8")而不是response.setContentType("data:text/csv;charset=utf-8")

此外,如果您使用的是Spring 3,则应该使用@ResponseBody HttpMessageConverter进行代码重用。 例如:

  • 在控制器中:

     @RequestMapping(value = "/getFullData2.html", method = RequestMethod.GET, consumes = "text/csv") @ResponseBody // indicate to use a compatible HttpMessageConverter public CsvResponse getFullData(HttpSession session) throws IOException { List allRecords = compReqServ.getFullDataSet((String) session.getAttribute("currentProject")); return new CsvResponse(allRecords, "yourData.csv"); } 
  • 加上一个简单的HttpMessageConverter:

     public class CsvMessageConverter extends AbstractHttpMessageConverter { public static final MediaType MEDIA_TYPE = new MediaType("text", "csv", Charset.forName("utf-8")); public CsvMessageConverter() { super(MEDIA_TYPE); } protected boolean supports(Class clazz) { return CsvResponse.class.equals(clazz); } protected void writeInternal(CsvResponse response, HttpOutputMessage output) throws IOException, HttpMessageNotWritableException { output.getHeaders().setContentType(MEDIA_TYPE); output.getHeaders().set("Content-Disposition", "attachment; filename=\"" + response.getFilename() + "\""); OutputStream out = output.getBody(); CsvWriter writer = new CsvWriter(new OutputStreamWriter(out), '\u0009'); List allRecords = response.getRecords(); for (int i = 1; i < allRecords.size(); i++) { CompositeRequirement aReq = allRecords.get(i); writer.write(aReq.toString()); } writer.close(); } } 
  • 以及将所有内容绑定在一起的简单对象:

     public class CsvResponse { private final String filename; private final List records; public CsvResponse(List records, String filename) { this.records = records; this.filename = filename; } public String getFilename() { return filename; } public List getRecords() { return records; } } 

根据皮埃尔的回答,我做了一个转换器。 这是完整的代码,适用于传递的任何Object:

TsvMessageConverter.java

 public class TsvMessageConverter extends AbstractHttpMessageConverter { public static final MediaType MEDIA_TYPE = new MediaType("text", "tsv", Charset.forName("utf-8")); private static final Logger logger = LoggerFactory.getLogger(TsvMessageConverter.class); public TsvMessageConverter() { super(MEDIA_TYPE); } protected boolean supports(Class clazz) { return TsvResponse.class.equals(clazz); } @Override protected TsvResponse readInternal(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { return null; } protected void writeInternal(TsvResponse tsvResponse, HttpOutputMessage output) throws IOException, HttpMessageNotWritableException { output.getHeaders().setContentType(MEDIA_TYPE); output.getHeaders().set("Content-Disposition", "attachment; filename=\"" + tsvResponse.getFilename() + "\""); final OutputStream out = output.getBody(); writeColumnTitles(tsvResponse, out); if (tsvResponse.getRecords() != null && tsvResponse.getRecords().size() != 0) { writeRecords(tsvResponse, out); } out.close(); } private void writeRecords(TsvResponse response, OutputStream out) throws IOException { List getters = getObjectGetters(response); for (final Object record : response.getRecords()) { for (String getter : getters) { try { Method method = ReflectionUtils.findMethod(record.getClass(), getter); out.write(method.invoke(record).toString().getBytes(Charset.forName("utf-8"))); out.write('\t'); } catch (IllegalAccessException | InvocationTargetException e) { logger.error("Erro ao transformar em CSV", e); } } out.write('\n'); } } private List getObjectGetters(TsvResponse response) { List getters = new ArrayList<>(); for (Method method : ReflectionUtils.getAllDeclaredMethods(response.getRecords().get(0).getClass())) { String methodName = method.getName(); if (methodName.startsWith("get") && !methodName.equals("getClass")) { getters.add(methodName); } } sort(getters); return getters; } private void writeColumnTitles(TsvResponse response, OutputStream out) throws IOException { for (String columnTitle : response.getColumnTitles()) { out.write(columnTitle.getBytes()); out.write('\t'); } out.write('\n'); } } 

TsvResponse.java

 public class TsvResponse { private final String filename; private final List records; private final String[] columnTitles; public TsvResponse(List records, String filename, String ... columnTitles) { this.records = records; this.filename = filename; this.columnTitles = columnTitles; } public String getFilename() { return filename; } public List getRecords() { return records; } public String[] getColumnTitles() { return columnTitles; } } 

在SpringContext.xml上添加以下内容:

      

所以,你可以在控制器上使用这样的:

 @RequestMapping(value="/tsv", method= RequestMethod.GET, produces = "text/tsv") @ResponseBody public TsvResponse tsv() { return new TsvResponse(myListOfPojos, "fileName.tsv", "Name", "Email", "Phone", "Mobile"); }