Java Web開発における実践的ノウハウとトラブルシューティング

Tomcat環境下でのクラスパス問題の解決

カスタムTomcatを構築する際、NoClassDefFoundError: javax/servlet/http/HttpServletというエラーが発生することがある。この問題は、Mavenプロジェクトの実行時クラスパスにServlet APIのJARファイルが含まれていないことが原因である。開発環境ではTomcatサーバーが自動的にservlet-api.jarを提供するが、メインメソッドから直接起動する場合は明示的に依存関係を追加する必要がある。

<dependencies>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>servlet-api</artifactId>
        <version>2.5</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

スコープをprovidedに設定することで、コンパイル時には利用可能だが、最終的なWARファイルには含まれないようになり、デプロイ時の衝突を防ぐことができる。

リフレクションによる動的初期化の実装

XML設定ファイルを使用してコンテナを初期化する場合、DOM4Jライブラリを活用すると効率的である。以下はweb.xmlからservletマッピング情報を読み込む実装例である:

public void initializeContainers() {
    try {
        String configPath = getClass().getResource("/").getPath();
        SAXReader reader = new SAXReader();
        Document doc = reader.read(new File(configPath + "web.xml"));
        
        Element root = doc.getRootElement();
        List<Element> elements = root.elements();

        for (Element element : elements) {
            String name = element.getName();
            if ("servlet".equals(name)) {
                String servletName = element.elementText("servlet-name");
                String className = element.elementText("servlet-class");
                servletRegistry.put(servletName, 
                    (GenericServlet) Class.forName(className).newInstance());
            } else if ("servlet-mapping".equals(name)) {
                String servletName = element.elementText("servlet-name");
                String urlPattern = element.elementText("url-pattern");
                urlMapping.put(urlPattern, servletName);
            }
        }
    } catch (Exception e) {
        logger.error("コンテナ初期化失敗", e);
    }
}

ClassLoader.getResourceとClass.getResourceの違い

リソースパスの取得において、二つの方法の挙動に注意が必要である:

  • ClassLoader.getResource():相対パスを指定し、先頭にスラッシュを含めない
  • Class.getResource():絶対パスとして使用する場合、先頭にスラッシュを付ける
// 正しい使い方
ClassLoader loader = this.getClass().getClassLoader();
URL resource1 = loader.getResource("config/database.properties");

URL resource2 = this.getClass().getResource("/config/database.properties");

JSP開発における重要なポイント

EL式とJSTLタグのベストプラクティス

EL式では文字列連結に+演算子を使用できない点に注意が必要である。代わりにJSTLの<c:set>タグを使用する:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<base href="${contextPath}/" />

フォーム重送信防止パターン

POSTリクエスト後のページ更新による重複処理を防ぐため、PRG(Post-Redirect-Get)パターンを採用する:

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
    // 処理実行
    boolean success = processForm(req);
    
    if (success) {
        // リダイレクトでGETリクエストへ遷移
        resp.sendRedirect(req.getContextPath() + "/success.jsp");
    } else {
        req.getRequestDispatcher("/form.jsp").forward(req, resp);
    }
}

データベースアクセス層の設計原則

DAOとService層の分離は、単なるコーディング規約ではなく、将来的な拡張性を考慮した重要な設計決定である:

  • DAO層:特定のデータソースに対するCRUD操作を担当
  • Service層:ビジネスロジックや複数DAOの協調を管理

たとえ現在は単純な委譲であっても、Service層を設けることでトランザクション管理やセキュリティチェックなどの横断的関心事を適切に扱うことができる。

フィルターチェインの挙動理解

リクエストディスパッチに関する重要な知識:

  • リクエスト転送(Forward):フィルターを通過しない
  • リダイレクト(Redirect):新しいリクエストとしてフィルターを通過する

認証フィルターの実装では、この挙動を正しく理解していないと無限ループが発生する可能性がある。

AJAXリクエストの特別な考慮事項

AJAXリクエストに対するリダイレクトはクライアント側で処理できないため、特別な対応が必要である:

public boolean isAjaxRequest(HttpServletRequest request) {
    return "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
}

// AJAXリクエストの場合の処理
if (isAjaxRequest(request)) {
    response.setStatus(401);
    response.getWriter().write("{\"redirect\":\"/login.jsp\"}");
} else {
    response.sendRedirect("/login.jsp");
}

ファイルアップロードの実装ノウハウ

マルチパートフォームのデータ取得には特別な処理が必要である:

// 通常のgetParameter()では取得できない
String id = request.getParameter("id"); // nullになる

// 解決策:URLパラメータとして渡す
String id = request.getParameter("id"); // action URLに含める

また、ファイル入力要素の見た目を改善するため、CSSで透明度を0に設定し、他の要素と重ね合わせる手法が有効である。

タグ: JavaWeb Tomcat Servlet JSP EL表达式

6月30日 22:45 投稿