📘 JSP와 Document 객체의 관계 정리


🧩 Document란?

서버(JSP)가 HTML + JavaScript 코드를 만들어 브라우저로 보내면,
브라우저는 HTML 문서를 DOM(Document Object Model) 구조로 메모리에 올립니다.

이때,
자바스크립트는 document 객체를 통해 이 DOM 구조에 접근할 수 있습니다.

💡 정리:
document는 자바스크립트가 HTML 구조에 접근하기 위해 필요한 객체입니다.


💻 JSP와 Document의 관계

구분설명
JSP 서버 측에서 실행되는 코드로, HTML을 생성하여 클라이언트(브라우저)에 전달
Document 클라이언트(브라우저)에서 실행되는 JavaScript 객체로, HTML 문서(DOM)에 접근할 때 사용

즉, JSP는 화면을 만드는 쪽(서버),
Document는 그 화면을 조작하는 쪽(클라이언트) 이라고 볼 수 있습니다.


📄 Document 예시

 
<form name="loginForm" id="loginForm"> <input type="text" name="userId" id="userId" value="limhyun"> <input type="password" name="password" id="password" value="1234"> <button type="button" onclick="document_test()">확인</button> </form>

🧠 사용 예시

 
function document_test() { // 방법 ① name 기반 접근 let id1 = document.loginForm.userId.value; // 방법 ② forms[] 배열 접근 let id2 = document.forms["loginForm"].elements["userId"].value; // 방법 ③ getElementById 접근 (요즘 방식) let id3 = document.getElementById("userId").value; console.log(id1, id2, id3); }

🔍 각 접근 방법 설명

✅ 방법 ① document.formName.inputName.value

  • 형식: document.formName.inputName.value
  • 의미: name="formName" 인 <form> 안에 있는
    name="inputName" 인 <input> 요소의 값을 가져온다.
  • 예시:
  •  
    document.loginForm.userId.value; // "limhyun"

✅ 방법 ② document.forms[] 배열 접근

  • 형식: document.forms["폼이름"].elements["입력요소이름"].value
  • 예시:
  •  
    document.forms["loginForm"].elements["userId"].value;
  • 명시적이고 안전한 방법.
    폼 이름이 중복되거나 DOM 구조가 복잡해도 정확하게 접근 가능.

✅ 방법 ③ document.getElementById("id")

  • 형식: document.getElementById("요소id").value
  • 예시:
  •  
    document.getElementById("userId").value;
  • 가장 일반적이고 표준적인 방식.
    현재는 이 방식이 권장됩니다.

🧾 정리 표

구분접근 방식특징
방법 ① document.formName.inputName.value 고전 방식, 빠르지만 name 중복 시 문제 가능
방법 ② document.forms["formName"].elements["inputName"].value 명시적이고 안정적
방법 ③ document.getElementById("id").value 현대적 표준 방식, 유지보수 용이

💬 결론

  • document 객체는 HTML 문서(DOM)에 접근하기 위한 자바스크립트의 시작점
  • JSP는 HTML을 만들어 브라우저로 보내는 서버 역할,
    Document는 그 HTML을 제어하는 클라이언트 역할
  • 최신 개발에서는 document.getElementById() 나 querySelector() 같은
    표준 DOM 접근 방식을 사용하는 것이 가장 바람직합니다.

📌 한 줄 요약

document는 브라우저가 HTML 문서를 메모리에 올려둔 “DOM”에 접근하기 위한 자바스크립트 객체이며,
JSP가 생성한 HTML을 제어·조작할 때 반드시 필요한 연결 다리입니다.


 

1. pom.xml에 의존성 추가하기

Eclipse에서 JWT(Json Web Token)를 사용하려면 jjwt 라이브러리를 의존성에 추가해야 한다.

<dependencies>
  <!-- JWT -->
  <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
  </dependency>

  <!-- Jackson (JSON 처리용) -->
  <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.2</version>
  </dependency>
</dependencies>

 

👉 주의: <dependencies>는 반드시 <build> 바깥, <project> 바로 아래에 위치해야 한다.  <build> 아래 있을 경우 Maven 프로젝트 갱신하여도 라이브러리가 다운받아지지 않는다.


2. Maven 프로젝트 갱신

pom.xml 수정 후에는 Eclipse에서 프로젝트 우클릭 → Maven → Update Project (Alt + F5) 를 눌러야 라이브러리가 다운로드 된다.

👉 주의: Update Project 후 라이브러리 다운받아졌는지 확인하기.


3. JWT 토큰 발급 코드

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;

public class JwtExample {
    public static void main(String[] args) {
        String secretKey = "mySecretKey";

        String jwt = Jwts.builder()
                .setSubject("test123")	// 토큰 발행 id
                .setIssuedAt(new Date())	//토큰 발급시간
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60)) // 1시간 만료
                .signWith(SignatureAlgorithm.HS256, secretKey.getBytes())
                .compact(); // ✅ compact() 호출해야 String 변환됨

        System.out.println("생성된 JWT: " + jwt);
    }
}
 
 
출력 결과 :
생성된 JWT : eyJhbGciOiJIUzI-------kzMzQ0Nzl9.p_qsN6mXOveSgksiscxX-xQdgJkPkE9sK31ZtCT5xlw​


4. JWT 토큰 검증하기

클라이언트가 보낸 JWT를 검증해서 위조 여부만료 여부를 체크합니다.

 
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;

public class JwtVerify {
    private static final String SECRET_KEY = "mySecretKey";

    public static void main(String[] args) {
        String token = "위에서 발급한 토큰 값";

        try {
            Claims claims = Jwts.parser()
                    .setSigningKey(SECRET_KEY.getBytes())
                    .parseClaimsJws(token)
                    .getBody();

            System.out.println("토큰 주체(subject): " + claims.getSubject());
            System.out.println("만료 시간(expiration): " + claims.getExpiration());
        } catch (Exception e) {
            System.out.println("토큰 검증 실패: " + e.getMessage());
        }
    }
}

 

출력 결과 :

토큰 주체(subject): test123
만료 시간(expiration): Thu Oct 02 00:40:30 KST 2025

5. 발생했던 에러와 해결 방법

(1) Type mismatch: cannot convert from JwtBuilder to String

  • 원인: compact() 호출을 빼먹어서 JwtBuilder 객체를 그대로 String 변수에 담으려 했음
  • 해결: 마지막에 .compact() 붙이기

(2) NoClassDefFoundError: com/fasterxml/jackson/core/JsonProcessingException

  • 원인: jjwt가 JSON 처리를 위해 Jackson 라이브러리를 필요로 하는데, 의존성을 추가하지 않음
  • 해결: jackson-databind (→ core, annotations 자동 포함) 추가

(3) NoClassDefFoundError: javax/xml/bind/DatatypeConverter

  • 원인:
    jjwt 0.9.1은 JDK 1.8을 기준으로 개발되었고, javax.xml.bind 패키지를 사용한다.
    JDK 11 이상에서는 JAXB(Java Architecture for XML Binding)가 JDK 기본에서 제거되었기 때문에 실행 시 오류가 발생한다.
    문제는, Eclipse에서 프로젝트 JRE를 1.8로 설정했음에도 불구하고 실제 실행 JVM은 17이었던 것.
  • 해결 과정

1) 프로젝트 JRE 버전 확인

  • 컴파일러 설정 확인
    • 프로젝트 우클릭 → Properties → Java Compiler
    • Enable project specific settings 체크
    • Compiler compliance level = 1.8
  • 빌드 경로 확인
    • 프로젝트 우클릭 → Properties → Java Build Path → Libraries
    • JRE System Library [JavaSE-1.8] 로 표시돼 있어야 함

👉 하지만 이 설정만으로는 “실행” JVM까지 1.8로 고정되는 게 아님을 알게 됨.


2) 실제 실행 JVM 확인

실제로 어떤 JVM으로 실행 중인지 코드로 확인:

System.out.println("Java version: " + System.getProperty("java.version"));
System.out.println("Java home: " + System.getProperty("java.home"));

 

출력 결과:

Java version: 17.0.12
Java home: C:\Program Files\Java\jdk-17

 

👉 Eclipse는 분명 1.8로 설정했는데도, 실행 JVM은 JDK 17을 사용하고 있었음.


3) 해결 방법

(a) Eclipse에 JDK 1.8 등록

  1. Window → Preferences → Java → Installed JREs
  2. Add... → Standard VM 선택
  3. JDK 1.8 설치 경로 지정
    (예: C:\Program Files\Java\jdk1.8.0_351)
  4. Finish 후 체크박스로 기본(Default) 설정

(b) 프로젝트에 JDK 1.8 적용

  1. 프로젝트 우클릭 → Properties
  2. Java Build Path → Libraries
  3. JRE System Library 선택 후 Edit
  4. Execution Environment → JavaSE-1.8 선택
  5. 방금 등록한 JDK 1.8이 연결돼야 함

(c) Run Configurations에서 실행 JRE 지정

  1. 상단 메뉴 → Run → Run Configurations…
  2. 해당 Java Application 선택
  3. JRE 탭 → Alternate JRE → jdk1.8 선택
  4. Apply 후 실행

✨ 정리

  • jjwt 0.9.1은 JDK 1.8 환경에서 쓰면 가장 편하다.
  • JDK 11 이상에서 쓰려면 JAXB 라이브러리 추가가 필요하다.
  • compact()를 반드시 호출해야 JWT 문자열이 만들어진다.
  • Eclipse에서 실제 실행 JVM 버전이 무엇인지 확인하는 습관을 들이면 디버깅이 훨씬 빨라진다.

 

@WebMvcTest 이란

- Spring Boot 테스트 어노테이션

- Sptring MVC 웹 계층의 테스트에 사용됨

- 서비스 계층, 데이터 액세스 계층, 외부 시스템과의 통신 등 다른 계층의 로직을 테스트하는 것이 아닌, 웹 계층의 로직만으로 테스트하려는 경우 사용

 

MockMvc 란

- HTTP 요청을 디스패처 서블릿에 전송하고 결과를 받아 테스트하는 데 사용됨

- API의 테스트 수행

- Spring MVC 구성요소를 사용하여 동작

- MockMvc 주입받아 사용할때 @WebMvcTest 어노테이션을 쓰지 않으면 "this.mockMvc" is null 오류 발생함

Cannot invoke "org.springframework.test.web.servlet.MockMvc.perform(org.springframework.test.web.servlet.RequestBuilder)" because "this.mockMvc" is null
java.lang.NullPointerException: Cannot invoke "org.springframework.test.web.servlet.MockMvc.perform(org.springframework.test.web.servlet.RequestBuilder)" because "this.mockMvc" is null

 

예제

@WebMvcTest
class PostControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    @DisplayName("/posts 요청시 Hello world를 출력한다.")
    void test() throws Exception {
        //expection
        mockMvc.perform(get("/posts"))
                .andExpect(status().isOk()) // 통신이 정상일때
                .andExpect(content().string("Hello World"))
                .andDo(print()); // 요청한 로그 보기 위해
    }
}

 

MockMvc.perform 메서드

- MockHttpServletRequestBuilder 객체를 인자로 받음

MockHttpServletRequestBuilder 객체는 API 호출을 나타내며, HTTP 메서드, URL, 요청 본문, 요청 해더 등을 설정 가능

 

 

 

 

[오류]

주문를 할 경우 아래와 같은 오류로  화면이 랜딩되지 않는 오류 발생

 

아래 더 오류를 보니 주문상태 값을 가져오지 못해 발생한 것을 알 수있었다

Caused by: org.attoparser.ParseException: Exception evaluating SpringEL expression: "T(jpabook.jpashop.domain.OrderStatus).values()" (template: "order/orderList" - line 16, col 33)
	at org.attoparser.MarkupParser.parseDocument(MarkupParser.java:393)
	at org.attoparser.MarkupParser.parse(MarkupParser.java:257)
	at org.thymeleaf.templateparser.markup.AbstractMarkupTemplateParser.parse(AbstractMarkupTemplateParser.java:230)
	... 48 more

 

 

 

[오류 원인]

강의에서 html 부분은 복붙하여 사용하여 그대로 나도 복붙해서 사용했는데 경로가 달라서 발생하는 문제였다

복붙을 하더라도 한 번 확인했어야 하는 부분인데 시간을 너무 많이 잡아 먹었다.

여기서 사용한 T연산자는 Thymeleaf의 표현식인데 이렇게 풀경로를 적어서 왜 values() 메소드를 호출했는지 모르겠다.

그래서 T연산자를 사용하지 않고 불러오도록 수정하려고 한다.

 

[코드 수정]

1.컨트롤러에서 모델에 Enum 값 추가

먼저, OrderController 컨트롤러에서 OrderStatus enum의 values() 메소드를 호출하여 모든 enum 값을 가져온 후, 이를 orderList.html 뷰로 전달하기 위해 모델에 추가

model.addAttribute("orderStatuses", OrderStatus.values());

 

 

 

 

2. 컨트롤러에서 모델에 추가된 orderStatuses를 Thymeleaf 템플릿에서 사용하여 옵션을 생성

T연산자사용한 부분을 지우고 아래와 같이 수정해주었다

<option th:each=
                "status : ${T(jpabook.jpashop2.domain.OrderStatus).values()}"

수정 전

status : ${orderStatuses}

 

수정 후

 

3. 정상작동

+ Recent posts