文件上传
文件上传使用的multipart,在之前介绍过。这里我们不采用xml配置,采用代码配置。之前我们采用的resolver为org.springframework.web.multipart.commons.CommonsMultipartResolver,API解析org.springframework.web.multipart.commons包是MultipartResolver implementation for Apache Commons FileUpload。本学习采用Servlet 3.1内置的multipart的支持,采用org.springframework.web.multipart.support.StandardServletMultipartResolver,这是Standard implementation of the MultipartResolver interface, based on the Servlet 3.0 Part API,因此我们无需引入commons-fileupload。
代码配置
(1) BootStrap:允许被设置multipart参数
public class BootStrap implements WebApplicationInitializer{
@Override
public void onStartup(ServletContext container) throws ServletException {
container.getServletRegistration("default").addMapping("/resource/*");
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(RootContextConfiguration.class);
container.addListener(new ContextLoaderListener(rootContext));
AnnotationConfigWebApplicationContext servletContext = new AnnotationConfigWebApplicationContext();
servletContext.register(ServletContextConfiguration.class);
ServletRegistration.Dynamic dispatcher = container.addServlet("springDispatcher", new DispatcherServlet(servletContext));
dispatcher.setLoadOnStartup(1);
// 【1】在DispatcherServlet中允许并设置multipart的参数。
dispatcher.setMultipartConfig(new MultipartConfigElement(
null /*location*/, 20_971_520L/*maxFileSize*/, 62_914_560L/*maxRequestSize*/, 512_000/*fileSizeThreshold*/));
dispatcher.addMapping("/");
// 【注】这里虽然和上传下载没有关系,但是如果我们采用代码设置配置,BootStrap是在web启动时运行,我们最好把所有的初始化的处理放在这里,而不是将其中一部分放入ServletContextListener,尽可能不要将初始化代码分在两个不同的地方。但是我们也注意到,WebApplicationInitializer是不提供web app退出的处理,所以ServletContextListener仍可能要用上。
initFilter(container);
}
private void initFilter(ServletContext container){
... ...
}
}
(2) ServletContextConfiguration:设置multipart resolver
@Configuration
@ComponentScan(basePackages = "cn.wei.flowingflying.customer_support.site",
useDefaultFilters = false,
includeFilters = @ComponentScan.Filter(Controller.class))
public class ServletContextConfiguration {
@Bean
public ViewResolver viewResolver(){
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setViewClass(JstlView.class);
resolver.setPrefix("/WEB-INF/jsp/view/");
resolver.setSuffix(".jsp");
return resolver;
}
@Bean
public RequestToViewNameTranslator viewNameTranslator(){
return new DefaultRequestToViewNameTranslator();
}
//【2】Spring兼容旧的Servlet API。在Servlet3.0之前,并没有内置multipart的支持,需使用第三方,为兼容,Spring需要设置MultipartResolver来明确采用内置的还是第三方的。
// 例子采用了自带的org.springframework.web.multipart.support.StandardServletMultipartResolver,不引入第三方。
@Bean
public MultipartResolver multipartResolver() {
return new StandardServletMultipartResolver();
}
}
jsp文件:add.jsp片段
既然学习了spring的form tag,我们就例子中使用。但是没有form:file,我们仍需要使用<input>的方式。
<form:form method="post" enctype="multipart/form-data" modelAttribute="ticketForm">
<form:label path="subject">Subject</form:label><br />
<form:input path="subject"/><br /><br />
<form:label path="body">Body</form:label><br />
<form:textarea path="body" rows="5" cols="30" /><br /><br />
<b>Attachments</b><br />
<input type="file" name="attachments" multiple="multiple"/><br /><br />
<input type="submit" value="Submit"/>
</form:form>
对应的java类Form
public class Form{
private String subject;
private String body;
private List<MultipartFile> attachments;
... getters & setters ...
}
Controller的代码片段
@RequestMapping(value = "create", method = RequestMethod.GET)
public String create(Map<String, Object> model){
model.put("ticketForm", new TicketController.Form());
return "ticket/add";
}
/* 1、ticket是业务对象,form是Form对象(输入对象),这两者有关联,但通常不一样。我们再三强调,我们应将业务逻辑和用户UI逻辑分离
* 2、从session中获取用户的登录账号
* 3、采用spring form tag可以将form对象和页面呈现进行双向转换,对于页面转为form对象,使用普通的input并无影响 */
@RequestMapping(value = "create", method = RequestMethod.POST)
public View create(HttpSession session,Form form) throws IOException{
Ticket ticket = new Ticket();
ticket.setId(getNextTicketId());
ticket.setCustomerName((String)session.getAttribute("username"));
ticket.setSubject(form.getSubject());
ticket.setBody(form.getBody());
ticket.setDateCreated(Instant.now());
for(MultipartFile filePart : form.getAttachments()){
logger.debug("Processing attachment for new ticket.");
Attachment attachment = new Attachment();
attachment.setName(filePart.getOriginalFilename());
attachment.setContents(filePart.getBytes());
attachment.setMimeContentType(filePart.getContentType());
if(attachment.getName()!= null && attachment.getName().length() > 0
&& attachment.getContents() != null && attachment.getContents().length > 0){
ticket.addAttachment(attachment);
}
}
this.ticketDatabase.put(ticket.getId(), ticket);
return new RedirectView("/ticket/view/" + ticket.getId(),true,false);
}
文件下载
文件下载使用自定义的view来进行处理。
自定义下载view:DownloadingView
public class DownloadingView implements View{
private final String filename;
private final String contentType;
private final byte[] contents;
public DownloadingView(String filename, String contentType,byte[] contents){
this.filename = filename;
this.contentType = contentType;
this.contents = contents;
}
@Override
public String getContentType() {
return this.contentType;
}
// render很有意思,参数包括servlet的request和response,以及controller的处理结果的数据存放model,可以根据model构造返回的http响应。
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception {
response.setHeader("Content-Disposition", "attachment; filename=" + this.filename);
response.setContentType("application/octet-stream");
ServletOutputStream stream = response.getOutputStream();
stream.write(this.contents);
}
}
Controller的代码片段
// 1、在url中采用正则表达式,.表示匹配除“\r\n”之外的任何单个字符;+表示匹配前面的子表达式一次或多次(大于等于1次)
// 2、判断如果{ticketId}无效,则跳转;进而如果{attachment}无效则跳转;最后进入自定义的view进行处理。
// 3、自定义的View在构造函数中已经填入所需的信息,不采用model进行数据传递,因此不使用ModelAndView,采用返回View的方式
@RequestMapping( value = "/{ticketId}/attachment/{attachment:.+}",
method = RequestMethod.GET )
public View download(@PathVariable("ticketId") long ticketId,
@PathVariable("attachment") String name){
Ticket ticket = ticketDatabase.get(ticketId);
if(ticket == null)
return getListRedirectView();
Attachment attachment = ticket.getAttachment(name);
if(attachment == null){
logger.info("Requested attachment {} not found on ticket {}.", name, ticketId);
return this.getListRedirectView();
}
return new DownloadingView(attachment.getName(),attachment.getMimeContentType(), attachment.getContents());
}
相关链接: 我的Professional Java for Web Applications相关文章