Webアプリケーションの管理画面などで画像ファイルなどをアップできるようにすれば管理が楽になります。

そのためには以下の2つのライブラリが必要になりますので入手して適切に設定してください。

https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload/1.5
https://mvnrepository.com/artifact/commons-io/commons-io/2.8.0

※commons-fileupload-1.4以前のバージョンには脆弱性が報告されています。(https://jvn.jp/jp/JVN89379547/)

まずは、1でファイルのみのアップロードを紹介します。なお、ファイルアップロードと通常のフォーム部品の入力データを同時にサーブレットに送信すると上手くいきません。そのような場合は、2,3の方法を試してみてください。

1.画像などのファイルのみをアップロードする

プロジェクトのイメージは以下になります。今回はimgフォルダに画像を格納する想定です。imgフォルダの絶対パスが必要になります。絶対パスはパッケージ・エクスプローラーで右クリックして「プロパティ」を選択して以下の画面から取得するのが良いでしょう。

HTMLファイルの例です

ポイントは

  • 9行目のenctype="multipart/form-data"とすることで入力されたデータをマルチパートデータとして送信します
  • 13行目のmultiple属性を付けることで複数ファイルの選択ができるようになります
<!DOCTYPE html>
<html>
    <head>
        <title>file-upload.html</title>
        <meta charset="UTF-8">
    </head>
    <body>
        <form action="/FileUpload/FileUpload" method="post" enctype="multipart/form-data">
            
            <input type="file" name ="file" multiple>
           
            <button type="submit">submit</button>
            
        </form>
    </body>
</html>

サーブレットの例です

ポイントは、

  • 24行目のServletFileUploadクラスのインスタンス生成は決まり文句です
  • 25行目では、複数ファイルを受け取れるようにFileItemのリストを宣言しています
  • 28行目では、ファイルのパスを絶対パスで指定します(存在しないフォルダを指定するとそのフォルダが作られます。ファイル名が重複しても上書きされないためにはユーザーIDなどの名前でフォルダを作成するとよいでしょう)
package fileUpload;

import java.io.File;
import java.io.IOException;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

@WebServlet(name = "FileUpload", urlPatterns = {
        "/FileUpload" })
public class FileUpload extends HttpServlet {

    protected static final String UPLOAD_PATH = "C:などドライブ文字から始まる絶対パスでimgフォルダを指定";

    @Override
    protected void doPost(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {

        try {
            ServletFileUpload sf = new ServletFileUpload(
                    new DiskFileItemFactory());
            List<FileItem> files = sf.parseRequest(request);

            for (FileItem fileItem : files) {
                String filename = fileItem.getName();
                File file = new File(UPLOAD_PATH, filename);
                fileItem.write(file);
                System.out
                        .println("ファイル名" + filename + "を");
                System.out.println(
                        file.getAbsolutePath() + "に保存しました");
            }

        } catch (FileUploadException ex) {
            System.err.println("アップロードのエラーです");
        } catch (Exception ex) {
            System.err.println("エラーです");
            System.err.println(ex.getStackTrace());
        }
    }
}

2.ファイルとフォーム部品の入力データを同時にサーブレットに送信する

以下のサンプルプログラムでは、最終的にファイルを格納するimgフォルダに加えて一時的にファイルを保存するtmpフォルダが必要になります。

tmpフォルダが必要

詳細についてはコメントを御覧ください。

HTMLファイルの例です

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
  <head>
    <title>マルチパートフォーム</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
  </head>
  <body>
  	<form action="/FileUpload/MultiPart" method="post" enctype="multipart/form-data">
		
			<p>名前<input type="text" name="name" value="" placeholder="入力してください"></p>
          
      <input type="file" name ="files" multiple>
     
      <button type="submit">submit</button>
          
    </form>
    
    <%-- 画像がアップロードされていれば表示 --%>
    <c:if test="${not empty name}">
      <p>前回の送信結果</p>
      <p>名前:${name}</p>
      <c:if test="${not empty list}">
	      <c:forEach items="${list}" var="fname">
	        <p><c:out value="${fname}" /></p>
	        <img src='${pageContext.request.contextPath += "/img/" += fname}' width="120">
	      </c:forEach>
	    </c:if>     
    </c:if>
  </body>
</html>

サーブレットの例です

package fileupload;

import java.io.IOException;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;

/**
 * 通常のテキストボックスなどを含む複数ファイルのPostサンプル
 * 
 * サーブレットのバージョンが3.0に上がったことで@MultipartConfigというアノテーションが追加されました
 * 参考URL:
 * https://spring.pleiades.io/specifications/platform/8/apidocs/javax/servlet/annotation/multipartconfig
 * 
 * @MultipartConfigに一時ファイルの保存先、最大ファイルサイズなどを設定することでmultipart-formを扱えるようになります
 * この実装法なら、外部jarを使わずに、同時にpostされた入力値もrequest.getParameterで取得できます
 * 
 * アノテーションには、一時ファイル格納先を絶対パスで指定してください
 * 設定値の説明
 * location 一時ファイル格納先(絶対パス)
 * maxFileSize アップロード可能なファイルの最大サイズ(byte) 当サンプルでは10Mバイトに設定しています 
 * 
 * 
 * ※ファイル操作について
 * 
 * リクエストからファイルの数だけPartというオブジェクトが取得できます
 * doPostが実行時には、すでに一時フォルダにファイルが保存されています(一時的なファイル名になっています)
 * Partから元のファイル名が取れますので、それを使って本番のフォルダにファイルを移動します
 * 
 * part.delete()で、一時ファイルを削除できます
 *
 */

/**
 * マルチパートリクエストで送信されたリクエストを処理するための定数を保持するクラス
 * サーブレットクラスの外にあるアノテーションに一時フォルダのパスを記述する必要があるので、
 * サーブレットクラス内の定数ではなく、別にクラスを定義し、定数を持たせる形に実装しています パスは各自の環境に合わせて変更してください
 */
final class Config {
	// 送信されたファイルを一時保存するディレクトリパス
	public static final String TMP = "C:\\pleiades\\2022-09\\workspace\\FileUpload\\resources\\tmp\\";

	// 送信されたファイルを正式に保存するディレクトリパス
	public static final String STORAGE = "C:\\pleiades\\2022-09\\workspace\\FileUpload\\resources\\img\\";

	// 送信可能ファイル最大サイズ(バイト)
	public static final int MAX_FSIZE = 104857600;

	// アップロードされた画像がTomcatに反映されるまでの待ち時間(ミリ秒)
	public static final int SLEEP_MILSEC = 3000;
}

@WebServlet(name = "MultiPart", urlPatterns = { "/MultiPart" })
@MultipartConfig(location = Config.TMP, maxFileSize = Config.MAX_FSIZE)
public class MultiPartServlet extends HttpServlet {

	/**
	 * ファイルアップロード画面表示アクション
	 */
	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		request.setCharacterEncoding("UTF-8");

		request.getRequestDispatcher("/index.jsp").forward(request, response);
	}

	/**
	 * 送信されたパラメータとファイルを処理するアクション Servlet3.0の機能を利用してマルチパートで送信されたパラメータとファイルを処理します
	 * 特にjarを追加せずとも、この機能は利用できます
	 * 
	 * ただし、アップロードされたファイルをTomcatが認識するまである程度の待ち時間が必要です
	 * よって、このサンプルでは結果表示の画面を表示するまで、3秒間の待ち時間を設定しています
	 */
	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		request.setCharacterEncoding("UTF-8");

		List<String> images = new ArrayList<String>();

		// postされたパラメータの確認
		String name = request.getParameter("name");
		System.out.println("テキストボックスの値は[" + name + "]です。");

		// multi-partリクエストからファイルを含むパーツを取得する
		for (Part part : request.getParts()) {

			// パーツの名前がpostされたfileタグのname属性と同じならファイルの処理を行う
			if (part.getName().equals("files")) {
				// 重複を回避するためのファイル名を取得
				String fname = makeUploadedFileName(part);

				// 一時ファイルをリネームして格納フォルダに保存する(実際は一時フォルダからの移動)
				String path = Config.STORAGE + fname;
				part.write(path);

				// 表示ファイル名をListに追加
				images.add(fname);

				// 一時ファイルを削除する
				// part.delete();

				System.out.println(path + "を保存しました。");
			}
		}

		// アップロードされた画像がサーバー(Tomcat)に反映されるまで待つ
		try {
			Thread.sleep(Config.SLEEP_MILSEC);
		} catch (InterruptedException e) {
			System.out.println("待ち時間中に割り込みが発生しました。");
		}

		// 結果表示用jspに渡す値をリクエスト属性に設定
		request.setAttribute("name", name);
		request.setAttribute("list", images);

		// 結果表示
		request.getRequestDispatcher("/index.jsp").forward(request, response);

	}

	/**
	 * アップロードされたファイル名に重複回避用の文字列を付与する 同名ファイルがミリ秒まで同時にアップロードされることなはい、
	 * という前提で上書を回避するためのファイル名を作成します
	 * 
	 * ファイル名は、 up年月日時分秒ミリ秒_元のファイル名 の形式で複製されます
	 * 
	 * @param Part p
	 * @return 現在日時(ミリ秒まで)を付与した重複アップロード回避用のファイル名
	 */
	private String makeUploadedFileName(Part p) {

		// 現在日時を取得
		LocalDateTime dt = LocalDateTime.now();
		String name = "up_" + dt.format(DateTimeFormatter.ofPattern("YYMMddHHmmssSSS_"));
		name += Paths.get(p.getSubmittedFileName()).getFileName().toString();

		return name;
	}
}

3.ファイルとフォーム部品の入力データを同時にサーブレットに送信し、別々のフォルダに格納する

以下のサンプルプログラムでは、最終的にファイルを格納するimgフォルダに加えて一時的にファイルを保存するtmpフォルダが必要になります。

tmpフォルダが必要

詳細についてはコメントを御覧ください。

HTMLファイルの例です

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
  <head>
    <title>ファイル種類選択</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
  </head>
  <body>
    <form action="/FileUpload/SelectFile" method="post" enctype="multipart/form-data">
    
      <p>名前<input type="text" name="name" value="" placeholder="入力してください"></p>
          
      <p>ファイル1<input type="file" name ="file1"></p>
      <p>ファイル2<input type="file" name ="file2"></p>
      <p>ファイル3<input type="file" name ="file3"></p>
     
      <button type="submit">submit</button>
          
    </form>
    
    <%-- 画像がアップロードされていれば表示 --%>
    <c:if test="${not empty name}">
      <p>前回の送信結果</p>
      <p>名前:${name}</p>
      <c:if test="${not empty list}">
        <c:forEach items="${list}" var="fname">
          <p><c:out value="${fname}" /></p>
          <img src='${pageContext.request.contextPath += "/img/" += fname}' width="120">
        </c:forEach>
      </c:if>     
    </c:if>
  </body>
</html>

サーブレットの例です

package fileupload;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;

/**
 * 画面にファイル送信ボタンが複数あり、送信するファイルを選べる場合のサンプルです
 * 各ボタンのname属性は、"file"+連番にしており、最初の4文字でファイルか否かを判定しています
 * ファイルが送信されているかは、Part.getSize()で、ファイルのサイズがゼロでなければ送信されたと判定しています
 */


@WebServlet(name = "SelectFile", urlPatterns = { "/SelectFile" })
@MultipartConfig(location =Config.TMP, maxFileSize = Config.MAX_FSIZE)
public class SelectFileServlet extends HttpServlet {

 
  /**
   * ファイルアップロード画面表示アクション
   */
  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
    
    request.setCharacterEncoding("UTF-8");
    
    request.getRequestDispatcher("/selfile.jsp").forward(request, response);
  }


  /**
   * 送信されたパラメータとファイルを処理するアクション
   * Servlet3.0の機能を利用してマルチパートで送信されたパラメータとファイルを処理します
   * 特にjarを追加せずとも、この機能は利用できます
   * 
   * ただし、アップロードされたファイルをTomcatが認識するまである程度の待ち時間が必要です
   * よって、このサンプルでは結果表示の画面を表示するまで、2秒間の待ち時間を設定しています
   */
  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {

    request.setCharacterEncoding("UTF-8");

    List<String> images = new ArrayList<String>();

    // postされたパラメータの確認
    String name = request.getParameter("name");
    System.out.println("テキストボックスの値は[" + name + "]です。");

    // multi-partリクエストからファイルを含むパーツを取得する
    for (Part part : request.getParts()) {

      // パーツの名前がpostされたfileタグのname属性と同じで、且つサイズがゼロでなければ、処理を行う
      if (part.getName().substring(0, 4).equals("file") && 0 < part.getSize()) {
        String fnum = part.getName().substring(4, 5);
        // 重複を回避するためのファイル名を取得
        String fname = makeUploadedFileName(part);

        // 一時ファイルをリネームして格納フォルダに保存する(実際は一時フォルダからの移動)
        String dir = String.format("%s%s", Config.STORAGE, fnum);
        
        // 保存先のディレクトリがなければ作成する
        Path p = Paths.get(dir);
        if (!Files.exists(p)) {
          try{
            Files.createDirectories(p);
          }catch(IOException e){
            System.out.println("保存先フォルダ作成に失敗しました。");
          }
        }
        
        // 一時ファイルをリネームして格納フォルダに保存する(実際は一時フォルダからの移動)
        String path = String.format("%s/%s", dir, fname);
        part.write(path);

        // 表示ファイル名をListに追加
        images.add(String.format("%s/%s", fnum, fname));

        // 一時ファイルを削除する
        //part.delete();

        System.out.println(path + "を保存しました。");
      }
    }
    
    // アップロードされた画像がサーバー(Tomcat)に反映されるまで待つ
    try {
      Thread.sleep(3000);
    } catch (InterruptedException e) {
      System.out.println("待ち時間中に割り込みが発生しました。");
    }

    // 結果表示用jspに渡す値をリクエスト属性に設定
    request.setAttribute("name", name);
    request.setAttribute("list", images);

    // 結果表示
    request.getRequestDispatcher("/selfile.jsp").forward(request, response);

  }
  

  /**
   * アップロードされたファイル名に重複回避用の文字列を付与する
   * 同名ファイルがミリ秒まで同時にアップロードされることなはい、
   * という前提で上書を回避するためのファイル名を作成します
   * 
   * ファイル名は、
   * up年月日時分秒ミリ秒_元のファイル名
   * の形式でふぁく精されます
   * 
   * @param Part p
   * @return 現在日時(ミリ秒まで)を付与した重複アップロード回避用のファイル名
   */
  private String makeUploadedFileName(Part p) {
    
    // 現在日時を取得
    LocalDateTime dt = LocalDateTime.now();
    String name =  "up_" + dt.format(DateTimeFormatter.ofPattern("YYMMddHHmmssSSS_"));
    name += Paths.get(p.getSubmittedFileName()).getFileName().toString();
    
    return name;
  }
}

最後までお読みいただきありがとうございます。