Java for Web学习笔记(六五):Controller替代Servlet(7)上传和下载(自定义View)

本文介绍如何在Spring MVC中实现文件上传与下载功能。通过代码配置multipart参数,并使用StandardServletMultipartResolver来处理文件上传。同时,还介绍了如何自定义view以实现文件下载。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

文件上传

文件上传使用的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相关文章
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值