Configuring Spring MVC controller to send file to client

后端 未结 2 543
暗喜
暗喜 2020-12-04 22:05

I think my scenario is pretty common. I have a database and I want my Spring MVC app to accept a request in the controller, invoke the DB service to get data an

相关标签:
2条回答
  • 2020-12-04 22:36

    Based on Pierre answer, i did a converter. Here is the full code, that works with any Object passed:

    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;
        }
    }
    

    And on SpringContext.xml add the following:

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

    So, you can use on your controller like this:

    @RequestMapping(value="/tsv", method= RequestMethod.GET, produces = "text/tsv")
        @ResponseBody
        public TsvResponse tsv() {
            return new TsvResponse(myListOfPojos, "fileName.tsv",
                    "Name", "Email", "Phone", "Mobile");
        }
    
    0 讨论(0)
  • 2020-12-04 23:01

    It seems to be because your Content-type is set incorrectly, it should be response.setContentType("text/csv;charset=utf-8") instead of response.setContentType("data:text/csv;charset=utf-8").

    Additionally, if you are using Spring 3, you should probably use a @ResponseBody HttpMessageConverter for code reuse. For example:

    • In the controller:

      @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");
      }
      
    • plus a simple 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();
         }
      }
      
    • and a simple object to bind everything together:

      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;
         }
      }
      
    0 讨论(0)
提交回复
热议问题