【问题标题】:Configuring Spring MVC controller to send file to client配置 Spring MVC 控制器以将文件发送到客户端
【发布时间】:2012-03-28 18:41:49
【问题描述】:

我认为我的情况很常见。我有一个数据库,我希望我的Spring MVC 应用程序接受控制器中的请求,调用 DB 服务来获取数据并将该数据作为 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<CompositeRequirement> 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();

};

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

【问题讨论】:

  • 发现问题。当响应数据已经采用正确的格式时,我试图处理它。当站点简单地引用到 getFullData.html 时,上面的代码有效。简而言之,这里没什么可看的......继续前进:):)

标签: java spring servlets file-io csv


【解决方案1】:

好像是因为你的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<CompositeRequirement> allRecords = compReqServ.getFullDataSet((String) session.getAttribute("currentProject"));
          return new CsvResponse(allRecords, "yourData.csv");
    }
    
  • 加上一个简单的HttpMessageConverter:

    public class CsvMessageConverter extends AbstractHttpMessageConverter<CsvResponse> {
       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<CompositeRequirement> 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<CompositeRequirement> records;
    
       public CsvResponse(List<CompositeRequirement> records, String filename) {
           this.records = records;
           this.filename = filename;
       }
       public String getFilename() {
           return filename;
       }
       public List<CompositeRequirement> getRecords() {
           return records;
       }
    }
    

【讨论】:

  • 不应该@RequestMapping 标签有produces = "text/csv" 而不是consumes?它正在向不使用它的客户端生成数据。
  • 我还需要为 CsvMessageConverter 配置一个 bean,例如&lt;annotation-driven&gt; &lt;message-converters&gt; &lt;beans:bean class="blah.blah.blah.export.CsvMessageConverter" /&gt; &lt;/message-converters&gt; &lt;/annotation-driven&gt;
  • googlers:CsVWriter 需要在消息转换器中关闭 - writer.close(),此外,您可能希望在 writeInternal 方法中向文件添加标题行。例如,在遍历 CompositeRequirement 对象列表之前,添加一行: writer.write("col1header,col2header,col3header");
  • 对于 CsvMessageConverter:导入 au.com.bytecode.opencsv.CSVWriter;导入 org.springframework.http.HttpInputMessage;导入 org.springframework.http.HttpOutputMessage;导入 org.springframework.http.MediaType;导入 org.springframework.http.converter.AbstractHttpMessageConverter;导入 org.springframework.http.converter.HttpMessageNotReadableException;导入 org.springframework.http.converter.HttpMessageNotWritableException;导入 java.io.IOException;导入 java.io.OutputStream;导入 java.io.OutputStreamWriter;导入 java.nio.charset.Charset;
  • 我已经发布了一个完整的答案,并通过一些反思来响应任何传递的对象。
【解决方案2】:

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

TsvMessageConverter.java

public class TsvMessageConverter extends AbstractHttpMessageConverter<TsvResponse> {

    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<? extends TsvResponse> 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<String> 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<String> getObjectGetters(TsvResponse response) {
        List<String> 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 上添加以下内容:

<mvc:annotation-driven>
        <mvc:message-converters register-defaults="true">
            <bean class="com.mypackage.TsvMessageConverter"/>
        </mvc:message-converters>
    </mvc:annotation-driven>

因此,您可以像这样在控制器上使用:

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

【讨论】:

    猜你喜欢
    • 2012-08-25
    • 2012-09-09
    • 2016-03-27
    • 2012-01-29
    • 2020-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多