Pular para o conteúdo principal

Como fazer upload e download de arquivos usando Servlet e JSP

Este post vai apresentar como implementar um upload e download de arquivos usando Servlets, JSP, JSTL, EL e o padrão MVC.

Para este exemplo, vou criar um projeto web dinâmico no Eclipse. Eu não vou especificar um banco de dados, o código deve funcionar com qualquer banco relacional.

Para implementar o exemplo precisamos de quatro bibliotecas adicionais, sendo duas da Apache:
E para usar o JSTL precisamos de mais duas bibliotecas adicionais, a API e a implementação. Ambos podem ser encontrados no link:
Baixe todas as bibliotecas e adicione na pasta lib dentro de WebContent. A pasta lib também deve conter o conector para o banco de dados utilizado.

Upload

A página de envio deve conter um formulário com os campos necessários e mais um campo de input type file para o upload. No exemplo a seguir, vou mostrar o formulário com apenas dois campos: input type="file" e outro input type="text".

<form action="upload.do" enctype="multipart/form-data" method="post">
    <label for="desc">Descrição:</label>
    <input type="text" name="desc" id="desc"><br/>
    <label for="file">Arquivo:</label>
    <input type="file" name="file" id="file"><br/>
    <input type="submit" value="Enviar"/>
</form>

A servlet que vai receber os dados deste formulário deve conter o código:

public class UploadFileServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        DiskFileItemFactory dfif = new DiskFileItemFactory();
        ServletFileUpload sfu = new ServletFileUpload(dfif);

        if (!ServletFileUpload.isMultipartContent(req)) {
              // tratar erro
        }

        try {
            List items = sfu.parseRequest(req);
            //a posicao 0 corresponde ao primeiro campo input do formulario (descricao)
            FileItem descFI = (FileItem) items.get(0);
            String desc = descFI.getString();
            //a posicao 1 corresponde ao segundo campo input do formulario (arquivo)
            FileItem fileFI = (FileItem) items.get(1);
            byte[] bytes = read(fileFI);
            //Não é o File do Java. É um JavaBean apresentado a seguir
            FileBean filebean = new FileBean();                   
            filebean.setDescription(fileFI.getDesc());
            filebean.setName(fileFI.getName());
            filebean.setData(bytes);

            FileDao fdao = new FileDaoImpl();
            fdao.add(filebean);
        } catch (FileUploadException e) {
            // tratar erro
        }

        resp.sendRedirect("list.jsp");
    }

    private byte[] read(FileItem fi) throws IOException{
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        InputStream is = fi.getInputStream();
        int read = 0;
        final byte[] b = new byte[1024];

        while ((read = is.read(b)) != -1) {
            out.write(b, 0, read);
        }
        return out.toByteArray();
    }
}

Para verificar se um FileItem é um campo do comum formulário ou um campo (input) do tipo file, podemos usar o método: fileItem.isFormField().

Não se esqueça de mapear a sua servlet (UploadFileServlet)! Pode ser mapeada usando anotações (API Servlet 3.x) ou com o bom e velho deployment descriptor (web.xml). Segue o trecho do web.xml correspondente:


    <servlet>
        <servlet-name>UploadFileServlet</servlet-name>
        <servlet-class>controller.user.file.UploadFileServlet</servlet-class>
    </servlet>

   <servlet-mapping>
        <servlet-name>UploadFileServlet</servlet-name>
        <url-pattern>/upload.do</url-pattern>
    </servlet-mapping>

No código acima a classe da servlet se encontra no pacote controller.user.file , altere para o caminho correto dentro da sua aplicação.

A servlet UploadFileServlet faz uso da classe JavaBean FileBean, com o código:

public class FileBean {
    private String description;
    private String name;
    private Long id;
    private byte[] data;

    public FileBean() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public byte[] getData() {
        return data;
    }

    public void setData(byte[] data) {
        this.data = data;
    }
}

Para armazenar o código no banco de dados temos que ter uma tabela que corresponda ao arquivo. Essa tabela deve conter um varchar para o nome, outro para a descrição, um campo blob para o arquivo (bytes) e um campo id para identificar cada arquivo adicionado. Devemos setar esse último campo como autoincrement (MySQL).

O código da DAO que armazena os dados, lista e busca o JavaBean (FileBean) no banco de dados é:

     public boolean add(FileBean filebean) {
        int added = 0;
        try {
            Connection con = ConnectionFactory.createConnection();
            if (con != null) {
                String sql = "INSERT INTO file (name, description, data) VALUES (?, ?, ?)";
                PreparedStatement st = con.prepareStatement(sql);
                st.setString(1, filebean.getName());
                st.setString(2, filebean.getDescription());
                st.setBytes(3, filebean.getData());
                added = st.executeUpdate();
                st.close();
                con.close();
            }
        } catch (SQLException e) {
            //tratar erro
        }

        return added > 0;
    }

     public List list() {
        List files = new ArrayList();
        try {
            Connection con = ConnectionFactory.createConnection();
            if (con != null) {
                String sql = "SELECT * FROM file";
                Statement st = con.createStatement();
                ResultSet rs = st.execute(sql);

                while(rs.next()){
                    FileBean filebean = createFileBean(rs);
                    files.add(filebean);
                }

                st.close();
                con.close();
            }
        } catch (SQLException e) {
            //tratar erro
        }

        return files;
    }

    public FileBean getFile(long id) {
        FileBean filebean = null;
        try {
            Connection con = ConnectionFactory.createConnection();
            if (con != null) {
                String sql = "SELECT * FROM file WHERE id=?";
                PreparedStatement st = con.prepareStatement(sql);
                st.setLong(1, id);
                ResultSet rs = st.executeQuery();
                if (rs.next()) {
                    filebean = createFileBean(rs);
                }
                rs.close();
                st.close();
                con.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return file;
    }

    private FileBean createFileBean(ResultSet rs) throws SQLException {
        FileBean filebean = new FileBean();
        filebean.setData(rs.getBytes("data"));
        filebean.setId(rs.getLong("id"));
        filebean.setName(rs.getString("name"));
        filebean.setDescription(rs.getString("description"));

        return filebean;
    }

Os métodos da DAO fazem chamada a um método da classe ConnectionFactory que apenas cria e retorna uma conexão ao banco de dados. Daqui já podemos executar pra testar!

Download

Para o download vou mostrar um trecho de código de um JSP (list.jsp) que lista os arquivos do banco de dados e ao clicar sobre um arquivo o download é iniciado. Este código faz uso do JSTL e da EL. Se for uma imagem ele mostra um thumbnail ao invés do botão de download.


    <table>
        <thead>
            <tr>
                <th align="center"><b>Nome</b></th>
                <th align="center"><b>Descrição</b></th>
                <th align="center"><b>Arquivo</b></th>
            </tr>
        </thead>

        <tbody>
            <c:forEach var="file" items="${items}">
                <c:set value="${fn:endsWith(file.name, 'jpg') or fn:endsWith(file.name, 'png')}" var="isImage"/>
                <tr>
                    <td>${file.name}</td>
                    <td>${file.description}</td>
                    <c:if test="${isImage}">
                        <td><img width="50" height="50" src="download.do?id=${file.id}"/></td>
                    </c:if>
                    <c:if test="${not isImage}">
                        <td><a href="download.do?id=${file.id}">Baixar</a></td>
                    </c:if>
                </tr>
            </c:forEach>
        </tbody>
    </table>

A servlet que busca os arquivos na base de dados tem o código:

  public class ListFilesServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        FileDao dao = new FileDaoImpl();
        List<FileBean> files = dao.list();

        req.setAttribute("items", dao.list());
    
        RequestDispatcher rd = pReq.getRequestDispatcher("list.jsp");
        rd.forward(req, resp);
    }
}

A classe da servlet responsável pelo download tem o código:

public class DownloadFileServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        String idStr = req.getParameter("id");
        if(idStr == null){     
           //redirect to home or other
        }

        FileDao dao = new FileDaoImpl();
        Long id = Long.parseLong(idStr);
        FileBean filebean = dao.getFile(id);

        if (file != null) {
            ServletContext context = getServletConfig().getServletContext();
            String mimetype = context.getMimeType(filebean.getName());

            resp.setContentLength(filebean.getData().length);
            resp.setContentType(mimetype);//resp.setContentType("image/jpeg");

            resp.setHeader("Content-Disposition", "attachment; filename=\"" +    filebean.getName() + "\"");

            OutputStream out = resp.getOutputStream();
            InputStream in = new ByteArrayInputStream(filebean.getData());
            byte[] buffer = new byte[4096];
            int length;
            while ((length = in.read(buffer)) > 0) {
                out.write(buffer, 0, length);
            }
            in.close();
            out.flush();
        }
    }
}

Elas também devem ser mapeadas, tal como no exemplo a seguir:

<servlet>
        <servlet-name>DownloadFileServlet</servlet-name>
        <servlet-class>controller.user.file.DownloadFileServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>DownloadFileServlet</servlet-name>
        <url-pattern>/download.do</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>ListFilesServlet</servlet-name>
        <servlet-class>controller.user.file.ListFilesServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>ListFilesServlet</servlet-name>
        <url-pattern>/list.do</url-pattern>
    </servlet-mapping>

Acho que é isso. Se tiver algum erro, melhoria ou dificuldade na implementação é só avisar!

Happy coding!
[]`s

Comentários

  1. Olá muito boa sua explicação
    Porém estou com um problema, eu preciso usar no meu form um tipo hidden. E usando esse enctype eu não to conseguindo.
    Sabe algum jeito de resolver isso ?

    Desde já grato

    Marcos Fabris

    ResponderExcluir
    Respostas
    1. Marcos, fiz um teste aqui e verifiquei que funciona normalmente como se fosse um campo do tipo "text", como no exemplo da descrição. Acho que o problema deve ser outro.

      []s

      Excluir
  2. Olá Anderson, muito boa sua explicação, só não consegui entender como fazer o upload de arquivos grandes, como crio um buffer? teria algum material?

    Muito obrigado

    ResponderExcluir
    Respostas
    1. Alterei o código do exemplo para possibilitar o upload de arquivos de qualquer tamanho.

      []s

      Excluir
  3. Muito obrigado Anderson :)

    Helder Weber Gayer

    ResponderExcluir
  4. Consegui salvar a imagem no entanto não consigo a recuperar, no meu projeto tenho que usar jsp puro, não posso colocar jstl.

    Você pode me ajudar?

    ResponderExcluir
    Respostas
    1. Olá Douglas,

      Para fazer o download ou exibir a imagem sem o JSTL e EL é só trocar o código para um código com scripts (scriptlets, expressões, declarações JSP) correspondente. Por exemplo: a tag c:forEach deve ser trocada por um <% for(File file: request.getAttribute("items")) ... %>. Já a tag c:if deve ser trocada por um <% if( ... ) %> e assim por diante.

      []s

      Excluir
  5. Olá Anderson,
    Gostaria de uma dica se possivel, nosso sistema tem a missão de fazer upload de muitas imagens, e tambem ter uma campo de busca de imagens, essa busca de imagens seria assim, primeiro colocariamos o numero que faz referencia a um determinado registro e este registro iria puxar e exibir a imagem para o usuario, kara, são muitos registros e muitas imagens, muitas um numero que iria aumentando gradativamente todos os dias, e este servidor com as imagens seria acessado por varias maquinas, kara, vc tem alguma dica onde posso pesquisar algo do tipo, e qual processo de upload nao iria pesar o sistema? uma dica, por favor? Agradecido

    ResponderExcluir
    Respostas
    1. Olá Marcos,
      Não sei se entendi direito, mas o upload que eu mostrei vai enviar a imagem para o servidor e vai guardar no BD. É claro que depois de um tempo, o BD vai aumentando sim. Existe a possibilidade de enviar várias imagens de uma vez através do atributo multiple da tag input. Só que você tem que ler corretamente do lado servidor.
      Não entendi corretamente a parte do sistema com a busca de imagens. Preciso de mais informações, são muitos detalhes.

      Um abraço.

      Excluir

Postar um comentário

Postagens mais visitadas deste blog

Bye Bye JavaFX Scene Builder, Bem-vindo Gluon Scene Builder 8

Desde Java 8u40 a Oracle anunciou que o Scene Builder só será liberado como código-fonte dentro do projeto OpenJFX. A Oracle não irá mais fornecer os instaladores. O que é uma pena, era muito fácil instalar e trabalhar com Scene Builder especialmente para os novatos em JavaFX, mas o novo Gluon resolve este problema. Eles irão fornecer compilações do Scene Builder como antes, como instaladores para Max OS X, Linux, Windows ou simplesmente como um jar executável. O que significa para os desenvolvedores JavaFX e especialmente para iniciantes? Nada até agora. Você simplesmente tem que baixar página de Gluon Scene Builder em vez do Oracle. Todo o resto é a mesma coisa de antes. A atualização de Scene Builder 2.0 para o Gluon Scene Builder 8.0.0 é bastante fácil. Você tem que instalá-lo via .deb ou outro. Não importa se você já tem um instalação do Scene Builder em sua máquina. Ele simplesmente vai ser atualizado. Temos que agradecer a Gluon por fornecer e apoiar esta grande ferramen

Introdução ao Maven

Pessoal, acabei de adicionar duas apresentações no slideshare sobre Maven. Uma introdução ao Maven e outro falando um pouco de como usá-lo na prática. Só acessar: Introdução ao Maven e Maven na Prática . []s