| sitelink1 | |
|---|---|
| sitelink2 | |
| sitelink3 | |
| sitelink4 | |
| sitelink5 | |
| sitelink6 | 
javax.servlet.http.HttpServlet과 jakarta.servlet.http.HttpServlet 중 어떤 패키지를 사용해야 할지 모르는 상황에서 코드 한 벌로 양쪽 WAS에서 동작하게 만들고 싶을 때, 현실적으로 가능한 방법은 다음과 같습니다:
✅ 해결 전략
???? 1. 빌드 타임에 결정 → 멀티 빌드 구조 사용
하나의 소스에서 빌드 시점에 어떤 WAS(또는 어떤 서블릿 API)를 대상으로 할지 결정하는 구조로 설계합니다.
???? 방법: Gradle의 sourceSet 또는 build variant 활용
???? 디렉토리 구조 예:
src/
  common/java/         ← 공통 로직
  javaee/java/         ← javax.servlet 기반 코드
  jakarta/java/        ← jakarta.servlet 기반 코드
???? build.gradle.kts 예:
val useJakarta = project.hasProperty("jakarta")
sourceSets {
    val main by getting {
        java {
            if (useJakarta) {
                setSrcDirs(listOf("src/common/java", "src/jakarta/java"))
            } else {
                setSrcDirs(listOf("src/common/java", "src/javaee/java"))
            }
        }
    }
}
dependencies {
    if (useJakarta) {
        implementation("jakarta.servlet:jakarta.servlet-api:5.0.0")
    } else {
        implementation("javax.servlet:javax.servlet-api:4.0.1")
    }
}
???? 실행 예:
# Java EE (javax.servlet) 빌드
./gradlew build
# Jakarta EE (jakarta.servlet) 빌드
./gradlew build -Pjakarta
???? 2. 동적 로딩 방식 (Class.forName + Reflection) [⚠️ 비추천]
Object servlet;
try {
    // 시도 1: jakarta.servlet
    Class<?> clazz = Class.forName("jakarta.servlet.http.HttpServlet");
    servlet = clazz.getDeclaredConstructor().newInstance();
} catch (ClassNotFoundException e) {
    // 시도 2: javax.servlet
    Class<?> clazz = Class.forName("javax.servlet.http.HttpServlet");
    servlet = clazz.getDeclaredConstructor().newInstance();
}
그러나
extends HttpServlet구문 자체는 컴파일 타임에 결정되므로, 실제 상속 구조를 동적으로 처리하는 건 불가능합니다. 위 방식은 서블릿 클래스를 감싸는 어댑터 객체 등에서 간접적으로 활용하는 형태에 한정됩니다.
???? 현실적으로는…
**"동일한 서블릿 클래스에서 두 패키지를 동시에 고려해 상속받는 것"**은 불가능합니다.
결국 컴파일 타임에 둘 중 하나를 선택하도록 구조를 만들고, WAS에 맞는 빌드를 수행하는 구조를 택하는 게 가장 현실적입니다.
javax.servlet.http.HttpServlet과 jakarta.servlet.http.HttpServlet 둘 다 지원하기 위한 상속 분리 구조 예제를 아래에 설명드릴게요.
???? 목표
- 
	공통 서블릿 로직은 중복 없이 유지하고, 
- 
	두 개의 WAS (Java EE, Jakarta EE) 모두에서 사용할 수 있도록 
- 
	각각 javax/jakarta패키지에 맞는 클래스를 제공
???? 디렉토리 구조
src/
  main/
    java/
      common/                   ← 공통 서블릿 로직
        MyServletLogic.java
      javaee/                   ← javax.servlet 전용 클래스
        JavaxServletWrapper.java
      jakartaee/                ← jakarta.servlet 전용 클래스
        JakartaServletWrapper.java
???? 공통 서블릿 로직 클래스 (common/MyServletLogic.java)
package common;
import java.io.IOException;
import java.io.PrintWriter;
public class MyServletLogic {
    public void handleRequest(Object request, Object response) {
        try {
            Object writer = response.getClass().getMethod("getWriter").invoke(response);
            writer.getClass().getMethod("println", String.class).invoke(writer, "Hello from common logic!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
???? javax.servlet 기반 서블릿 (javaee/JavaxServletWrapper.java)
package javaee;
import common.MyServletLogic;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JavaxServletWrapper extends HttpServlet {
    private final MyServletLogic logic = new MyServletLogic();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        logic.handleRequest(req, resp);
    }
}
???? jakarta.servlet 기반 서블릿 (jakartaee/JakartaServletWrapper.java)
package jakartaee;
import common.MyServletLogic;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JakartaServletWrapper extends HttpServlet {
    private final MyServletLogic logic = new MyServletLogic();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        logic.handleRequest(req, resp);
    }
}
???? Gradle 빌드 스크립트 예시 (build.gradle.kts)
val useJakarta = project.hasProperty("jakarta")
sourceSets["main"].java {
    setSrcDirs(
        listOf(
            "src/main/java/common",
            if (useJakarta) "src/main/java/jakartaee" else "src/main/java/javaee"
        )
    )
}
dependencies {
    if (useJakarta) {
        implementation("jakarta.servlet:jakarta.servlet-api:5.0.0")
    } else {
        implementation("javax.servlet:javax.servlet-api:4.0.1")
    }
}
빌드 명령
# javax.servlet 기반 빌드
./gradlew build
# jakarta.servlet 기반 빌드
./gradlew build -Pjakarta
✅ 요약
| 항목 | 설명 | 
|---|---|
| 공통 로직 | MyServletLogic클래스에 유지 | 
| Wrapper 클래스 | 각각 JavaxServletWrapper,JakartaServletWrapper로 나눔 | 
| 유연한 빌드 | Gradle 플래그에 따라 동적으로 패키지 선택 가능 | 
| 장점 | 코드 중복 방지, 유지 보수 용이 | 
GitHub 프로젝트 구조 예제를 아래와 같이 설계했습니다.
이 구조는 javax.servlet.http.HttpServlet 과 jakarta.servlet.http.HttpServlet 둘 다 사용할 수 있도록 상속 분리 방식으로 구성되어 있습니다:
dual-servlet-support/
├── build.gradle.kts
├── settings.gradle.kts
└── src/
    └── main/
        └── java/
            ├── common/
            │   └── MyServletLogic.java               // 실제 로직 구현
            ├── javaee/
            │   └── JavaxServletWrapper.java         // javax.servlet 기반 서블릿
            └── jakartaee/
                └── JakartaServletWrapper.java       // jakarta.servlet 기반 서블릿
 
							
