Spring 이론

웹 개발 방식

방식  설명  장점  단점  사용 예시
정적 콘텐츠 제공 서버에서 변하지 않는 정적 데이터를 제공 (HTML, CSS, JS 등). - 빠르게 응답 가능- 단순한 구조. - 동적인 데이터 제공 불가- 복잡한 로직 처리 불가. 단순 웹사이트, 빠르게 로드해야 하는 리소스.
MVC + 템플릿 엔진 MVC 패턴으로 데이터를 처리 후 템플릿 엔진을 이용해 동적으로 HTML 생성. - 클라이언트 로직 단순화- 서버에서 완성된 HTML 제공으로 개발 초기 비용 적음. - 서버 부하 증가 가능- 클라이언트-서버 독립성 낮음.- 다양한 플랫폼 대응 어려움. 중소규모 웹 애플리케이션.
API 방식 서버가 JSON 등의 데이터만 제공하고, 클라이언트가 이를 렌더링. - 클라이언트-서버 독립적 개발 가능- 동일한 API를 여러 플랫폼에서 재사용 가능- UI 동적 처리 용이. - 클라이언트 로직 복잡- 추가적인 프론트엔드 개발 필요. SPA, 모바일 앱, 프론트엔드 프레임워크 기반 앱.
 

[Spring] 스프링 웹 개발 기초

웹 개발 방식📌 스프링에서 웹 개발 기초는 크게 세 가지 방식으로 나뉩니다: 정적 콘텐츠 제공 방식, MVC와 템플릿 엔진 방식, 그리고 API 방식입니다. 각 방식이 웹 애플리케이션에서 데이터를

kyunghun0515.tistory.com

 

스프링 개발의 로드맵

📌 아래는 흔히 스프링 개발에서 사용하는 웹 개발 형식 중 MVC의 내부적 흐름을 표기한 그림이다.

상세 설명은 아래의 접은 글을 참조해 주세요

더보기
  1. 클라이언트 요청: 클라이언트가 서버에 요청을 보낸다.
  2. 디스패처 서블릿: 요청을 받아 적절한 핸들러를 찾고 실행한다.
  3. 핸들러 매핑: 요청 URL에 매핑된 컨트롤러를 찾는다.
  4. 핸들러 어댑터: 컨트롤러를 실행하는 방법을 정의한다.
  5. 컨트롤러: 요청을 처리하고 모델 데이터를 준비한다.
  6. 모델: 뷰에 전달될 데이터를 포함한다.
  7. 뷰 리졸버: 논리적 뷰 이름을 실제 뷰로 변환한다.
  8. 뷰: 모델 데이터를 기반으로 응답을 생성한다.
  9. 응답 반환: 생성된 응답이 클라이언트에게 반환한다

 

[ 참고 글 ]

 

요청이 왔을 때 스프링 MVC 내부적 흐름 과정 (DispatcherServlet 중심)

Spring MVC 구조 흐름스프링 MVC는 Model-View-Controller 디자인 패턴을 기반으로 하는 웹 프레임워크로, 웹 애플리케이션의 구성 요소를 명확히 분리하여 유지보수성과 확장성을 높인다.기능과 역할 별

programmer-may.tistory.com

 

위의 로드맵을 머리속에 그리고 아래의 글을 보면 이해가 쉽다.

 

전체 개발 단계 요약

  1. 기획 및 설계: 목표 정의, 와이어프레임 작성, 기술 스택 결정.
  2. 환경 설정: GitLab, AWS, 배포 환경 구성.
  3. CI/CD 구축: 자동화된 빌드, 테스트, 배포 설정.
  4. 프론트엔드 개발: React로 사용자 인터페이스 개발.
  5. 백엔드 개발: NestJS와 RDS MySQL로 서버 및 데이터베이스 구축.
  6. 코드 리뷰 및 협업: GitLab 머지 리퀘스트 및 이슈 관리.
  7. 테스트 및 최적화: 단위/통합 테스트, 성능 최적화.
  8. 출시 및 유지보수: 공식 릴리스 및 지속적 개선.

 

Spring Framework

📌 Spring Framework는 자바 기반의 애플리케이션 개발을 위한 오픈 소스 프레임워크로, 기업용 애플리케이션 개발에서 널리 사용됩니다. 경량 프레임워크로 시작했지만, 현재는 복잡한 애플리케이션 개발을 단순화하고 생산성을 높이는 강력한 생태계를 제공한다.

Referece of docs.spring.io

Spring Framework 주요 구성 요소

영역  설명 주요 구성 요소
Core Container - Spring의 기본 기능을 제공하는 모듈로, 애플리케이션의 IoC와 DI를 처리. - Beans: 애플리케이션 객체의 생성 및 관리.
- Core: IoC 컨테이너 기능 제공.
- Context: 애플리케이션 컨텍스트 제공.
- Expression Language: 런타임 시 값 조회와 조작.
AOP (Aspect-Oriented Programming) - 횡단 관심사(로깅, 트랜잭션, 보안 등)를 처리하기 위한 기능을 제공. - Aspects: 횡단 관심사를 모듈화하여 관리.
- Instrumentation: JVM에서 클래스 계층 구조를 변경 가능.
Data Access/Integration - 데이터 접근과 통합 기능을 제공. - JDBC: JDBC 사용을 단순화.
- ORM: JPA, Hibernate와 같은 ORM 지원.
- Transactions: 트랜잭션 관리.
- JMS: 메시지 서비스 지원.
- OXM: 객체-XML 매핑.
Web (MVC/Remoting) - 웹 애플리케이션 개발을 위한 MVC 구조 및 원격 호출(Remoting) 지원. - Web: Spring Web MVC 제공.
- Servlet: 서블릿 관련 요청 처리.
- Portlet: 포틀릿 기반 애플리케이션 지원.
- Struts: 기존 Struts 통합 지원.
Test - 애플리케이션 테스트를 위한 지원. - Spring 애플리케이션 테스트를 위한 다양한 도구 제공(JUnit, TestNG).

Spring Framework 구성 요소 설명

= 아래는 사전적인 정의로 이해하기 쉽게 아래 접은 글에 설명을 추가해 두었다.

  1. Core Container:
    • Core : Spring의 핵심 모듈로, IoC(Inversion of Control)와 DI(Dependency Injection)를 처리.
    • Beans: 객체의 생성, 초기화, 주입, 소멸 등을 관리.
    • Context: ApplicationContext와 같은 고급 IoC 기능 제공.
    • Expression Language: EL을 사용해 객체 속성에 동적으로 접근.
  2. AOP:
    • 애플리케이션에서 공통적으로 반복되는 로직(로깅, 보안 등)을 분리.
    • 코드 중복 없이 비즈니스 로직과 공통 로직을 분리 가능.
  3. Data Access/Integration:
    • 데이터 접근 및 통합 작업을 단순화.
    • JDBC와 ORM을 위한 템플릿 클래스 제공.
    • 트랜잭션 관리 기능을 제공하여 데이터 무결성을 유지.
  4. Web (MVC/Remoting):
    • Spring MVC를 사용해 RESTful API와 동적 웹 애플리케이션을 개발.
    • Servlet과 통합하여 요청 처리.
    • 기존 Struts 프레임워크와 통합 가능.
  5. Test:
    • JUnit, TestNG와의 통합으로 단위 테스트와 통합 테스트를 지원.
    • IoC 컨테이너를 사용하여 애플리케이션 컨텍스트를 로드하고 테스트.
더보기

Spring Framework 구성 요소의 일상적인 비유


1. Core Container

Core (IoC, DI):

  • 비유: 레스토랑의 주방장.
    • 주방장이 모든 요리를 직접 만들지 않고, 각각의 요리사(객체)들에게 필요한 재료(의존성)를 전달하고 일을 맡깁니다.
    • Spring은 객체(요리사)를 생성하고 의존성(재료)을 주입해줍니다. 개발자는 객체 생성에 신경 쓰지 않고 비즈니스 로직에만 집중할 수 있습니다.

Beans:

  • 비유: 레스토랑에서 요리 재료를 저장하는 창고.
    • 요리를 만들기 위해 필요한 재료들이 창고에 저장됩니다. Spring의 Beans는 애플리케이션에서 사용할 객체들을 생성하고 저장하는 역할을 합니다.

Context:

  • 비유: 레스토랑 매니저.
    • 매니저는 창고(Beans)에 어떤 재료(객체)가 어디에 있는지 알고 필요할 때 제공해줍니다.
    • ApplicationContext는 애플리케이션에서 필요한 모든 Bean 정보를 가지고 관리합니다.

Expression Language (EL):

  • 비유: 요리사가 레시피에 적힌 동적인 계산을 사용하는 상황.
    • "이 요리에는 현재 재고에 따라 고기를 200g에서 300g으로 조절해서 사용하세요."처럼 동적으로 데이터를 접근하는 방식입니다.
    • Spring의 Expression Language는 런타임에 데이터를 동적으로 가져오는 기능을 제공합니다.

2. AOP (Aspect-Oriented Programming)

  • 비유: 레스토랑의 청소 담당 직원.
    • 주방에서 요리를 하는 동안 청소 담당 직원은 요리사마다 반복적으로 발생하는 "쓰레기 처리", "바닥 닦기" 같은 공통 작업을 처리합니다.
    • Spring AOP는 비즈니스 로직(요리)에 영향을 주지 않으면서, 공통된 작업(로깅, 보안, 트랜잭션 관리)을 따로 처리합니다.

3. Data Access/Integration

JDBC와 ORM:

  • 비유: 음식 주문을 주방에 전달하고 결과를 받는 웨이터.
    • 고객(사용자)이 음식을 주문하면 웨이터가 이를 주방(데이터베이스)에 전달하고 음식을 받아옵니다.
    • JDBC(직접 주문서 작성)와 ORM(자동화된 시스템으로 주문서 작성)은 데이터베이스와 상호작용하는 방식입니다.

Transactions:

  • 비유: 음식 주문의 일괄 처리.
    • 고객이 여러 요리를 주문했을 때, "모든 요리가 준비되지 않으면 서빙하지 말아주세요."라는 요청처럼, 트랜잭션은 모든 작업이 성공해야만 최종적으로 완료되도록 보장합니다.

4. Web (MVC/Remoting)

Spring MVC:

  • 비유: 레스토랑의 주문 시스템.
    • Controller: 고객의 주문을 받는 직원.
    • Service: 주문서 작성과 요리 준비 과정을 처리.
    • Model: 요리 데이터를 담는 접시.
    • View: 고객이 요리를 보거나 먹는 것(화면에 출력).
    • Spring MVC는 이러한 "요청 → 처리 → 응답" 과정을 체계적으로 처리합니다.

Servlet:

  • 비유: 주문서 전달자.
    • Servlet은 레스토랑의 직원이 주문서를 주방으로 전달하는 역할처럼, 클라이언트 요청을 서버로 전달하는 일을 합니다.

Struts 통합:

  • 비유: 레스토랑에서 이전에 사용하던 오래된 POS 시스템과 새로운 POS 시스템을 통합.
    • 기존의 Struts 기반 시스템과 Spring을 함께 사용하도록 지원합니다.

5. Test

JUnit, TestNG 통합:

  • 비유: 요리사가 요리를 고객에게 내기 전에 맛을 보는 시식 과정.
    • JUnit과 TestNG는 "이 요리가 고객의 요구사항(요청)에 부합하는지"를 테스트하는 도구입니다.

IoC 컨테이너를 통한 테스트:

  • 비유: 주방에서 요리를 테스트하기 위해, 고객을 대신할 내부 테스터를 초대.
    • Spring IoC 컨테이너는 테스트 중에도 실제 애플리케이션처럼 객체를 주입하여 테스트 환경을 만듭니다.

요약 비유

구성 요소 비유
Core 레스토랑 주방장: 요리사들에게 필요한 재료와 역할을 배정.
Beans 재료 창고: 객체(재료)를 저장하고 관리.
Context 매니저: 필요한 객체를 제공하고 관리.
Expression Language 레시피의 동적 계산: 특정 조건에 따라 재료를 조절.
AOP 청소 담당 직원: 반복적인 작업(쓰레기 처리, 바닥 닦기 등)을 처리.
JDBC/ORM 웨이터: 주문을 전달하고 결과를 가져옴.
Transactions 음식 서빙 조건: 모든 요리가 준비되었을 때만 서빙.
Spring MVC 주문 시스템: 고객 주문(Controller), 요리 준비(Service), 요리(Model), 서빙(View).
Servlet 주문 전달자: 주문서를 주방으로 전달.
Test 시식 과정: 요리가 고객 요구사항에 맞는지 확인.

 

IoC와 DI

더보기

IoC (Inversion of Control)와 DI (Dependency Injection)의 개념과 비유


1. IoC (Inversion of Control, 제어의 역전)

개념

  • IoC는 객체의 생성과 관리를 개발자가 아닌 Spring 컨테이너가 담당하는 설계 원칙입니다.
  • 애플리케이션의 흐름 제어를 개발자가 아닌 **프레임워크(Spring)**가 제어하게 합니다.

비유

  • 비유: 요리사가 아닌 **주방장(매니저)**가 모든 요리사를 관리하는 상황.
    • 기존 방식: 요리사가 직접 재료를 구입하고 요리를 만들며 모든 과정을 제어.
    • IoC 방식: 주방장이 재료를 준비해주고 요리사에게 요리를 맡기는 방식. 요리사는 재료 준비에 신경 쓸 필요 없이 요리에만 집중.

2. DI (Dependency Injection, 의존성 주입)

개념

  • DI는 객체가 필요로 하는 의존성을 객체 내부에서 직접 생성하지 않고, 외부에서 주입해주는 방식입니다.
  • IoC의 구현 방법 중 하나로, 객체 간의 결합도를 낮추고 유연성을 높입니다.

비유

  • 비유: 요리사가 재료를 직접 준비하지 않고 **매니저(주방장)**가 필요한 재료를 가져다 주는 상황.
    • 기존 방식: 요리사가 요리마다 필요한 재료를 직접 슈퍼마켓에서 사와야 함.
    • DI 방식: 매니저가 요리사가 요청한 재료를 준비해 전달. 요리사는 요리(비즈니스 로직)에만 집중.

3. IoC와 DI를 연결하는 비유

비유 상황

  • 레스토랑 시스템:
    1. IoC:
      • 주방장이 전체 흐름을 제어(객체 생성, 관리).
      • 요리사(객체)는 주방장(Spring 컨테이너)에 의해 관리됨.
    2. DI:
      • 주방장이 요리사가 필요한 재료(의존성)를 제공(주입).

4. 예제 코드

IoC와 DI 적용 전 (전통적인 방식)

public class Chef {
    private Ingredient ingredient;

    public Chef() {
        this.ingredient = new Ingredient(); // 요리사가 직접 재료를 생성
    }

    public void cook() {
        System.out.println("Cooking with " + ingredient.getName());
    }
}

public class Ingredient {
    public String getName() {
        return "Tomatoes";
    }
}
  • 문제점:
    • Chef 클래스가 Ingredient 클래스를 직접 생성(강한 결합).
    • Ingredient를 변경하려면 Chef도 수정해야 함.

IoC와 DI 적용 후

public class Chef {
    private Ingredient ingredient;

    // 의존성 주입 (DI)
    public Chef(Ingredient ingredient) {
        this.ingredient = ingredient;
    }

    public void cook() {
        System.out.println("Cooking with " + ingredient.getName());
    }
}

public class Ingredient {
    public String getName() {
        return "Tomatoes";
    }
}

// Spring Configuration
@Configuration
public class AppConfig {
    @Bean
    public Ingredient ingredient() {
        return new Ingredient(); // 의존성 생성
    }

    @Bean
    public Chef chef(Ingredient ingredient) {
        return new Chef(ingredient); // 의존성 주입
    }
}
  • 장점:
    • Chef 클래스는 Ingredient에 대해 아무것도 몰라도 됨.
    • Ingredient를 변경해도 Chef 클래스는 수정할 필요가 없음(유연성 증가).

5. IoC와 DI 요약

개념 설명 비유
IoC 객체의 생성 및 생명주기 관리를 개발자가 아닌 Spring 컨테이너가 담당. 주방장이 전체 요리 과정을 관리하고 제어.
DI 객체가 필요한 의존성을 외부에서 주입받아 사용. 주방장이 요리사가 필요한 재료를 전달. 요리사는 요리에만 집중.
IoC + DI 결합 IoC로 객체를 관리하고, DI로 객체 간 의존성을 주입하여 객체 간 결합도를 낮춤. 주방장이 요리사(객체)를 관리하고, 필요한 재료(의존성)를 전달하여 효율적으로 관리.

6. 결론

  • IoC는 **"객체의 제어권을 개발자가 아닌 Spring 컨테이너에 위임"**하는 철학.
  • DI는 **"객체 간의 의존성을 외부에서 주입"**하여 유연성과 테스트 용이성을 높이는 구현 방식.
  • Spring은 IoC 컨테이너와 DI를 사용해 객체 생성을 관리하고, 의존성을 주입하여 효율적이고 유연한 애플리케이션 설계를 가능하게 합니다! 😊

AOP

더보기

Spring AOP 설명

Spring AOP는 **Aspect-Oriented Programming(AOP)**의 개념을 Spring Framework에 적용한 구현체로,
**횡단 관심사(Cross-Cutting Concerns)**[ 여러 곳에서 공통적으로 필요한 기능 ]를 깔끔하게 관리하고, 핵심 비즈니스 로직과 보조 로직을 분리할 수 있게 도와줍니다.

= AOP란 공통 기능을 한 곳에 정의하고 필요한 코드에 자동으로 적용하는 방법

Spring AOP는 프록시(Proxy) 기반으로 동작하며, 주로 메서드 실행에 Advice를 적용합니다.

추가로 bean에서만 적용됩니다.

 

 


Spring AOP 주요 개념

  1. Aspect
    • 횡단 관심사를 정의하는 모듈.
    • @Aspect를 사용하여 선언.
  2. Advice
    • Join Point에서 수행할 작업.
    • 종류: @Before, @After, @Around, @AfterReturning, @AfterThrowing.
  3. Pointcut
    • Advice가 적용될 Join Point를 정의.
    • 표현식으로 특정 메서드나 클래스 지정.
  4. Join Point
    • Advice가 적용 가능한 지점. (Spring AOP에서는 메서드 실행 지점만 Join Point로 지원)
  5. Weaving
    • Aspect를 애플리케이션 코드에 적용하는 과정.

Spring AOP 예시

상황: 애플리케이션에서 서비스 메서드 호출 시 실행 시간을 측정하려고 함.

1. Aspect 정의

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

@Aspect
@Component
public class ExecutionTimeAspect {

    @Around("execution(* com.example.service.*.*(..))")
    public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = joinPoint.proceed(); // 메서드 실행
        long endTime = System.currentTimeMillis();
        System.out.println("Execution time of " + joinPoint.getSignature() + ": " + (endTime - startTime) + " ms");
        return result;
    }
}

2. Pointcut 정의

@Around("execution(* com.example.service.*.*(..))")
  • com.example.service 패키지의 모든 메서드에 적용.

3. 서비스 클래스

@Service
public class ExampleService {
    public void exampleMethod() {
        // 비즈니스 로직
        System.out.println("Executing exampleMethod...");
    }
}

4. 결과

  • exampleMethod() 실행 시 실행 시간이 콘솔에 출력됨:
    Executing exampleMethod...
    Execution time of void com.example.service.ExampleService.exampleMethod(): 5 ms
    

일상적 예시

일상적 AOP: 운동 기록 관리

상황: 운동할 때 각 운동(달리기, 웨이트, 스트레칭)의 시작과 종료 시간을 기록.

  1. Join Point: 운동 시작/종료.
  2. Advice: 운동 시작 시 현재 시간 기록, 종료 시 운동 시간 계산.
  3. Aspect: "운동 기록 관리"라는 단위로 정의.

코드 흐름

  1. 운동(메서드) 시작 시 Before Advice로 현재 시간 기록.
  2. 운동 종료 시 After Advice로 총 운동 시간 계산.

Spring AOP 요약표

특징 설명
목적 횡단 관심사를 모듈화하여 코드 중복 제거 및 핵심 비즈니스 로직과 보조 로직 분리.
주요 개념 Aspect, Advice, Pointcut, Join Point, Weaving.
지원 Advice 종류 @Before, @After, @Around, @AfterReturning, @AfterThrowing.
작동 방식 프록시 기반으로 동작하며, 주로 메서드 실행 시 Advice를 적용.
장점 - 코드 중복 제거- 유지보수성 향상- 비즈니스 로직과 부가 로직 분리 가능.
단점 - Pointcut 표현식 학습 필요- 프록시 객체로 인해 디버깅이 어려울 수 있음.
사용 사례 - 로깅- 실행 시간 측정- 트랜잭션 관리- 보안 검증.
일상적 예시 - 운동 기록 관리- 일상 업무 단계에서 시간 추적- 반복적인 프로세스 자동화.

 


Spring 구조 흐름

  • Core Container는 Spring의 기반 기능을 제공.
  • AOP는 비즈니스 로직과 공통 로직을 분리.
  • Data Access/Integration은 데이터베이스와의 통신 및 메시지 처리를 단순화.
  • Web 모듈은 MVC를 지원하여 웹 애플리케이션 개발을 간소화.
  • Test는 Spring 애플리케이션의 안정성을 검증.

 

Spring Framework 특징

특징 설명
IoC (Inversion of Control) 객체 생성 및 의존성 관리(Dependency Injection)를 통해 애플리케이션의 결합도를 낮추고 유연성 제공
DI (Dependency Injection) 객체 간의 의존 관계를 자동으로 주입하여 코드의 유지보수성과 테스트 용이성 향상
AOP (Aspect-Oriented Programming) 공통 관심사를 분리하여 코드 재사용성을 높이고 비즈니스 로직과 분리된 처리 가능 (예: 트랜잭션 관리, 로깅)
모듈화 필요한 모듈만 선택적으로 사용할 수 있어 애플리케이션의 크기와 복잡도를 줄임
Spring MVC 웹 애플리케이션을 위한 모델-뷰-컨트롤러(MVC) 패턴 지원, RESTful API 개발 가능
Spring Boot 자동 설정과 내장 서버를 제공하여 설정을 간소화하고 독립 실행형 애플리케이션을 쉽게 만들 수 있음
트랜잭션 관리 선언적 트랜잭션 관리 지원, @Transactional 어노테이션을 통해 트랜잭션을 쉽게 처리
Spring Security 인증 및 권한 부여를 처리하며, 웹 애플리케이션의 보안을 쉽게 설정할 수 있음
Spring Data 데이터베이스와의 상호작용을 단순화하며, JPA, Hibernate와의 통합을 지원
Spring Batch 대용량 배치 작업 처리 지원, 데이터를 읽고 쓰는 배치 작업을 효율적으로 처리할 수 있음
Spring Cloud 마이크로서비스 아키텍처 지원, 서비스 디스커버리, API 게이트웨이 등 분산 시스템 구축에 필요한 도구 제공
강력한 커뮤니티 활발한 오픈소스 프로젝트로, 커뮤니티와의 상호작용을 통해 지속적으로 발전
유연성 다양한 기술과 쉽게 통합 가능, 애플리케이션 아키텍처에 맞게 확장 가능

 

스프링 컨테이너(Spring Container)와 스프링 빈(Spring Bean)

1. 스프링 컨테이너(Spring Container)란?

스프링 컨테이너는 스프링 프레임워크의 핵심 컴포넌트로, 애플리케이션에서 사용하는 객체(빈)를 생성, 관리, 의존성 주입, 생명 주기 관리를 수행하는 도구입니다.

 

+ 실제로 스프링 컨테이너는 위의 IOC와 DI 개념을 구현하는 도구이다.

스프링 컨테이너의 주요 역할

  1. 객체(스프링 빈) 생성:
    • 애플리케이션에서 필요한 객체를 생성합니다.
  2. 의존성 주입(DI):
    • 객체 간의 의존성을 설정하고 주입합니다.
  3. 객체 생명 주기 관리:
    • 객체의 생성, 초기화, 사용, 소멸 과정을 관리합니다.
  4. 빈의 스코프 설정:
    • 객체가 싱글톤인지, 매번 새로운 객체인지 스코프를 정의하고 관리합니다.

2. 스프링 빈(Spring Bean)이란?

스프링 컨테이너가 관리하는 객체를 **스프링 빈(Spring Bean)**이라고 합니다.

스프링 빈의 특징

  • 스프링 컨테이너에 의해 생성 및 관리됩니다.
  • 기본적으로 **싱글톤(하나의 인스턴스를 공유)**으로 관리됩니다.
  • 개발자가 빈을 정의하면, 컨테이너가 이를 관리하고, 의존성을 자동으로 주입합니다.
  • 스프링 빈은 보통 어노테이션(@Component, @Service, @Repository, @Controller) 또는 XML 설정으로 등록합니다.

 

[그래서 스프링 빈은 객체를 어떻게 관리할까?]

더보기

스프링 컨테이너가 **스프링 빈(Spring Bean)**을 관리한다는 것은 빈의 생성, 초기화, 의존성 주입, 스코프 관리, 그리고 소멸까지의 전체 생명 주기를 관리한다는 것을 의미합니다. 관리 과정은 다음과 같은 방식으로 이루어집니다.


1. 스프링 빈의 관리 과정 (생명 주기)

스프링 컨테이너가 스프링 빈을 관리하는 과정은 다음 단계를 따릅니다:

1) 빈 정의 (Bean Definition):

  • 개발자는 어노테이션(@Component, @Service, @Controller 등)이나 XML/Java 설정 파일을 통해 빈을 정의합니다.
  • 스프링 컨테이너는 애플리케이션 시작 시 이 정의를 읽어들여 빈 생성 규칙을 설정합니다.

2) 빈 생성 (Bean Creation):

  • 스프링 컨테이너는 빈 정의에 따라 빈 객체를 생성합니다.
  • 기본적으로 **싱글톤(Singleton)**으로 관리되며, 애플리케이션 컨텍스트 초기화 시 생성됩니다.

3) 의존성 주입 (Dependency Injection):

  • 빈이 생성된 후, 스프링 컨테이너는 해당 빈이 필요로 하는 의존성을 주입합니다.
  • 생성자 주입, 세터 주입, 필드 주입 중 하나의 방식으로 주입됩니다.
    • : @Autowired, @Value, @Qualifier 등을 사용.

4) 초기화 (Initialization):

  • 빈 생성 후 초기화 메서드(예: @PostConstruct 또는 init-method)를 호출하여 필요한 설정을 수행합니다.
  • 개발자가 정의한 초기화 로직이 실행됩니다.

5) 사용 (Usage):

  • 빈은 애플리케이션 내에서 사용되며, 컨테이너가 이를 제공하고 호출하는 역할을 합니다.

6) 소멸 (Destruction):

  • 애플리케이션 종료 시, 컨테이너는 빈을 제거하기 전에 소멸 메서드(예: @PreDestroy 또는 destroy-method)를 호출합니다.

2. 스프링 빈의 관리 요소

1) 빈 스코프 관리

스프링 빈은 **스코프(Scope)**를 통해 객체의 생명 주기를 정의합니다.
빈의 스코프는 객체가 생성되고 유지되는 범위를 의미합니다.

  • 싱글톤(Singleton):
    • 컨테이너 당 하나의 인스턴스만 생성 (기본값).
    • 모든 요청에서 같은 객체를 공유합니다.
  • 프로토타입(Prototype):
    • 요청할 때마다 새로운 객체 생성.
  • 요청(Request):
    • 웹 애플리케이션에서 HTTP 요청마다 새로운 빈 생성.
  • 세션(Session):
    • 웹 애플리케이션에서 HTTP 세션마다 새로운 빈 생성.

2) 의존성 관리

스프링 컨테이너는 빈이 필요로 하는 의존성을 자동으로 관리하고 주입합니다.

  • 예제: 생성자 주입
@Component
class NotificationService {
    private final MessageService messageService;

    @Autowired
    public NotificationService(MessageService messageService) {
        this.messageService = messageService; // 의존성 주입
    }

    public void notify(String message) {
        messageService.sendMessage(message);
    }
}
  • 예제: 세터 주입
@Component
class NotificationService {
    private MessageService messageService;

    @Autowired
    public void setMessageService(MessageService messageService) {
        this.messageService = messageService; // 의존성 주입
    }

    public void notify(String message) {
        messageService.sendMessage(message);
    }
}

3. 코드 예제로 이해

스프링 빈의 생명 주기

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

@Component
class ExampleBean implements InitializingBean, DisposableBean {
    // 빈 생성자
    public ExampleBean() {
        System.out.println("1. 빈 생성");
    }

    // 의존성 주입 후 호출
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("2. 빈 초기화");
    }

    // 빈 소멸 전 호출
    @Override
    public void destroy() throws Exception {
        System.out.println("3. 빈 소멸");
    }
}

// 메인 클래스
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        // 컨테이너 생성 및 빈 관리 시작
        var context = new AnnotationConfigApplicationContext(AppConfig.class);

        // ExampleBean 사용
        ExampleBean exampleBean = context.getBean(ExampleBean.class);

        // 컨테이너 종료 및 빈 소멸
        context.close();
    }
}

실행 결과:

1. 빈 생성
2. 빈 초기화
3. 빈 소멸

4. 일상적인 비유

스프링 컨테이너와 빈 관리의 비유

  1. 스프링 컨테이너: 레스토랑의 매니저
    • 메뉴와 재료를 정의하고, 주방에서 요리가 준비되도록 관리.
    • 각 요리(빈)를 주문받아 제공.
  2. 스프링 빈: 레스토랑의 요리
    • 매니저가 주방에서 요리를 준비하고, 고객이 주문하면 제공합니다.
    • 요청마다 같은 요리를 재사용할 수도 있고(싱글톤), 매번 새롭게 준비할 수도 있습니다(프로토타입).

5. 정리 표

  스프링 컨테이너 스프링 빈
정의 객체(스프링 빈)의 생성, 초기화, 의존성 주입, 생명 주기를 관리하는 도구. 스프링 컨테이너에 의해 관리되는 객체.
역할 빈 생성, 의존성 주입, 스코프 관리, 생명 주기 관리. 실제 애플리케이션에서 사용되는 객체.
생성 시점 애플리케이션 시작 시 초기화 (스프링 컨테이너 초기화). 스프링 컨테이너에 의해 정의된 설정에 따라 생성.
소멸 시점 애플리케이션 종료 시 빈 소멸. 빈의 소멸 메서드 호출 후 컨테이너에서 제거.
관리 범위 애플리케이션 전체의 객체 관리. 개별 빈 단위 관리.
스코프 싱글톤, 프로토타입, 요청, 세션 등 관리. 컨테이너의 설정에 따라 하나의 객체 또는 여러 객체로 생성.

6. 결론

  • 스프링 컨테이너는 빈의 생성부터 소멸까지 전체 생명 주기를 관리하며, 개발자는 객체 생성과 의존성 설정에 대한 걱정을 줄이고 비즈니스 로직에 집중할 수 있습니다.
  • 스프링 빈은 컨테이너에서 관리되는 객체로, 요청에 따라 필요한 의존성을 주입받고, 컨테이너의 설정에 따라 재사용되거나 새로 생성됩니다.
  • 스프링의 이러한 관리 방식은 객체 간 결합도를 줄이고, 코드의 유연성과 유지보수성을 높이는 데 큰 역할을 합니다.

 


3. 스프링 컨테이너와 스프링 빈의 관계

  • 스프링 컨테이너는 스프링 빈을 관리하는 도구입니다.
  • 개발자는 스프링 컨테이너에 객체(빈)를 등록하고, 컨테이너는 이 빈을 생성하고 관리합니다.
  • 스프링 컨테이너는 애플리케이션 실행 시 빈을 생성하고, 의존성을 주입하며, 생명 주기를 관리합니다.

4. 일상적인 예시

1) 스프링 컨테이너와 스프링 빈

  • 스프링 컨테이너: **관리인(컨테이너)**이 빵집의 모든 직원과 재료를 관리.
  • 스프링 빈: 관리인이 관리하는 **직원(객체)**과 재료(의존성).

비유

  • 빵집의 모든 직원은 관리인의 관리하에 빵을 만듭니다.
  • 직원들 간의 의존성(예: 반죽 -> 오븐 -> 포장)은 관리인이 연결해줍니다.
  • 관리인이 직원의 고용, 교체, 해고(생명 주기)를 관리합니다.

5. 코드 예제

1) 스프링 컨테이너와 빈 등록

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Component;

// 메시지 서비스 인터페이스
interface MessageService {
    void sendMessage(String message);
}

// EmailService 구현체
@Component
class EmailService implements MessageService {
    public void sendMessage(String message) {
        System.out.println("Sending Email: " + message);
    }
}

// NotificationService 클래스
@Component
class NotificationService {
    private final MessageService messageService;

    // 생성자를 통한 의존성 주입
    public NotificationService(MessageService messageService) {
        this.messageService = messageService;
    }

    public void notify(String message) {
        messageService.sendMessage(message);
    }
}

// 스프링 설정 클래스
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = "com.example")
class AppConfig {
}

// Main 클래스
public class Main {
    public static void main(String[] args) {
        // 스프링 컨테이너 생성
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        // 스프링 컨테이너에서 NotificationService 빈 가져오기
        NotificationService notificationService = context.getBean(NotificationService.class);
        notificationService.notify("Hello, Spring!");
    }
}

2) 코드 설명

  1. 스프링 컨테이너 생성:
    • ApplicationContext를 통해 스프링 컨테이너를 생성.
    • AppConfig 클래스에서 @ComponentScan으로 스캔한 클래스들을 빈으로 등록.
  2. 빈 생성 및 관리:
    • @Component로 등록된 EmailService와 NotificationService는 스프링 컨테이너에 의해 관리됨.
  3. 의존성 주입:
    • NotificationService의 생성자에 MessageService 타입의 객체(EmailService)가 자동으로 주입.

6. 정리 표

  스프링 컨테이너 스프링 빈
정의 객체(스프링 빈)를 생성, 관리, 의존성 주입, 생명 주기를 관리하는 도구. 스프링 컨테이너에서 생성 및 관리되는 객체.
역할 1. 빈 생성 및 관리
2. 의존성 주입
3. 객체 생명 주기 관리
애플리케이션에서 사용되는 실제 객체(컨테이너가 관리).
등록 방법 @Configuration, @ComponentScan 또는 XML 설정 파일에서 관리. @Component, @Service, @Repository, @Controller 등 어노테이션 또는 XML로 등록.
관리 범위 애플리케이션 전반에 걸친 빈의 관리와 설정. 컨테이너가 관리하는 객체 단위.
관계 빈의 생성과 관리를 책임짐. 컨테이너에 의해 생성되고 관리됨.
일상적 비유 빵집의 관리자: 직원과 재료를 관리하고, 작업 흐름을 조율. 빵집의 직원: 관리자가 관리하며, 각각의 역할을 수행.

7. 스프링 컨테이너와 스프링 빈의 관계

  1. 컨테이너는 빈을 생성하고 관리:
    • 스프링 컨테이너는 빈을 생성하고, 애플리케이션에서 필요한 의존성을 주입하며, 빈의 생명 주기를 관리합니다.
  2. 빈은 컨테이너에 의해 관리되는 객체:
    • 빈은 컨테이너가 생성, 관리하며, 개발자는 컨테이너를 통해 빈을 사용합니다.
    • 빈은 컨테이너가 없으면 독립적으로 존재할 수 없습니다.

결론

  • 스프링 컨테이너는 스프링 프레임워크에서 IOC와 DI를 구현하는 핵심 도구로, 애플리케이션의 객체 생성과 의존성 주입을 관리합니다.
  • 스프링 빈은 스프링 컨테이너가 관리하는 객체로, 개발자는 이 빈을 사용하여 비즈니스 로직을 구현합니다.
  • 스프링 컨테이너와 스프링 빈의 관계는 관리자와 직원의 관계로 비유할 수 있습니다.
    컨테이너가 모든 객체를 생성하고 관리하며, 빈은 각자의 역할에 맞는 작업을 수행합니다.

 

싱글톤 컨테이너 (Singleton Container)

https://dangdangee.tistory.com

1. 싱글톤 컨테이너란?

  • 싱글톤 컨테이너스프링 컨테이너가 애플리케이션 실행 시 한 번 생성된 빈(Bean) 객체를 재사용하도록 관리하는 컨테이너입니다.
  • 스프링에서 기본 빈 스코프는 싱글톤이며, 이는 애플리케이션 내에서 동일한 빈 객체가 공유된다는 것을 의미합니다.
  • 즉, 동일한 타입의 빈을 여러 번 요청해도 동일한 객체의 참조를 반환합니다.

2. 왜 싱글톤 컨테이너를 사용하는가?

  1. 효율적인 메모리 관리:
    • 객체를 한 번만 생성하고 재사용하므로 메모리 낭비를 줄일 수 있습니다.
  2. 애플리케이션의 일관성 유지:
    • 여러 컴포넌트에서 같은 빈 객체를 사용하므로 상태 관리가 용이합니다.
  3. 객체 생성 비용 절감:
    • 빈을 필요할 때마다 새로 생성하는 대신, 이미 생성된 객체를 재사용하여 성능을 향상시킵니다.

3. 싱글톤 컨테이너의 동작 방식

  1. 스프링 컨테이너는 빈 정의를 스캔하여 메모리에 빈 객체를 한 번만 생성합니다.
  2. 이후 빈이 필요할 때마다 동일한 객체의 참조를 반환합니다.
  3. 기본적으로 모든 빈은 싱글톤 스코프로 관리되지만, 필요에 따라 프로토타입 스코프 등으로 변경할 수 있습니다.

4. 코드 예제

싱글톤 컨테이너 동작

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Component;

@Component
class SingletonBean {
    public SingletonBean() {
        System.out.println("SingletonBean 객체 생성!");
    }

    public void doSomething() {
        System.out.println("SingletonBean 동작 수행!");
    }
}

public class Main {
    public static void main(String[] args) {
        // 스프링 컨테이너 생성
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Main.class.getPackageName());

        // 동일한 빈을 두 번 조회
        SingletonBean bean1 = context.getBean(SingletonBean.class);
        SingletonBean bean2 = context.getBean(SingletonBean.class);

        // 메서드 호출
        bean1.doSomething();

        // 객체 비교
        System.out.println("bean1 == bean2: " + (bean1 == bean2));

        // 컨테이너 종료
        context.close();
    }
}

실행 결과

SingletonBean 객체 생성!
SingletonBean 동작 수행!
bean1 == bean2: true

5. 코드 설명

  1. SingletonBean 정의:
    • @Component를 사용해 스프링 컨테이너에 빈으로 등록.
    • 컨테이너 초기화 시 객체가 한 번 생성됩니다.
  2. 컨테이너에서 빈 요청:
    • 두 번 getBean() 메서드를 호출했지만, 동일한 객체를 반환했습니다.
  3. 싱글톤 보장:
    • bean1과 bean2는 동일한 객체임을 확인(bean1 == bean2: true).

6. 싱글톤 컨테이너의 한계와 주의점

  1. 공유 객체이므로 상태 관리 주의:
    • 싱글톤 빈은 애플리케이션 전역에서 공유되므로, **빈 내부에 상태를 유지(필드에 값 저장 등)**하면 동시성 문제가 발생할 수 있습니다.
    • 상태를 유지하지 않고 **무상태(stateless)**로 설계하는 것이 중요합니다.
  2. 멀티스레드 환경에서 동시성 문제:
    • 공유되는 객체이므로 여러 스레드에서 동시에 접근할 경우 동시성 문제가 발생할 수 있습니다.

7. 요약

항목 내용
정의 스프링 컨테이너가 한 번 생성된 빈 객체를 재사용하여 관리하는 방식.
기본 스코프 싱글톤은 스프링 빈의 기본 스코프이며, 동일한 빈 객체를 공유합니다.
장점 메모리 효율성, 성능 향상, 객체 재사용성 증가.
단점 및 주의점 1. 공유 객체로 인한 동시성 문제 가능성2. 상태를 유지하는 빈 설계 시 주의 필요.
실행 방식 스프링 컨테이너가 빈을 한 번 생성 후 재사용.
기타 스코프와의 차이 프로토타입: 요청마다 새 객체 생성. 싱글톤: 모든 요청에 동일 객체 반환.

8. 싱글톤과 프로토타입 비교

특징 싱글톤 프로토타입
객체 생성 횟수 한 번 생성 후 컨테이너 내에서 재사용. 요청할 때마다 새로운 객체 생성.
기본 스코프 여부 스프링 빈의 기본 스코프. 요청 시마다 새 객체를 생성해야 하는 경우 사용.
사용 예 대부분의 서비스나 DAO 클래스. 상태가 있는 객체(일회성 데이터 처리 객체 등).
메모리 사용 메모리 효율적 사용. 요청마다 새로운 객체를 생성하므로 더 많은 메모리 사용.
상태 관리 무상태로 설계해야 함(상태 저장 시 동시성 문제 발생 가능). 상태를 유지할 수 있음(각 요청마다 객체가 새로 생성되므로 독립적).

9. 결론

  • 싱글톤 컨테이너는 애플리케이션 전반에서 하나의 객체를 공유하도록 관리하는 방식으로, 메모리와 성능 면에서 효율적입니다.
  • 스프링의 빈은 기본적으로 싱글톤으로 관리되며, 이를 통해 객체 재사용이 보장됩니다.
  • 하지만 공유 객체이므로 상태를 유지하지 않도록 설계해야 하며, 동시성 문제를 피하기 위해 빈을 **무상태(stateless)**로 설계하는 것이 중요합니다.
  • 필요 시 프로토타입이나 다른 스코프를 사용하여 빈의 생명 주기를 유연하게 관리할 수 있습니다.
 

[Spring] Spring의 핵심 개념

Spring Container📌 Spring으로 구성된 애플리케이션에서 객체(Bean)를 생성, 관리, 소멸하는 역할을 담당한다. 애플리케이션 시작 시, 설정 파일이나 Annotation을 읽어 Bean을 생성하고 주입하는 모든 과

kyunghun0515.tistory.com

 

컴포넌트 스캔 (Component Scan)

1. 컴포넌트 스캔이란?

**컴포넌트 스캔(Component Scan)**은 스프링 프레임워크가 특정 패키지와 하위 패키지를 검색하여 **빈(Bean)**으로 등록할 클래스들을 자동으로 찾아 스프링 컨테이너에 등록하는 기능입니다.


2. 컴포넌트 스캔의 작동 방식

  • 컴포넌트 스캔은 클래스에 붙은 특정 어노테이션(@Component 계열)을 기준으로 스프링 빈으로 등록할 대상을 결정합니다.
  • 스프링 컨테이너는 지정된 패키지를 스캔하며, 등록 대상 어노테이션이 붙은 클래스를 발견하면 이를 빈으로 등록합니다.

3. 컴포넌트 스캔 대상 어노테이션

어노테이션 설명
@Component 기본 컴포넌트 등록 어노테이션. 모든 스프링 관리 객체에 사용 가능.
@Controller 웹 계층(Controller)에서 사용. DispatcherServlet이 처리할 수 있도록 등록.
@Service 비즈니스 로직을 처리하는 서비스 계층에서 사용. 명시적으로 로직 계층을 표시.
@Repository 데이터 액세스 계층에서 사용. 스프링이 데이터 예외를 변환하도록 지원.
@Configuration 설정 클래스를 나타냄. @Bean 메서드를 통해 빈 정의를 명시적으로 추가.

4. 컴포넌트 스캔 활성화

1) Spring Boot (기본 활성화)

  • Spring Boot는 자동으로 @SpringBootApplication 어노테이션이 붙은 클래스의 패키지와 하위 패키지를 스캔합니다.
  • 내부적으로 @ComponentScan 어노테이션을 포함하고 있습니다.
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

2) 명시적으로 컴포넌트 스캔 설정

스프링 프레임워크에서는 **@ComponentScan**을 사용하여 스캔할 패키지를 명시적으로 설정할 수 있습니다.

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
}

5. 커스텀 스캔 설정

1) 특정 패키지 및 클래스만 스캔

  • **basePackages**로 스캔할 패키지 지정:
@ComponentScan(basePackages = {"com.example.service", "com.example.repository"})
  • **basePackageClasses**로 기준 클래스 지정:
@ComponentScan(basePackageClasses = {MyService.class, MyRepository.class})

2) 필터로 포함/제외 설정

컴포넌트 스캔에 필터를 추가하여 특정 클래스만 포함하거나 제외할 수 있습니다.

@ComponentScan(
    basePackages = "com.example",
    includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyCustomAnnotation.class),
    excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "com\\.example\\.exclude\\..*")
)

 

옵션 설명
FilterType.ANNOTATION 특정 어노테이션 기준으로 필터링.
FilterType.ASSIGNABLE_TYPE 특정 클래스 기준으로 필터링.
FilterType.REGEX 정규식을 사용해 클래스 이름으로 필터링.
FilterType.ASPECTJ AspectJ 표현식으로 필터링.
FilterType.CUSTOM 사용자 정의 필터를 구현하여 필터링.

6. 컴포넌트 스캔과 빈 등록 방식 비교

등록 방식 설명 예제
컴포넌트 스캔 @Component 계열 어노테이션을 사용해 스프링이 자동으로 빈 등록. @Service, @Repository, @Component.
명시적 등록 @Configuration 클래스에서 @Bean 메서드를 사용해 직접 등록. @Bean public MyService myService() { return new MyService(); }

7. 컴포넌트 스캔이 동작하지 않을 때 확인 사항

  1. 패키지 경로 확인:
    • 컴포넌트 스캔이 설정된 basePackages 또는 @SpringBootApplication 클래스의 위치를 확인합니다.
    • 클래스가 스캔 경로 내에 있어야 합니다.
  2. 어노테이션 누락:
    • 클래스에 @Component 계열 어노테이션이 붙어 있는지 확인합니다.
  3. 필터 설정 확인:
    • excludeFilters가 의도치 않게 클래스를 필터링하고 있지 않은지 확인합니다.

8. 예제 코드

프로젝트 구조

com.example
 ├── MainApplication.java
 ├── controller
 │    └── MyController.java
 ├── service
 │    └── MyService.java
 └── repository
      └── MyRepository.java

코드

  1. Controller
package com.example.controller;

import com.example.service.MyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyController {

    @Autowired
    private MyService myService;

    @GetMapping("/")
    public String home() {
        return myService.sayHello();
    }
}
  1. Service
package com.example.service;

import org.springframework.stereotype.Service;

@Service
public class MyService {
    public String sayHello() {
        return "Hello, Component Scan!";
    }
}
  1. Repository
package com.example.repository;

import org.springframework.stereotype.Repository;

@Repository
public class MyRepository {
    // Data access logic
}
  1. Main Application
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
    }
}

9. 정리

항목 설명
정의 특정 패키지를 스캔하여 @Component 계열 어노테이션이 붙은 클래스를 자동으로 스프링 빈으로 등록하는 기능.
대상 어노테이션 @Component, @Service, @Repository, @Controller, @Configuration.
활성화 방법 기본적으로 Spring Boot에서 자동 활성화 (@SpringBootApplication), 수동으로 @ComponentScan 설정 가능.
장점 1. 수동 등록 불필요2. 반복 코드 감소3. 계층 구조를 명확히 설계 가능.
단점 및 주의사항 1. 잘못된 경로 설정으로 인해 클래스 누락 가능2. 무분별한 클래스 스캔은 성능에 영향을 줄 수 있음.

10. 결론

  • 컴포넌트 스캔은 스프링의 주요 기능으로, 어노테이션을 기반으로 자동으로 스프링 빈을 등록하는 방식입니다.
  • 스프링 부트는 기본적으로 컴포넌트 스캔이 활성화되어 있으므로, 개발자는 어노테이션만 추가하면 빈 등록이 가능합니다.
  • 필요한 경우 @ComponentScan과 필터를 활용해 스캔 범위와 조건을 세부적으로 조정할 수 있습니다.

 

빈 스코프 (Bean Scope)

 

1. 빈 스코프란?

스프링 빈(Bean) 스코프스프링 컨테이너가 빈의 생성과 관리 주기를 결정하는 방식을 의미합니다.
즉, 빈이 언제 생성되고, 몇 번 생성되며, 어떤 범위에서 공유되는지를 정의합니다.

 

+ 스프링 컨테이너는 프로토타입 빈을 생성하고 의존관계 주입, 초기화까지만 처리한다. 클라이언트에 빈을 반환한 이후에는 스프링 컨테이너는 해당 빈을 관리하지 않는다. 그렇기에 스프링을 떠난 빈은 온전히 클라이언트가 생명주기를 관리해야하며, 그래서 @PreDestory같은 스프링의 어노테이션으로 종료가 안된다. = 이 순간부터 스프링의 객체가 아니다.


2. 스프링이 지원하는 빈 스코프 종류

스코프 설명 기본 스코프 주요 사용 사례
Singleton 컨테이너당 한 번 생성. 모든 요청에서 동일한 인스턴스를 공유. ✅ 기본값 대부분의 서비스 객체, 공통 데이터 처리.
Prototype 요청할 때마다 새로운 빈 생성. 상태가 있는 객체(예: 사용자 입력 데이터 처리 객체).
Request HTTP 요청당 하나의 빈 생성. 요청이 종료되면 빈도 소멸. 각 HTTP 요청마다 고유한 상태를 유지해야 하는 경우(예: HTTP 세션 정보).
Session HTTP 세션당 하나의 빈 생성. 세션이 종료되면 빈도 소멸. 사용자 세션에 따라 데이터를 유지해야 하는 경우.
Application 애플리케이션 컨텍스트(WebApplicationContext) 전체에서 하나의 빈 공유. 애플리케이션 전역에서 공유해야 하는 데이터(예: 전역 설정 데이터).
Websocket WebSocket 연결당 하나의 빈 생성. 연결이 종료되면 빈도 소멸. WebSocket 기반 실시간 통신에서 각 연결별로 고유 상태를 유지해야 하는 경우.

3. 빈 스코프별 동작 방식

1) Singleton Scope (싱글톤 스코프)

  • 특징:
    • 컨테이너 내에 하나의 빈 인스턴스만 생성.
    • 모든 요청에서 동일한 객체를 반환.
  • 사용 사례:
    • 공유 데이터나 상태를 가지지 않는 서비스 객체, 비즈니스 로직 처리 클래스.
@Service
public class MySingletonService {
    public String getMessage() {
        return "This is a Singleton Bean";
    }
}

2) Prototype Scope (프로토타입 스코프)

  • 특징:
    • 요청할 때마다 새로운 빈 인스턴스를 생성.
    • 빈의 생명 주기를 컨테이너가 관리하지 않음(직접 관리해야 함).
  • 사용 사례:
    • 상태가 있는 객체(예: 임시 데이터 저장소).
@Component
@Scope("prototype")
public class MyPrototypeService {
    public String getMessage() {
        return "This is a Prototype Bean";
    }
}

주요 차이점:

  • 싱글톤은 동일한 객체를 공유하지만, 프로토타입은 요청마다 새로운 객체를 생성.

3) Request Scope (요청 스코프)

  • 특징:
    • HTTP 요청당 하나의 빈 생성.
    • 요청이 종료되면 빈도 소멸.
  • 사용 사례:
    • 각 요청마다 고유한 상태를 유지해야 하는 경우.
@Component
@Scope("request")
public class MyRequestBean {
    private String value;

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

주의: 요청 스코프는 웹 애플리케이션 환경(@RestController, @Controller)에서만 사용할 수 있습니다.


4) Session Scope (세션 스코프)

  • 특징:
    • HTTP 세션당 하나의 빈 생성.
    • 세션이 종료되면 빈도 소멸.
  • 사용 사례:
    • 사용자별 데이터를 유지해야 하는 경우(예: 로그인 상태, 장바구니).
@Component
@Scope("session")
public class MySessionBean {
    private String userData;

    public String getUserData() {
        return userData;
    }

    public void setUserData(String userData) {
        this.userData = userData;
    }
}

5) Application Scope (애플리케이션 스코프)

  • 특징:
    • 웹 애플리케이션 컨텍스트 전체에서 하나의 빈 공유.
  • 사용 사례:
    • 애플리케이션 전역에서 유지해야 하는 데이터.
@Component
@Scope("application")
public class MyApplicationBean {
    public String getGlobalValue() {
        return "This is an Application Scoped Bean";
    }
}

6) Websocket Scope (웹소켓 스코프)

  • 특징:
    • WebSocket 연결당 하나의 빈 생성.
    • 연결 종료 시 빈도 소멸.
  • 사용 사례:
    • WebSocket 연결별로 데이터를 유지해야 하는 경우.
@Component
@Scope("websocket")
public class MyWebsocketBean {
    private String connectionId;

    public String getConnectionId() {
        return connectionId;
    }

    public void setConnectionId(String connectionId) {
        this.connectionId = connectionId;
    }
}

4. 빈 스코프 설정 방법

1) 어노테이션 사용

  • **@Scope**를 사용하여 빈의 스코프를 지정합니다.
@Component
@Scope("prototype") // 프로토타입 스코프 지정
public class MyPrototypeBean {
    // 빈의 내용
}

2) XML 설정 (스프링 설정 파일 사용)

<bean id="myBean" class="com.example.MyBean" scope="prototype"/>

3) Java Config 사용

@Bean
@Scope("prototype") // 프로토타입 스코프 지정
public MyPrototypeBean myPrototypeBean() {
    return new MyPrototypeBean();
}

5. 정리 표

스코프  생성 시점 소멸 시점 주요 사용 사례
Singleton 컨테이너 초기화 시 컨테이너 종료 시 공유 데이터를 가지지 않는 서비스 클래스, 공통 로직 처리.
Prototype 빈 요청 시 사용자가 관리 상태를 가지는 객체, 임시 데이터 처리.
Request HTTP 요청 시작 시 HTTP 요청 종료 시 요청마다 고유한 데이터 유지.
Session HTTP 세션 시작 시 HTTP 세션 종료 시 사용자 세션별 데이터 유지(로그인 상태, 사용자 데이터).
Application 컨테이너 초기화 시 애플리케이션 종료 시 전역 설정 데이터, 애플리케이션 전역에서 공유해야 하는 데이터.
Websocket WebSocket 연결 시작 시 WebSocket 연결 종료 시 WebSocket 연결별 데이터 유지(예: 실시간 통신 데이터).

6. 빈 스코프 선택 기준

  1. 싱글톤(Singleton):
    • 공유 데이터를 가지지 않으며, 애플리케이션 전역에서 빈을 공유해야 할 때 사용.
    • 기본값이므로 특별한 이유가 없다면 싱글톤을 사용하는 것이 권장됩니다.
  2. 프로토타입(Prototype):
    • 상태를 가지는 객체가 필요하거나 요청 시마다 새로운 인스턴스가 필요할 때.
  3. 요청/세션/애플리케이션(Web):
    • 웹 애플리케이션 환경에서 요청별, 세션별, 또는 애플리케이션 전역 데이터를 유지해야 할 때 사용.
  4. 웹소켓(WebSocket):
    • WebSocket 기반 통신에서 연결별 데이터를 유지해야 할 때 사용.

7. 결론

  • 스프링 빈 스코프는 빈의 생성 주기와 공유 범위를 제어하는 중요한 설정입니다.
  • 기본값은 **싱글톤(Singleton)**이며, 대부분의 경우 효율적이고 적합하지만, 특정 상황에서는 프로토타입, 요청, 세션, 애플리케이션 스코프를 활용해야 합니다.
  • 스코프를 적절히 설정하면 애플리케이션의 효율성과 유지보수성이 크게 향상됩니다.

 

 

Spring Boot

📌 Spring Framework를 기반으로 하여 간편하고 신속하게 애플리케이션을 개발할 수 있도록 도와주는 도구이다.

Web Application 이라는 라면을 끓일 때(만들 때) 조리 도구 세트를 사용한다. 라면 : Java 냄비 : Spring 조리 도구 세트 : Spring Boot

 

 

[1]  Spring Boot의 주요 특징

특징 설명
자동 설정 (Auto Configuration) Spring Boot는 애플리케이션의 환경을 자동으로 감지하여 적절한 설정을 자동으로 적용합니다. 이를 통해 많은 설정을 수동으로 할 필요 없이, 개발자가 설정에 대해 걱정하지 않고 애플리케이션을 시작할 수 있습니다.
독립 실행형 애플리케이션 Spring Boot는 내장된 서버(예: Tomcat, Jetty, Undertow 등)를 포함하여, WAR 파일 없이도 실행할 수 있는 독립 실행형 애플리케이션을 만듭니다.
스타터 프로젝트 (Starter Projects) Spring Boot는 필요한 라이브러리나 종속성을 손쉽게 추가할 수 있도록 "스타터" 의존성(예: spring-boot-starter-web, spring-boot-starter-data-jpa 등)을 제공합니다.
프로덕션 준비 Spring Boot는 메트릭스, 상태 점검, 로깅과 같은 프로덕션 환경에서 필요한 다양한 기능을 기본적으로 제공합니다. Actuator 라이브러리를 통해 애플리케이션의 상태를 모니터링하고 관리할 수 있습니다.
Spring Initializr Spring Boot는 프로젝트 초기화를 쉽게 할 수 있도록 https://start.spring.io/에서 다양한 설정을 선택하여 빠르게 프로젝트를 생성할 수 있습니다.
간단한 설정 Spring Boot는 application.properties 또는 application.yml 파일을 사용해 간단하게 애플리케이션의 설정을 관리할 수 있습니다.
서버의 자동 실행 내장 웹 서버를 통해 별도의 설정 없이, 애플리케이션을 java -jar 명령어로 실행할 수 있습니다.
개발자 친화적인 환경 Spring Boot는 애플리케이션의 개발과 디버깅을 빠르게 할 수 있도록 DevTools라는 기능을 제공하며, 코드 변경 시 자동으로 애플리케이션을 재시작하여 개발 편의성을 제공합니다.

 

 

[2]  Spring Boot의 장점

  1. 빠른 시작:
    • Spring Boot는 자동 설정 및 기본적인 템플릿을 제공하여, 애플리케이션을 몇 가지 설정만으로 빠르게 시작할 수 있습니다. @SpringBootApplication 어노테이션을 추가하면, 기본적인 설정이 자동으로 이루어지고, 애플리케이션을 실행할 수 있는 상태가 됩니다.
  2. 설정 최소화:
    • Spring Boot는 많은 설정을 자동으로 처리하므로 개발자는 비즈니스 로직에만 집중할 수 있습니다. 예를 들어, 데이터베이스 설정, 서버 설정등 대부분의 설정을 자동으로 처리하여 개발자가 별도로 신경 쓸 필요가 없습니다.
  3. 내장 서버:
    • 내장 서버를 제공하여 별도의 외부 웹 서버(Tomcat, Jetty 등이 내장되어 있다.)를 설치할 필요 없이 바로 실행할 수 있습니다. 이는 애플리케이션의 배포를 간소화하고, 실행 파일 하나로 애플리케이션을 배포할 수 있게 만듭니다.
  4. 생산성 향상:
    • Spring Boot는 개발자가 빠르게 단독으로 실행할 수 있는 애플리케이션을 작성하고 실행할 수 있도록 돕기 위해 많은 스타터 의존성을 제공합니다. 예를 들어, 웹 애플리케이션을 만들기 위한 spring-boot-starter-web, 데이터베이스 연동을 위한 spring-boot-starter-data-jpa 등이 있습니다.
  5. DevTools:
    • Spring Boot는 개발 중에 자동 재시작  Hot swapping을 지원하여, 개발자가 코드 수정 후 애플리케이션을 다시 시작하지 않고도 변경 사항을 바로 반영할 수 있도록 돕습니다.

 

[?] 특징, 장점은 여러개가 있지만 결국 왜 스프링을 사용할까?

= 자바의 가장 큰 특징은 객체 지향이다. 스프링은 좋은 객체 지향 애플리케이션 개발을 도와주는 프레임워크다.

 

 

[3]  Spring Boot 애플리케이션 구조

  1. 애플리케이션 클래스
    • @SpringBootApplication 어노테이션이 붙은 클래스는 Spring Boot 애플리케이션의 진입점입니다. 이 클래스는 자동으로 필요한 설정을 수행하고 애플리케이션을 실행합니다.
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
  1. 자동 설정:
    • Spring Boot는 애플리케이션을 실행할 때, 주어진 환경에 맞춰 자동으로 설정을 적용합니다. 예를 들어, 데이터베이스가 설정되면 자동으로 DataSource와 관련된 설정이 적용됩니다.
  2. 프로퍼티 파일:
    • Spring Boot는 application.properties 또는 application.yml 파일을 통해 애플리케이션의 설정을 관리할 수 있습니다. 데이터베이스 연결 정보, 서버 포트, 로깅 수준 등 다양한 설정을 이 파일에서 처리합니다.
  3. 내장 서버:
    • Spring Boot는 기본적으로 내장된 Tomcat 서버를 포함하고 있으며, 설정에 따라 다른 내장 서버(Undertow, Jetty 등)를 사용할 수 있습니다.

 

[4]  Spring Boot 애플리케이션 실행

  1. Maven 또는 Gradle을 사용한 빌드 후 실행:
    • 애플리케이션을 빌드하고 실행하려면, mvn spring-boot:run 또는 gradle bootRun 명령을 사용할 수 있습니다.
  2. JAR 파일로 실행:
    • Spring Boot 애플리케이션을 JAR 파일로 빌드한 후, java -jar 명령어로 실행할 수 있습니다.
java -jar myapp.jar

 

[5]  Spring Boot 사용 예시

  • 간단한 RESTful 웹 서비스:
@RestController
@RequestMapping("/api")
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "Hello, World!";
    }
}
  • 자동 설정 예시: Spring Boot는 데이터베이스 연결을 자동으로 처리할 수 있습니다. 예를 들어, 데이터베이스 설정을 application.properties에서 지정하면, Spring Boot는 이를 자동으로 인식하고 설정을 완료합니다.
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=secret

 

 

 

Spring MVC

항목  설명
Spring MVC란? Model-View-Controller (MVC) 패턴을 기반으로 웹 애플리케이션 개발을 지원하는 Spring Framework의 모듈.
목적 클라이언트 요청을 처리하고, 비즈니스 로직을 수행하며, 응답을 반환하는 웹 애플리케이션 개발을 간소화.
구조적 특징 - 프론트 컨트롤러 패턴을 적용한 구조.- 중앙 컨트롤러인 DispatcherServlet이 요청과 응답을 총괄 관리.
핵심 구성 요소 - DispatcherServlet (프론트 컨트롤러)- HandlerMapping (핸들러 매핑)- HandlerAdapter (핸들러 실행)- ViewResolver (뷰 선택 및 렌더링)
동작 원리 1. 클라이언트 요청 수신 → 2. 핸들러 매핑 및 핸들러 실행 → 3. 비즈니스 로직 수행 → 4. 결과 데이터를 뷰에 전달 → 5. 뷰 렌더링 후 응답 반환.
특징 - 확장성: 대부분의 구성 요소가 인터페이스로 제공되어 커스터마이징 가능.- 유연성: 다양한 템플릿 엔진과 통합 가능 (JSP, Thymeleaf 등).
장점 - 클라이언트 요청과 비즈니스 로직, 뷰 렌더링의 명확한 분리.- 유지보수 용이성 테스트 용이성.- 대규모 애플리케이션 개발에 적합.
단점 - 초보자에게는 상대적으로 높은 학습 곡선.- 복잡한 설정이 필요한 경우가 있음 (Spring Boot로 해결 가능).
주요 어노테이션 - @Controller, @RestController, @RequestMapping, @GetMapping, @PostMapping, @Service, @Repository 등.
사용 사례 - RESTful 웹 서비스.- 템플릿 기반 서버 사이드 렌더링 애플리케이션.- JSON 응답 기반 API 제공.

 

Spring MVC 구조

구성 요소 설명 주요 역할 및 특징
DispatcherServlet Spring MVC의 프론트 컨트롤러. 모든 HTTP 요청의 진입점으로 작동. - 요청을 받아 핸들러 매핑 및 핸들러 어댑터를 통해 요청 처리 흐름 제어.- HTTP 요청 파싱 후 알맞은 핸들러 호출.- 최종 응답 반환.
HandlerMapping 요청 URL과 알맞은 핸들러(Controller)를 매핑. - 요청 URL에 따라 핸들러를 찾는 역할.- 우선순위에 따라 실행 (e.g., RequestMappingHandlerMapping, BeanNameUrlHandlerMapping).
HandlerAdapter 요청을 처리할 핸들러를 실행하도록 도와주는 어댑터. - 핸들러(Controller)가 실행되도록 요청 위임.- 우선순위에 따라 적절한 어댑터 선택 (e.g., RequestMappingHandlerAdapter, SimpleControllerHandlerAdapter).
Handler (Controller) 클라이언트 요청을 처리하는 핵심 비즈니스 로직 담당. - 요청 데이터를 바탕으로 필요한 로직 수행.- 결과를 ModelAndView로 반환.
ModelAndView 요청 처리 결과 데이터를 담고, 반환할 뷰의 논리 이름을 포함. - 모델(Model)과 뷰(View)를 묶어서 관리.- 핸들러에서 반환하여 View Resolver가 처리.
ViewResolver 논리 뷰 이름(View Name)을 실제 뷰 파일(View Path)로 변환. - View를 렌더링할 수 있도록 설정.- e.g., InternalResourceViewResolver, ThymeleafViewResolver.
View 최종 사용자에게 응답으로 전달할 화면을 렌더링. - ModelAndView 데이터를 사용해 화면 생성.- JSP, Thymeleaf, JSON 등 다양한 형태로 결과 반환.

Spring MVC 실행 흐름 요약

  단계 설명
1 요청 수신 클라이언트가 HTTP 요청을 보내면 DispatcherServlet이 이를 수신.
2 Handler 조회 HandlerMapping이 요청 URL에 매핑된 핸들러(Controller)를 검색.
3 HandlerAdapter 조회 매핑된 핸들러를 처리할 수 있는 적합한 HandlerAdapter를 검색.
4 핸들러 실행 HandlerAdapter가 핸들러를 호출하여 요청 처리 및 결과 반환.
5 ModelAndView 반환 핸들러가 결과 데이터를 ModelAndView 객체로 반환.
6 ViewResolver 호출 ViewResolver가 ModelAndView의 논리 이름(View Name)을 실제 뷰 파일 경로로 변환.
7 View 렌더링 변환된 뷰(View)에서 화면을 렌더링하여 클라이언트에게 응답.

Spring MVC 주요 인터페이스 요약

인터페이스  역할  주요 구현체
HandlerMapping 요청 URL과 핸들러 매핑. RequestMappingHandlerMapping, BeanNameUrlHandlerMapping.
HandlerAdapter 핸들러(Controller)를 실행하도록 지원. RequestMappingHandlerAdapter, HttpRequestHandlerAdapter, SimpleControllerHandlerAdapter.
ViewResolver 논리적 View 이름을 물리적 경로로 변환. InternalResourceViewResolver, ThymeleafViewResolver.
View 클라이언트에 응답할 화면 렌더링. JSP, Thymeleaf, JSON 등 다양한 형태.
 

ArgumentResolver

RequestMappingHandlerAdapter📌 Spring MVC에서 HTTP 요청을 컨트롤러 메서드에 매핑하고 실행하는 핵심 구성 요소로, 클라이언트 요청을 적절한 컨트롤러 메서드와 연결한 후 이 메서드를 호출하여 결과

kyunghun0515.tistory.com

 


Spring MVC 주요 컴포넌트와 동작 흐름

컴포넌트  설명
DispatcherServlet - 요청의 중앙 처리국.- 핸들러 매핑, 핸들러 어댑터 조회 및 호출, 최종 응답 반환까지 모든 작업 관리.
HandlerMapping - 요청 URL에 매핑된 핸들러를 찾아 반환.
HandlerAdapter - 핸들러 실행을 위임받아 적합한 어댑터로 요청 전달.
- 핸들러로부터 받은 결과를 ModelAndView로 변환하여 반환.
HttpMessageConverter - View 와 HandlerAdapter 사이에서 데이터 변환을 처리하는 역할을 한다.
ViewResolver - 핸들러가 반환한 논리 뷰 이름(View Name)을 실제 뷰 경로로 변환.- 적합한 View 객체 반환.
View - 최종 사용자에게 응답 화면을 렌더링.- JSP, Thymeleaf, JSON 등으로 변환하여 응답 반환.

 

 

[Spring] Spring MVC 패턴

Spring MVC 구조📌 Spring은 MVC 패턴에 프론트 컨트롤러 패턴, 어댑터 패턴이 적용된 구조를 가지고 있다.MVC는 소프트웨어 설계 패턴으로 구축 개념에 가깝다. 당연히 구축 방식은 때에 따라 달라

kyunghun0515.tistory.com

 

 

HttpMessageConverter

HttpMessageConverter의 역할

  • 클라이언트와 서버 간의 HTTP 요청과 응답을 처리할 때 데이터 형식 변환을 담당 합니다. 
  • HTTP 요청/응답 본문(body)의 데이터를 객체로 변환하거나, 객체를 특정 형식(JSON, XML 등)으로 변환하여 HTTP 응답 본문에 적합하게 변환하는 역할을 담당합니다.
  • 주로 REST API에서 사용되며, JSON이나 XML 데이터를 객체로 역직렬화하거나 객체를 JSON, XML 형식으로 직렬화하는 데 사용됩니다.
  • [ View를 응답하는 것이 아닌, Rest API(HTTP API)로 JSON, TEXT, XML 등의 데이터를 응답 Message Body에 직접 입력하는 경우 HttpMessageConverter를 사용합니다. ]

HttpMessageConverter와 다른 컴포넌트의 관계

  1. DispatcherServlet:
    • 요청이 들어오면, HandlerMapping과 HandlerAdapter를 통해 적절한 핸들러(Controller)를 실행합니다.
  2. HandlerAdapter:
    • 핸들러(Controller)에서 반환된 객체(@ResponseBody 또는 ResponseEntity)를 처리하는 과정에서 HttpMessageConverter를 호출합니다.
    • HttpMessageConverter는 데이터를 변환한 후, View를 렌더링하거나 HTTP 응답 본문에 데이터를 직접 작성합니다.
  3. ViewResolver & View:
    • ViewResolver는 논리적 뷰 이름을 실제 뷰로 변환하지만, 만약 JSON이나 XML 데이터로 응답해야 한다면 HttpMessageConverter가 뷰 렌더링 대신 데이터를 직접 변환하여 응답 본문으로 반환합니다.
  • 요청시에는 Argument Resolver를 사용합니다.
  • 응답시에는 ReturnValueHandler를 사용합니다.

HttpMessageConverter의 동작 흐름

  1. 클라이언트가 JSON 데이터를 포함한 HTTP 요청을 보냅니다.
  2. DispatcherServlet이 요청을 HandlerAdapter로 전달합니다.
  3. HandlerAdapter는 컨트롤러(핸들러)를 호출하여 요청을 처리합니다.
  4. 컨트롤러가 객체(예: @ResponseBody 또는 ResponseEntity)를 반환합니다.
  5. HandlerAdapter가 반환된 데이터를 처리하기 위해 적절한 **HttpMessageConverter**를 선택합니다.
  6. HttpMessageConverter는 데이터를 변환(JSON ↔ 객체)하여 응답 본문에 작성하거나, 요청 본문에서 데이터를 추출합니다.

HttpMessageConverter가 처리하는 경우

  • 요청 데이터 변환:
    • JSON, XML, plain text 데이터를 컨트롤러가 처리할 수 있는 Java 객체로 변환.
  • 응답 데이터 변환:
    • 컨트롤러에서 반환한 Java 객체를 JSON, XML, plain text 형식으로 변환하여 클라이언트에 전달.

HttpMessageConverter가 없는 경우와 있는 경우

없는 경우:

@RestController
public class MyController {
    @GetMapping("/data")
    public String getData() {
        return "{ \"key\": \"value\" }"; // 직접 JSON 문자열 작성
    }
}
  • JSON 데이터를 직접 작성해야 하므로 불편하고 오류 가능성이 높습니다.

있는 경우 (HttpMessageConverter 사용):

@RestController
public class MyController {
    @GetMapping("/data")
    public MyResponse getData() {
        return new MyResponse("value"); // 객체를 반환
    }
}

class MyResponse {
    private String key;

    public MyResponse(String key) {
        this.key = key;
    }

    // Getter/Setter
}
  • 컨트롤러는 Java 객체를 반환하며, HttpMessageConverter가 이를 JSON으로 변환하여 클라이언트에 응답합니다.

정리 표

컴포넌트 역할

DispatcherServlet 요청을 중앙에서 관리하며, HandlerMapping, HandlerAdapter를 통해 핸들러 실행 및 응답 반환.
HandlerMapping 요청 URL에 매핑된 핸들러(Controller)를 찾음.
HandlerAdapter 핸들러를 호출하고, 결과 데이터를 처리하여 적절한 변환기(HttpMessageConverter) 또는 View로 전달.
HttpMessageConverter 요청/응답 데이터를 JSON, XML 등으로 변환하거나 객체로 역직렬화하여 핸들러나 클라이언트와 데이터를 주고받음.
ViewResolver 논리적인 뷰 이름(View Name)을 실제 View 객체로 변환.
View 최종적으로 사용자에게 응답 화면을 렌더링. JSP, Thymeleaf, JSON, XML 등 다양한 형식을 처리.

결론

  • HttpMessageConverter는 HandlerAdapter와 View 사이에서 데이터 변환을 담당합니다.
  • 요청 데이터(JSON, XML 등)를 Java 객체로 변환하거나, 컨트롤러에서 반환한 객체를 JSON, XML 등으로 변환해 응답 본문으로 작성하는 데 사용됩니다.
  • REST API 개발에서 매우 중요한 역할을 하며, Spring MVC의 데이터 처리 흐름을 간단하게 만들어줍니다.
 

HttpMessageConverter

HttpMessageConverter📌 클라이언트와 서버 간의 HTTP 요청과 응답을 처리할 때 데이터 형식 변환을 담당 한다. 클라이언트가 보낸 데이터를 서버가 이해할 수 있는 형태로 변환하거나, 서버가 응답으

kyunghun0515.tistory.com

 

WebMvcConfigurer

  • WebMvcConfigurer는 스프링 프레임워크에서 제공하는 인터페이스로, 스프링 MVC의 동작을 사용자 정의할 수 있는 방법을 제공합니다.
  • 기본적으로 스프링은 자동 설정(Auto Configuration)을 통해 대부분의 MVC 설정을 처리하지만, 필요한 경우 개발자가 이를 커스터마이징할 수 있도록 WebMvcConfigurer를 제공합니다.

1. WebMvcConfigurer의 역할

WebMvcConfigurer를 구현하여 다음과 같은 스프링 MVC 동작을 커스터마이징할 수 있습니다:

  1. 요청 매핑(Custom Request Mapping):
    • 특정 URL 패턴의 요청 처리 방식을 사용자 정의.
  2. CORS 설정:
    • 특정 도메인에서의 Cross-Origin 요청을 허용하거나 제한.
  3. 정적 리소스 핸들링:
    • 정적 리소스(예: CSS, JS, 이미지) 경로 설정.
  4. 뷰 컨트롤러(View Controller) 추가:
    • 컨트롤러 없이 URL에 대해 바로 뷰를 반환하도록 설정.
  5. 포맷터(Formatter) 및 변환기(Converter) 등록:
    • 커스텀 데이터 타입 변환 또는 데이터 포맷팅.
  6. 인터셉터 등록:
    • 요청 전후 처리에 대한 로직 추가.
  7. MessageConverter 등록:
    • 요청/응답 데이터의 변환 규칙 추가 또는 커스터마이징.

2. WebMvcConfigurer를 사용하는 방법

1) 기본적인 구현

스프링에서 WebMvcConfigurer를 구현하는 클래스는 다음과 같습니다:

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {
    // 필요한 메서드 오버라이드
}
  • @Configuration:
    • 이 클래스가 설정 클래스임을 나타냅니다.
  • WebMvcConfigurer:
    • 인터페이스로, 필요한 메서드만 선택적으로 오버라이드할 수 있습니다.

3. WebMvcConfigurer에서 커스터마이징 예제

1) 정적 리소스 경로 설정

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**") // URL 패턴
                .addResourceLocations("classpath:/static/"); // 실제 리소스 경로
    }
}
  • 설명:
    • /static/** URL 요청은 classpath:/static/ 경로의 정적 리소스로 매핑됩니다.
    • 예: /static/image.png 요청 → src/main/resources/static/image.png로 응답.

2) CORS 설정

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**") // 특정 URL 패턴
                .allowedOrigins("http://example.com") // 허용할 도메인
                .allowedMethods("GET", "POST"); // 허용할 HTTP 메서드
    }
}
  • 설명:
    • /api/** 경로의 요청에 대해 http://example.com 도메인에서의 GET, POST 요청만 허용합니다.

3) 뷰 컨트롤러 설정

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/home") // URL 요청
                .setViewName("home"); // 반환할 뷰 이름
    }
}
  • 설명:
    • /home 요청 시 컨트롤러 없이 바로 home.html을 반환합니다.

4) 인터셉터 등록

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor())
                .addPathPatterns("/api/**") // 인터셉터 적용 경로
                .excludePathPatterns("/api/public/**"); // 제외 경로
    }
}
  • 설명:
    • MyInterceptor가 /api/** 요청을 처리하며, /api/public/** 경로는 제외됩니다.

5) 커스텀 MessageConverter 등록

import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new MyCustomMessageConverter());
    }
}
  • 설명:
    • 사용자 정의 HttpMessageConverter를 등록하여 요청/응답 데이터를 커스터마이징합니다.

4. WebMvcConfigurer의 주요 메서드

메서드 설명
addCorsMappings(CorsRegistry registry) CORS 설정을 추가합니다.
addViewControllers(ViewControllerRegistry registry) 특정 URL 요청에 대해 바로 View를 반환하도록 설정합니다.
addResourceHandlers(ResourceHandlerRegistry registry) 정적 리소스 경로를 설정합니다.
addInterceptors(InterceptorRegistry registry) 요청 전후에 실행할 인터셉터를 등록합니다.
configureMessageConverters(List<HttpMessageConverter<?>> converters) 사용자 정의 MessageConverter를 등록하거나 기존 것을 대체합니다.
extendMessageConverters(List<HttpMessageConverter<?>> converters) 기존 MessageConverter를 확장합니다.
addFormatters(FormatterRegistry registry) 사용자 정의 Formatter 또는 Converter를 등록합니다.

5. 요약 표

항목  내용
역할 스프링 MVC 동작을 커스터마이징하여 요청 처리 방식을 사용자 정의.
주요 사용 목적 1. 정적 리소스 핸들링2. CORS 설정3. 인터셉터 등록4. MessageConverter 추가.
주요 메서드 addCorsMappings, addViewControllers, addResourceHandlers, addInterceptors, configureMessageConverters.
사용 예 REST API 응답 포맷 변경, 정적 리소스 경로 지정, CORS 정책 설정 등.
장점 기본 설정을 유지하면서도 필요한 부분만 사용자 정의 가능.

6. 결론

  • WebMvcConfigurer는 스프링 MVC의 동작을 세부적으로 커스터마이징할 수 있는 강력한 도구입니다.
  • REST API 개발, CORS 설정, 정적 리소스 처리 등 다양한 요구사항을 처리할 때 사용됩니다.
  • 필요한 메서드만 선택적으로 오버라이드하면 되므로 설정이 간단하며, 스프링의 기본 동작과 충돌 없이 동작합니다.

 

Spring 타입 변형 

TypeConverter

  • TypeConverter는 스프링 프레임워크에서 다양한 타입 간의 변환을 지원하는 인터페이스입니다.
    주로 사용자 입력 값(HTTP 요청 파라미터, 쿼리 매개변수 등)을 컨트롤러 메서드의 매개변수에 매핑하거나, 빈의 속성을 설정할 때 사용됩니다.
  • 이는 개발자가 **String 타입의 입력값을 Java 객체(예: Date, Enum 등)**로 변환하거나, Java 객체를 다시 String으로 변환할 수 있도록 돕습니다.

1. TypeConverter의 역할

  1. 입력 값 변환:
    • HTTP 요청 파라미터나 쿼리 매개변수를 컨트롤러 메서드의 매개변수로 변환.
    • 예: String "123"을 Integer로 변환.
  2. 빈 속성 변환:
    • 설정 파일(예: application.properties)의 속성을 빈 속성 값으로 변환.
    • 예: "2025-01-01" → LocalDate.
  3. 커스텀 타입 지원:
    • 기본 제공 타입 외에 사용자 정의 타입을 지원하도록 확장 가능.

2. 기본 동작

스프링은 기본적인 타입 변환을 지원합니다. 예를 들어, 아래의 기본 변환은 자동으로 처리됩니다:

  • String → int, long, double, boolean.
  • String → Date, LocalDate, LocalDateTime.
  • String → Enum.

3. TypeConverter의 구현 및 사용

1) 기본 사용 (스프링 자동 변환)

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyController {

    @GetMapping("/convert")
    public String convert(@RequestParam("id") int id, @RequestParam("active") boolean active) {
        return "Converted ID: " + id + ", Active: " + active;
    }
}
  • 입력 요청:
    GET /convert?id=123&active=true
    
  • 결과:
    Converted ID: 123, Active: true
    

스프링은 String으로 들어오는 id와 active 파라미터를 각각 int와 boolean으로 자동 변환합니다.


2) 커스텀 타입 변환기 작성

스프링의 기본 변환 외에 사용자 정의 변환기를 등록할 수 있습니다.

1. 사용자 정의 타입

public class CustomType {
    private String value;

    public CustomType(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }

    @Override
    public String toString() {
        return "CustomType{" +
                "value='" + value + '\'' +
                '}';
    }
}

2. TypeConverter 구현

import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;

// String -> CustomType 변환기
@Component
public class StringToCustomTypeConverter implements Converter<String, CustomType> {

    @Override
    public CustomType convert(String source) {
        return new CustomType(source);
    }
}

3. 컨트롤러에서 사용

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyController {

    @GetMapping("/custom-convert")
    public String customConvert(@RequestParam("value") CustomType customType) {
        return "Converted Value: " + customType.getValue();
    }
}

입력 요청:

GET /custom-convert?value=test

결과:

Converted Value: test

스프링은 StringToCustomTypeConverter를 사용해 요청 파라미터 "test"를 CustomType 객체로 변환합니다.


4. TypeConverter 등록

스프링에서 커스텀 변환기를 등록하려면 **WebMvcConfigurer**를 사용합니다:

import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new StringToCustomTypeConverter());
    }
}

5. TypeConverter와 관련된 컴포넌트

  1. ConversionService:
    • 스프링의 타입 변환을 총괄하는 서비스.
    • TypeConverter를 등록하고 관리.
    • 기본 구현: DefaultConversionService.
  2. FormatterRegistry:
    • 변환기와 포맷터를 등록하는 레지스트리.
  3. Converter:
    • TypeConverter의 구체적인 구현체로, 한 타입을 다른 타입으로 변환하는 로직을 작성.

6. 정리 표

항목 내용

정의 다양한 타입(String, Integer, Date 등) 간의 변환을 지원하는 스프링의 인터페이스.
기본 역할 1. HTTP 요청 파라미터 → Java 객체 변환2. Java 객체 → HTTP 응답 데이터 변환.
주요 컴포넌트 TypeConverter, ConversionService, FormatterRegistry.
커스터마이징 방법 1. Converter 인터페이스 구현2. WebMvcConfigurer를 통해 등록.
사용 사례 1. 요청 파라미터 자동 변환2. 빈 속성 설정3. 사용자 정의 타입 변환.
장점 1. 코드 간결화2. 타입 변환 로직의 재사용성 증가3. 일관된 타입 변환 제공.

7. 결론

  • TypeConverter는 스프링에서 입력 데이터(문자열 등)를 원하는 타입(Java 객체 등)으로 변환하거나, Java 객체를 특정 포맷(String 등)으로 변환하는 데 사용됩니다.
  • 기본적으로 제공되는 변환 외에도, 사용자 정의 변환기를 등록해 스프링의 타입 변환 기능을 확장할 수 있습니다.
  • REST API나 요청 파라미터의 데이터 처리 과정에서 매우 유용하며, 데이터 타입 변환을 간단하고 일관되게 관리할 수 있도록 도와줍니다.

ConversionService

  • **ConversionService**는 스프링 프레임워크에서 제공하는 인터페이스로, 한 타입을 다른 타입으로 변환하는 데 사용됩니다.
  • 이는 타입 변환을 중앙에서 관리하고, 통합된 방식으로 변환기를 사용할 수 있도록 도와줍니다.

1. ConversionService의 역할

  • 타입 변환의 중심 역할:
    • Converter를 관리하고, 필요한 경우 적절한 변환기를 선택해 타입 변환을 수행합니다.
  • 기본 제공 변환:
    • String ↔ Integer, String ↔ Boolean, String ↔ Date, String ↔ Enum 등.
  • 커스텀 타입 변환:
    • 사용자 정의 타입 변환기를 등록해 특정 타입 간의 변환도 처리할 수 있습니다.

2. ConversionService의 구현체

  • 스프링은 ConversionService 인터페이스를 구현한 기본 클래스들을 제공합니다:
    1. DefaultConversionService:
      • 스프링에서 제공하는 기본 변환 서비스.
      • 대부분의 변환 작업에 대해 기본 구현을 제공하며, 추가 변환기를 등록할 수도 있습니다.
    2. GenericConversionService:
      • 커스텀 변환기 등록을 지원하는 범용 변환 서비스.
    3. FormattingConversionService:
      • DefaultConversionService를 확장하며, 포맷터(Formatter)를 지원.

3. ConversionService 사용 방법

1) 기본 제공 변환

스프링은 DefaultConversionService를 통해 다양한 타입 변환을 기본 제공하며, 이를 직접 사용할 수 있습니다.

import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.ConversionService;

public class Main {
    public static void main(String[] args) {
        // DefaultConversionService 생성
        ConversionService conversionService = new DefaultConversionService();

        // 기본 변환 사용
        int convertedInt = conversionService.convert("123", Integer.class);
        boolean convertedBool = conversionService.convert("true", Boolean.class);

        // 결과 출력
        System.out.println("Converted Integer: " + convertedInt); // 123
        System.out.println("Converted Boolean: " + convertedBool); // true
    }
}

2) 커스텀 변환기 등록

1. 커스텀 변환기 작성

import org.springframework.core.convert.converter.Converter;

// String -> CustomType 변환기
public class StringToCustomTypeConverter implements Converter<String, CustomType> {

    @Override
    public CustomType convert(String source) {
        return new CustomType(source);
    }
}

// CustomType 클래스
public class CustomType {
    private final String value;

    public CustomType(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }
}

2. Custom ConversionService 설정

import org.springframework.core.convert.support.GenericConversionService;

public class Main {
    public static void main(String[] args) {
        // ConversionService 생성
        GenericConversionService conversionService = new GenericConversionService();

        // 커스텀 변환기 등록
        conversionService.addConverter(new StringToCustomTypeConverter());

        // 변환 수행
        CustomType customType = conversionService.convert("test-value", CustomType.class);

        // 결과 출력
        System.out.println("Converted CustomType Value: " + customType.getValue());
    }
}

4. ConversionService와 FormatterRegistry

  • **ConversionService**는 변환기(Converter)를 관리하며, 타입 변환을 처리합니다.
  • **FormatterRegistry**는 포맷터(Formatter)를 지원하며, ConversionService의 확장된 형태로 볼 수 있습니다.
  • WebMvcConfigurer에서 FormatterRegistry를 사용하여 변환기나 포맷터를 등록할 수 있습니다.

변환기 등록

import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new StringToCustomTypeConverter());
    }
}

5. ConversionService와 Spring MVC의 관계

Spring MVC에서의 ConversionService 동작

  • 입력 요청 처리:
    • HTTP 요청 파라미터를 컨트롤러 메서드의 매개변수로 매핑할 때 사용.
    • 예: "123" → Integer.
  • 출력 응답 처리:
    • 컨트롤러 메서드의 반환값을 클라이언트로 전송할 데이터 형식으로 변환.
    • 예: Java 객체 → String.

6. 주요 메서드

메서드 설명
boolean canConvert(Class<?> sourceType, Class<?> targetType) 특정 타입 간 변환이 가능한지 확인.
<T> T convert(Object source, Class<T> targetType) 주어진 소스 객체를 특정 대상 타입으로 변환.
<T> T convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) 소스 객체를 주어진 TypeDescriptor를 기반으로 변환.

7. 정리

항목 내용
정의 다양한 타입 간의 변환을 통합 관리하는 스프링의 중심 인터페이스.
기본 구현체 DefaultConversionService, GenericConversionService, FormattingConversionService.
기본 제공 기능 1. String ↔ 기본 데이터 타입 변환2. String ↔ Date/Enum 변환 등.
커스터마이징 방법 1. Converter 구현2. GenericConversionService 또는 FormatterRegistry에 등록.
사용 사례 1. HTTP 요청 파라미터 처리2. 빈 설정 속성 변환3. 컨트롤러 응답 데이터 변환.
장점 타입 변환 로직의 중앙 관리, 확장성, 일관성.

8. 결론

  • **ConversionService**는 스프링에서 타입 변환을 통합적으로 관리하는 강력한 도구입니다.
  • 기본 제공 변환기 외에도, 사용자 정의 변환기를 등록해 커스텀 타입 간의 변환을 처리할 수 있습니다.
  • HTTP 요청, 응답, 빈 설정 등 다양한 상황에서 일관된 방식으로 타입 변환을 처리하여 개발자의 부담을 줄이고 코드의 유지보수성을 높입니다.

Formatter

  • Formatter는 스프링에서 제공하는 인터페이스로, 문자열(String)을 특정 객체로 변환하거나, 객체를 문자열로 변환하는 데 사용됩니다.
  • 특히, **지역화(Localization)**를 지원하여 날짜, 숫자 등의 데이터 포맷팅과 같은 작업에 적합합니다.

1. Formatter의 역할

  1. 문자열 → 객체 변환:
    • 사용자가 입력한 문자열 데이터를 특정 Java 객체로 변환.
    • 예: "2025-01-01" → LocalDate.
  2. 객체 → 문자열 변환:
    • Java 객체를 특정 형식의 문자열로 변환하여 사용자에게 출력.
    • 예: LocalDate.of(2025, 1, 1) → "2025-01-01".
  3. 지역화(Localization):
    • 포맷팅 작업 시, Locale을 고려하여 데이터를 변환.

2. Formatter와 Converter의 차이점

  Formatter Converter
주요 역할 문자열과 객체 간의 변환을 수행. 주로 사용자 입력 값과 출력 값을 처리. 다양한 타입 간의 변환을 수행. 특정 타입에서 다른 타입으로 변환.
지역화 지원 Locale을 기반으로 데이터 포맷팅 및 변환을 지원. 지역화를 지원하지 않음. 단순 타입 간 변환에 사용.
주요 사용 사례 날짜, 숫자, 통화 등 지역화된 값의 처리. 예: "123,456" → BigDecimal, "01/23/2025" → LocalDate. JSON, XML, 사용자 정의 객체 등 특정 타입 간의 변환. 예: String → CustomType.
인터페이스 org.springframework.format.Formatter. org.springframework.core.convert.converter.Converter.
주요 메서드 - parse(String text, Locale locale): 문자열 → 객체 변환- print(T object, Locale locale): 객체 → 문자열 변환 - convert(S source): 한 타입 → 다른 타입 변환.
통합 지원 FormattingConversionService에 등록하여 사용. ConversionService에 등록하여 사용.

3. Formatter와 ConversionService의 관계

  • **ConversionService**는 다양한 타입 간의 변환을 관리하는 중심 인터페이스입니다.
  • Formatter는 **FormattingConversionService**에 등록되어 동작하며, 주로 사용자 입력값의 포맷팅 작업을 처리합니다.
  • 즉, Formatter는 ConversionService의 한 부분으로, 특정 포맷의 데이터를 처리할 때 사용됩니다.

4. Formatter 사용 방법

1) Formatter 구현

import org.springframework.format.Formatter;

import java.text.ParseException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class LocalDateFormatter implements Formatter<LocalDate> {

    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    @Override
    public LocalDate parse(String text, Locale locale) throws ParseException {
        return LocalDate.parse(text, FORMATTER); // 문자열 → LocalDate 변환
    }

    @Override
    public String print(LocalDate object, Locale locale) {
        return object.format(FORMATTER); // LocalDate → 문자열 변환
    }
}

2) Formatter 등록

Formatter를 사용하려면 스프링의 WebMvcConfigurer 또는 FormattingConversionService에 등록해야 합니다.

import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addFormatter(new LocalDateFormatter()); // Formatter 등록
    }
}

3) 컨트롤러에서 사용

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDate;

@RestController
public class MyController {

    @GetMapping("/date")
    public String getDate(@RequestParam("date") LocalDate date) {
        return "Parsed Date: " + date;
    }
}
  • 요청: /date?date=2025-01-01
  • 응답: Parsed Date: 2025-01-01

5. 주요 메서드

메서드 설명
parse(String text, Locale locale) 문자열을 주어진 로케일을 고려하여 특정 객체로 변환. 예: "01-23-2025" → LocalDate.
print(T object, Locale locale) 객체를 주어진 로케일을 고려하여 특정 포맷의 문자열로 변환. 예: LocalDate.of(2025, 1, 23) → "2025-01-23".

6. 정리 표

항목 Formatter
정의 문자열과 객체 간의 변환을 처리하는 인터페이스. 지역화(Localization)를 지원.
주요 기능 - 문자열 → 객체 변환 (parse)- 객체 → 문자열 변환 (print).
주요 사용 사례 날짜, 숫자, 통화 등의 포맷팅 및 변환.
주요 메서드 - parse(String text, Locale locale)- print(T object, Locale locale).
지역화 지원 지원 (Locale 기반 변환 가능).
Converter와의 차이 - Formatter는 문자열과 객체 간 변환에 특화되어 있으며 지역화 지원.- Converter는 단순 타입 간 변환에 사용.
등록 방식 FormatterRegistry 또는 FormattingConversionService에 등록.

7. Formatter와 Converter의 활용 예

상황 Formatter 사용 Converter 사용
HTTP 요청 파라미터 변환 사용자 입력 문자열을 특정 포맷의 객체로 변환. 문자열을 단순히 Java 객체로 변환.
HTTP 응답 출력 변환 객체를 지역화된 포맷의 문자열로 변환. 객체를 특정 포맷으로 변환하지 않고, 단순히 JSON 직렬화 등으로 사용.
예제 "2025-01-01" ↔ LocalDate 변환. "test-value" ↔ CustomType 변환.

8. 결론

  • Formatter문자열과 객체 간의 변환에 특화된 도구로, 지역화(Localization)를 고려한 데이터 포맷팅에 사용됩니다.
  • Converter는 더 일반적인 타입 간 변환을 처리하며, 지역화는 지원하지 않습니다.
  • ConversionService는 Formatter와 Converter를 통합 관리하며, 다양한 변환 작업을 처리하는 중심 인터페이스 역할을 합니다.
  • Formatter는 주로 날짜, 숫자, 통화와 같은 포맷된 데이터를 처리하는 데 적합하며, 사용자 입력값 변환과 출력 포맷팅 작업에서 유용합니다.

 

 

 

 

Spring 프로그래밍

Spring 웹 애플리케이션 계층 구조 (개발 구조, 만들 것들)

계층  책임  역할  주요 어노테이션
Controller (Web Layer) 클라이언트의 요청을 처리하고 Service 계층에 전달한 뒤 결과를 반환. - HTTP 요청 처리 (GET, POST 등)- Service 호출하여 비즈니스 로직 실행- View에 데이터 전달 또는 JSON 응답 반환. @Controller, @RestController, @GetMapping, @PostMapping
Service (Business Logic) 비즈니스 로직을 처리하고 Repository와 연계하여 데이터 처리. - 핵심 비즈니스 로직 수행- 여러 도메인 객체와의 연산- 데이터 조회/수정/삭제 요청 처리. @Service
Domain (Model Layer) 애플리케이션의 데이터를 모델링하고 처리할 객체를 정의. - 비즈니스 데이터를 표현 (e.g., User, Product 등)- 데이터베이스 테이블과 매핑되는 엔티티로 활용- 필요한 경우 DTO로 변환. @Entity, @Id, @GeneratedValue
Repository (Data Access) 데이터베이스와의 상호작용을 담당. - 데이터 조회, 저장, 수정, 삭제 처리- JPA 또는 MongoDB와 같은 ORM 기술로 데이터베이스 접근- Service 계층에서 요청한 데이터 반환. @Repository, JpaRepository
Database 데이터를 영구적으로 저장. - 데이터 저장, 조회, 수정, 삭제 처리- SQL 또는 NoSQL 데이터베이스 사용- 레포지토리를 통해 데이터 관리. N/A
View 사용자에게 결과를 응답. - Controller에서 받은 데이터를 UI로 렌더링- HTML, JSP, JSON 등 다양한 형식으로 결과 반환. (HTML, JSP 파일 등)
 

[Spring] Spring 웹 애플리케이션 계층 구조

Spring 웹 애플리케이션 계층 구조📌  계층 구조는 애플리케이션을 여러 층으로 나누어 각 계층의 책임을 분리하여 관리하는 구조입니다. 이렇게 계층화하면 유지보수성, 확장성, 테스트 용이성

kyunghun0515.tistory.com

 

 

 

Maven & Gradle

  Maven  Gradle
개요 Java 생태계에서 오래된 빌드 도구로, 표준화된 방식으로 프로젝트 빌드와 의존성 관리를 지원. 최신 빌드 도구로, 유연성과 속도를 강조하며 다양한 언어와 프로젝트에서 사용 가능.
설정 파일 pom.xml (XML 기반 선언적 설정) build.gradle (Groovy DSL) 또는 build.gradle.kts (Kotlin DSL).
철학 Convention Over Configuration: 기본 규칙과 디렉토리 구조를 따르며 설정을 최소화. Configuration Over Convention: 선언적 설정과 절차적 코드를 혼합해 유연한 빌드 제공.
사용 방식 선언형 설정 방식으로, XML로 의존성과 빌드 과정을 정의. 선언형 + 절차적 설정 방식으로, 코드 스타일 설정 가능 (복잡한 빌드 작업 지원).
의존성 관리 Maven Central 저장소에서 의존성 관리. Maven Central + 추가적인 커스텀 저장소 지원 (JCenter, Ivy 등).
빌드 속도 느림: 증분 빌드 미지원, 매번 전체 빌드. 빠름: 증분 빌드와 의존성 캐싱을 통해 빌드 시간 최적화.
유연성 제한적: 복잡한 커스터마이징 어려움. 매우 유연: 복잡한 빌드 논리 및 스크립트 작성 가능.
멀티 프로젝트 지원 지원 가능하지만 설정이 다소 복잡. 강력한 멀티 프로젝트 지원 (효율적인 모듈 간 의존성 관리).
확장성 플러그인으로 기능 확장 가능. 플러그인과 Groovy/Kotlin DSL로 무한히 확장 가능.
학습 곡선 쉬움: XML로 표준화된 설정이 직관적이고 학습 용이. 가파름: Groovy/Kotlin DSL 학습 필요.
생태계 Maven Central과 잘 통합되어 라이브러리 접근이 용이. Maven Central 외에도 다양한 저장소와 통합 가능.
표준화 정도 높음: 표준 디렉토리 구조와 빌드 절차를 따름. 낮음: 디렉토리 구조와 빌드 방식이 팀/프로젝트별로 달라질 수 있음.
문서화와 커뮤니티 지원 오래된 도구로, 문서와 커뮤니티 자료가 풍부. 빠르게 성장하는 도구로, 강력한 커뮤니티와 점진적으로 풍부해지는 자료.
사용 사례 전통적인 Java/Kotlin 프로젝트, 간단하고 표준화된 빌드가 필요한 프로젝트. 대규모 프로젝트, 복잡한 빌드 요구사항, 다중 모듈 프로젝트, 빌드 속도가 중요한 프로젝트.

Maven & Gradle 사용 상황 비교

  Maven 적합 Gradle 적합
프로젝트 규모 소규모 또는 단순한 프로젝트. 대규모, 복잡한 구조의 프로젝트.
빌드 속도 속도가 크게 중요하지 않은 경우. 빌드 속도가 중요한 경우 (증분 빌드, 캐싱 필요).
구조 표준화 표준 디렉토리 구조와 일관된 방식이 필요한 경우. 팀별로 유연한 디렉토리 구조와 커스텀 빌드가 필요한 경우.
사용자 친화성 초보 개발자나 팀원이 많아 학습 곡선을 낮추는 것이 중요한 경우. 팀이 Groovy/Kotlin DSL에 익숙하고, 복잡한 빌드를 설계할 필요가 있는 경우.
의존성 관리 Maven Central의 의존성 관리가 충분한 경우. Maven Central 외 커스텀 저장소와의 통합이 필요한 경우.
멀티 프로젝트 모듈 간 의존성이 적은 단순한 멀티 프로젝트. 복잡한 모듈 구조의 대규모 멀티 프로젝트.

주요 결론

  • Maven단순성표준화를 추구하며, 간단한 프로젝트나 학습용으로 적합합니다.
  • Gradle유연성속도를 중시하며, 대규모 프로젝트나 복잡한 빌드 작업에 유리합니다.
빌드 도구는 소프트웨어 개발 과정에서 코드 컴파일, 테스트, 의존성 관리, 패키징 등 반복적인 작업을 자동화하여 개발 효율성을 높이는 도구입니다

 

 

Spring Annotation

항목  설명
어노테이션(Annotation)이란? 코드에 메타데이터를 제공하여 컴파일러, 개발 툴, 런타임 환경에 정보를 전달하는 주석 형태의 기술.
주요 용도 - 컴파일러에 문법 에러 체크 정보 제공.- 코드 자동 생성 지원.- 런타임에 특정 기능 실행 정보 제공.
Reflection이란? 실행 중에 클래스, 필드, 메서드 정보를 동적으로 분석하거나 수정할 수 있는 기능으로, 어노테이션 정보를 활용하는 데 사용.
어노테이션 사용 단계 1. 어노테이션 정의2. 어노테이션 적용3. Reflection으로 어노테이션 정보 획득 및 기능 실행.

주요 어노테이션 분류

종류  대표 어노테이션 설명 및 용도
Spring 기본 @Component, @Bean - @Component: 클래스 자동 Bean 등록.- @Bean: 개발자가 직접 제어 불가능한 외부 라이브러리를 Bean으로 등록.
의존성 주입 @Autowired, @Qualifier - @Autowired: 타입에 따라 Bean 자동 주입.- @Qualifier: 동일한 타입의 Bean 중 특정 이름 지정.
Spring MVC @Controller, @RestController - @Controller: View 반환.- @RestController: JSON, XML 등 데이터 반환.
요청 처리 @RequestMapping, @GetMapping - 요청 URL과 메서드를 매핑. @RequestMapping은 GET, POST 등 지정 가능.- 단축형: @GetMapping, @PostMapping.
데이터 처리 @RequestParam, @PathVariable - @RequestParam: 요청 파라미터 매핑.- @PathVariable: URL 경로 변수 매핑.
트랜잭션 @Transactional - 데이터베이스 트랜잭션 관리. 성공 시 커밋, 실패 시 롤백.
유효성 검사 @Valid - 입력 데이터의 유효성 검사 수행.
캐싱 @Cacheable, @CacheEvict - @Cacheable: 메서드 실행 결과를 캐시에 저장.- @CacheEvict: 캐시 데이터 삭제.
스프링 부트 설정 @SpringBootApplication - @Configuration, @EnableAutoConfiguration, @ComponentScan의 복합 어노테이션.

Lombok 어노테이션

어노테이션 설명
@Getter, @Setter 클래스의 모든 필드에 Getter/Setter 자동 생성.
@ToString 클래스의 toString 메서드 자동 생성.
@EqualsAndHashCode equals 및 hashCode 메서드 자동 생성.
@Builder 빌더 패턴 생성.
@NoArgsConstructor 기본 생성자 자동 생성.
@AllArgsConstructor 모든 필드 값을 파라미터로 받는 생성자 자동 생성.
@Slf4j SLF4J 기반 로깅 객체 자동 생성.

Spring JPA 어노테이션

어노테이션 설명
@Entity 클래스가 데이터베이스 테이블과 매핑됨을 표시.
@Table 매핑할 테이블 정보 지정 (예: @Table(name = "users")).
@Id 테이블의 Primary Key 지정.
@GeneratedValue PK의 생성 규칙 지정 (예: AUTO_INCREMENT).
@Column 테이블의 컬럼을 나타내며, 추가 옵션 설정 가능 (예: @Column(name = "username")).

기타 어노테이션

어노테이션  설명
@RequestBody, @ResponseBody - @RequestBody: 요청 데이터를 객체로 매핑.- @ResponseBody: 메서드 반환값을 HTTP 응답 본문으로 전달.
@SessionAttributes 세션에 데이터를 저장.
@ModelAttribute 요청 데이터를 객체에 바인딩.
@CrossOrigin CORS(Cross-Origin Resource Sharing) 요청 허용.
@PostConstruct, @PreDestroy Bean 초기화/소멸 직전에 실행될 메서드 지정.

장점과 주의점

장점  설명
코드 간소화 보일러플레이트 코드 제거로 가독성과 유지보수성 향상.
유연성 증가 Reflection을 활용해 어노테이션 기반으로 유연한 동작 구현 가능.
자동화 지원 컴파일 타임 또는 런타임에 자동으로 설정/처리 가능.

 

주의점  설명
기술 의존성 특정 프레임워크에 종속될 가능성.
가독성 저하 어노테이션의 동작 원리를 모르면 코드 이해가 어려움.
성능 이슈 Reflection 사용 시 성능 저하 우려.

[참고] 

 

[Spring] Annotation 정리

Annotation(@)은 사전적 의미로는 주석이라는 뜻이다. 자바에서 사용될 때의 Annotation은 코드 사이에 주석처럼 쓰여서 특별한 의미, 기능을 수행하도록 하는 기술이다.

velog.io

 

 

Lombok

항목  설명
Lombok이란? Java에서 **보일러플레이트 코드(반복적 코드)**를 줄여주는 라이브러리로, 코드 간결성과 가독성을 높임.
주요 기능 Getter/Setter, toString, equals, hashCode, 생성자, 빌더 패턴 등 반복 코드를 자동 생성.
주요 어노테이션 - @Getter, @Setter: 필드에 Getter/Setter 생성.- @ToString: toString 메서드 생성.- @Data: Getter/Setter, equals, toString 등 종합 기능 제공.- @Builder: 빌더 패턴 생성.- @Slf4j: SLF4J 기반 로거 생성.
장점 - 코드 간소화: 반복 코드를 줄여 핵심 로직에 집중 가능.- 가독성 향상: 코드가 짧고 명확해짐.- 유지보수성 개선: 코드 중복 감소로 수정 용이.
주의사항 - IDE 의존성: IDE와 빌드 환경이 Lombok 지원 필요.- 팀 협업: 팀원이 Lombok에 익숙하지 않으면 코드 리뷰 어려움.- Java 표준 아님: 프로젝트 정책에 따라 사용 제한될 수 있음.
대표적인 활용 사례 - 엔티티 클래스에 Getter/Setter 자동 생성.- 빌더 패턴으로 객체 생성 시 코드 간소화.- 로깅에 @Slf4j 사용.

Slf4j와 Lombok의 로깅

항목 설명
Slf4j란? Java의 로깅 인터페이스로, 다양한 로깅 구현체(Logback 등)와 통합 가능. Lombok의 @Slf4j로 로거 객체 자동 생성.
Logback과 관계 Logback은 Slf4j의 구현체 중 하나로, Spring Boot에서 기본 로깅 프레임워크로 사용.
Log Level 로그의 심각도를 나눠 관리 (TRACE > DEBUG > INFO > WARN > ERROR).
장점 - 로그로 디버깅 및 모니터링 가능.- Log Level 설정으로 출력 내용 제어.- 로그 파일 관리로 운영 환경에서 활용 가능.

@Controller vs @RestController

  @Controller @RestController
주요 용도 View(HTML, JSP) 반환 RESTful API 개발 (JSON, XML 데이터 반환)
기본 반환 타입 View 이름 (문자열) 데이터 자체 (JSON 등)
View Resolver View Resolver를 통해 뷰 파일 매핑 사용하지 않음
결합 어노테이션 단일 어노테이션 @Controller + @ResponseBody 결합
사용 사례 페이지 기반 애플리케이션 (Thymeleaf, JSP 등 템플릿 엔진 사용) 데이터 API 제공 (JSON, XML 형식의 응답 제공)

Spring MVC와 Lombok의 활용

항목  설명
Spring MVC에서 Lombok - 엔티티 클래스에서 Getter/Setter 자동 생성.- 빌더 패턴으로 API 요청 객체 생성 간소화.- @Slf4j로 로깅 간편화.
Lombok의 장점 - Spring MVC의 엔티티나 DTO에서 반복 코드를 줄여 생산성 향상.- 코드 간결성과 유지보수성 증대.

결론

  • Lombok코드 간소화와 가독성 향상에 특화된 Java 라이브러리로, Spring MVC와 결합 시 생산성을 극대화합니다.
  • Slf4jLogback은 로깅 관리에서 핵심적인 역할을 하며, Lombok의 @Slf4j로 더욱 간단히 구현 가능합니다.
  • @Controller는 뷰 기반 애플리케이션에, @RestController는 데이터 중심 RESTful API 개발에 적합합니다.
 

[Spring] Spring Annotation

Lombok📌 Java의 보일러플레이트 코드(반복적이고 지루한 코드)를 줄여주는 라이브러리입니다.간단한 어노테이션으로 getter, setter, equals, hashCode, toString, logger 생성 등을 자동으로 생성하여 코드의

kyunghun0515.tistory.com

 

[주요 어노테이션 정리]

더보기

Request Mapping

Spring @RequestMapping 및 관련 어노테이션 요약

항목  설명
@RequestMapping 클라이언트의 요청 URL과 컨트롤러 클래스/메서드를 매핑하는 데 사용. 특정 URL과 HTTP 메서드(GET, POST 등)를 조합하여 요청 처리.
주요 기능 - 요청 URL 매핑- HTTP 메서드 지정- 요청 조건 추가 (헤더, 파라미터 등).
사용 범위 클래스 레벨과 메서드 레벨에 적용 가능. 클래스 레벨에서 URL 공통 경로를 지정하고, 메서드 레벨에서 세부 요청 경로 처리.
대체 어노테이션 - @GetMapping, @PostMapping, @PutMapping, @DeleteMapping 등으로 세분화된 요청 처리 가능.

@RequestMapping vs Handler Adapter

항목  @RequestMapping Handler Adapter
역할 요청 URL과 컨트롤러 메서드 매핑. DispatcherServlet과 핸들러(컨트롤러) 연결 및 실행.
동작 방식 어노테이션 기반 메타데이터로 URL 처리 로직 지정. 요청을 실행 가능한 형태로 변환 후 핸들러 실행.
사용 주체 개발자가 컨트롤러 메서드에 명시적으로 사용. Spring 내부에서 동작하며, 개발자가 직접 호출하지 않음.

@RequestMapping 주요 속성

속성  설명  예시
value 매핑할 URL 경로. @RequestMapping("/users")
method 허용할 HTTP 메서드 지정 (GET, POST 등). method = RequestMethod.GET
params 특정 요청 파라미터 조건. params = "gender=man"
headers 특정 요청 헤더 조건. headers = "Content-Type=application/json"
consumes 요청 Content-Type 조건. consumes = "application/json"
produces 응답 Content-Type 조건. produces = "application/json"

@PathVariable

항목  설명
기능 URL 경로에 포함된 변수를 메서드 매개변수로 전달. 동적 요청 처리에 사용.
사용 예시 @GetMapping("/users/{id}") → @PathVariable("id")으로 경로 변수 매핑.
다중 변수 지원 여러 경로 변수 매핑 가능 (e.g., @PathVariable("postId"), @PathVariable("commentId")).
속성 규칙 매핑 변수명과 메서드 파라미터 이름이 같다면 @PathVariable 속성 생략 가능.

특정 파라미터/헤더/MediaType 매핑

속성 설명  예시
파라미터 매핑 (params) 특정 파라미터와 매핑. params = "gender=man"
헤더 매핑 (headers) 특정 요청 헤더와 매핑. headers = "Content-Type=application/json"
컨텐츠 타입 (consumes) 요청 Content-Type 조건 설정. consumes = "application/json"
응답 타입 (produces) 요청 Accept 헤더에 따라 응답 Content-Type 설정. produces = "application/json"

HTTP 헤더 및 요청 정보 조회

항목  설명  사용 예시
HttpServletRequest 요청 정보 접근 (URL, 헤더, 파라미터 등). HttpServletRequest request
HttpServletResponse 응답 정보 설정. HttpServletResponse response
@RequestHeader 특정 요청 헤더 조회. @RequestHeader("host") String host
@CookieValue 쿠키 값 조회. @CookieValue("cookieName") String cookie
HttpMethod 요청 메서드 조회. HttpMethod method
Locale 요청의 로케일 정보 조회. Locale locale

장점과 주의사항

장점  설명
유연성 URL, 메서드, 헤더, 파라미터 등 다양한 조건에 따른 요청 매핑 가능.
가독성 @GetMapping, @PostMapping 등 축약형 어노테이션으로 코드 가독성 향상.

 

주의사항  설명
호환성 Spring Boot 3.0부터는 URL 경로 매핑 방식이 변경되었으므로 버전에 따라 호환성 확인 필요.
정확한 조건 지정 파라미터나 헤더 조건 설정 시 누락된 값이 있을 경우 400 또는 404 에러 발생.

결론

  • @RequestMapping은 Spring MVC에서 가장 기본적인 요청 매핑 어노테이션으로, URL, 메서드, 헤더, 파라미터 등을 조합하여 세밀한 요청 처리가 가능.
  • @GetMapping과 같은 축약형 어노테이션을 사용하면 더 간결하고 직관적인 코드를 작성할 수 있음.
  • URL 경로 변수 매핑 시에는 @PathVariable을 활용하여 동적인 요청을 처리.
  • 추가 조건이 필요한 경우 params, headers, consumes, produces 등을 조합하여 세밀한 요청 조건 처리 가능.

 

[Spring] Request Mapping

@RequestMapping📌클라이언트의 요청 URL과 이를 처리할 컨트롤러의 클래스나 메서드를 매핑하는 데 사용됩니다.특정 URL로 Request를 보내면 들어온 요청을 Controller 내부의 특정 Method와 Mapping 하기 위

kyunghun0515.tistory.com



쿼리 파라미터(Query Parameter) 및 폼 데이터(Form Data) 요약

  쿼리 파라미터 (Query Parameter) 폼 데이터 (Form Data)
데이터 위치 URL 경로 끝 (e.g., /search?key=value). HTTP 요청 본문(POST) 또는 URL 쿼리(GET).
HTTP 메서드 주로 GET 요청에서 사용. 주로 POST 요청에서 사용.
노출 여부 URL에 노출. POST 요청 시 본문에 포함되어 상대적으로 안전.
용도 짧고 간단한 데이터 전달. 사용자 입력 데이터를 서버로 전달.
보안성 민감한 데이터에 부적합. HTTPS를 통해 상대적으로 안전.
데이터 크기 제한 브라우저의 URL 길이 제한(2,000~8,000자). 요청 본문을 사용하므로 데이터 크기 제한이 상대적으로 적음.

@RequestParam

항목 설명
기능 HTTP 요청의 쿼리 파라미터(Query Parameter)나 HTML 폼 데이터(Form Data)를 매개변수로 매핑.
주요 속성 - name: 요청 파라미터 이름.- required: 필수 여부 지정 (true 기본값).- defaultValue: 기본값 설정.
사용 사례 단일 Key-Value 데이터 처리.
주의사항 - 필수 파라미터 없을 시 에러 발생(required = false 또는 defaultValue 사용).- 타입 변환 실패 시 예외 발생.

@ModelAttribute

항목 설명
기능 요청 데이터를 객체로 바인딩하거나 뷰에 데이터를 전달.
사용 사례 복잡한 폼 데이터나 여러 Key-Value 데이터를 객체로 매핑.
특징 - 필드 이름과 요청 데이터 Key가 동일해야 바인딩 가능.- @Valid와 함께 사용하여 데이터 검증 가능.- 생략 가능.
주요 속성 - value: 모델 객체 이름 지정.- 데이터 바인딩과 뷰 전달 모두 가능.

쿼리 파라미터와 폼 데이터 비교

항목 @RequestParam @ModelAttribute
목적 단일 파라미터 처리. 요청 데이터를 객체로 매핑.
적용 대상 단일 Key-Value 데이터. 객체로 표현 가능한 복잡한 데이터 구조.
데이터 출처 쿼리 파라미터 또는 폼 데이터. 폼 데이터, 쿼리 파라미터, 경로 변수 등.
뷰 데이터 전달 불가능. 가능.
생략 가능 여부 생략 불가능. 생략 가능.

HTTP Message Body 처리

항목 설명
@RequestBody HTTP 요청 본문 데이터를 객체로 변환하여 매핑 (JSON, XML, TEXT).
@ResponseBody 컨트롤러 메서드 반환값을 HTTP 응답 본문에 직렬화하여 전달 (JSON, XML, TEXT).
HttpEntity HTTP 요청 및 응답의 헤더와 본문을 캡슐화하여 관리 (요청 및 응답 모두 사용 가능).

HTTPMessageConverter

| 역할 | HTTP 요청 본문 데이터를 Java 객체로 변환(@RequestBody)하거나, Java 객체를 JSON, XML 등으로 직렬화하여 응답(@ResponseBody). |

| 주요 구현체 | - MappingJackson2HttpMessageConverter (JSON 처리).- StringHttpMessageConverter (문자열 처리). |

| 장점 | - 자동화된 데이터 변환.- 다양한 데이터 형식(JSON, XML 등) 지원.- 확장 가능. |


결론

  1. 쿼리 파라미터는 단순 데이터를 전달할 때 적합하며, GET 요청에서 주로 사용.
  2. 폼 데이터는 사용자 입력 데이터를 처리하며, POST 요청에서 주로 사용.
  3. @RequestParam은 단일 Key-Value 데이터를 처리할 때 사용.
  4. @ModelAttribute는 폼 데이터나 객체로 매핑 가능한 복잡한 데이터를 처리할 때 적합.
  5. @RequestBody는 RESTful API 요청 본문 데이터를 객체로 변환.
  6. HTTPMessageConverter는 요청 및 응답 데이터를 자동으로 직렬화/역직렬화하여 개발 편의성을 높임.
 

[Spring] @RequestParam & @ModelAttribute

쿼리 파라미터(Query Parameter)📌 HTTP 요청 URL의 일부로, Key-Value 형태로 데이터를 전달하는 방법 쿼리 파라미터는 URL 경로 끝에 **?**로 시작하며, 여러 개의 파라미터는 **&**로 구분됩니다.구조?=&= 

kyunghun0515.tistory.com

 

 

필터(Filter)

 

1. 필터란?

**필터(Filter)**는 클라이언트의 요청(request)와 서버의 응답(response) 사이에서 요청 처리 전/후에 특정 작업을 수행할 수 있는 기능을 제공합니다.
필터는 주로 HTTP 요청과 응답을 가로채어 조작하거나, 요청이 컨트롤러에 도달하기 전에 사전 작업을 수행하는 데 사용됩니다.


2. 필터의 주요 사용 사례

  1. 인증 및 권한 검사:
    • 요청이 적합한 사용자로부터 왔는지 확인하거나, 요청의 권한을 검증.
  2. 로깅 및 모니터링:
    • 요청 및 응답 데이터를 기록하여 애플리케이션의 동작을 추적.
  3. 데이터 변환/압축:
    • 요청 데이터 변환 또는 응답 데이터 압축.
  4. CORS 처리:
    • Cross-Origin Resource Sharing(CORS) 요청 처리.
  5. 보안 작업:
    • CSRF(Cross-Site Request Forgery) 방지 토큰 검증, 헤더 조작 방지 등.

3. 필터의 특징

  1. 서블릿 컨테이너에서 동작:
    • 필터는 서블릿 컨테이너(Servlet Container, 예: Tomcat)에서 관리됩니다.
    • 스프링이 제공하는 Filter 인터페이스는 서블릿 표준(javax.servlet.Filter)을 확장한 것입니다.
  2. 전역적으로 동작:
    • 컨트롤러에 도달하기 전에 모든 요청/응답에 대해 동작합니다.
  3. 체인 방식:
    • 여러 필터가 등록되어 있는 경우, 필터 체인(Filter Chain)을 통해 순서대로 실행됩니다.

4. 필터 구현 방법

1) 기본 필터 구현

javax.servlet.Filter 인터페이스를 구현하여 필터를 작성합니다.

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

// 모든 요청에 대해 필터 적용
@WebFilter("/*")
public class LoggingFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 초기화 작업 (필요 시 구현)
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 요청 처리 전
        System.out.println("Request received at: " + request.getRemoteAddr());

        // 다음 필터 또는 서블릿 호출
        chain.doFilter(request, response);

        // 응답 처리 후
        System.out.println("Response processed");
    }

    @Override
    public void destroy() {
        // 리소스 해제 작업 (필요 시 구현)
    }
}
  • doFilter 메서드:
    • 요청이 컨트롤러로 전달되기 전, 특정 작업을 수행합니다.
    • chain.doFilter(request, response)를 호출하여 다음 필터 또는 서블릿으로 요청을 전달합니다.
    • chain.doFilter 이후 코드는 응답이 생성된 후 실행됩니다.

2) 스프링 부트에서 필터 등록

스프링 부트에서는 @Bean을 사용해 필터를 등록할 수 있습니다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;

@Configuration
public class FilterConfig {

    @Bean
    public Filter loggingFilter() {
        return new LoggingFilter();
    }
}

5. 필터 체인(Filter Chain)

필터는 여러 개 등록될 수 있으며, 등록 순서에 따라 체인 방식으로 동작합니다.

  • 필터가 호출될 때, FilterChain을 통해 다음 필터로 요청을 전달합니다.
  • 최종적으로 컨트롤러나 서블릿에 도달하며, 응답은 필터 체인을 역순으로 통과합니다.

6. 필터의 실행 순서 지정

스프링 부트에서는 @Order 또는 FilterRegistrationBean을 사용해 필터의 실행 순서를 지정할 수 있습니다.

1) @Order로 순서 지정

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(1) // 낮은 숫자가 먼저 실행됨
public class FirstFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("First Filter - Before");
        chain.doFilter(request, response);
        System.out.println("First Filter - After");
    }
}

2) FilterRegistrationBean으로 순서 지정

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<LoggingFilter> loggingFilter() {
        FilterRegistrationBean<LoggingFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new LoggingFilter());
        registrationBean.setOrder(2); // 순서 지정 (낮은 숫자가 먼저 실행)
        registrationBean.addUrlPatterns("/*"); // 필터를 적용할 URL 패턴
        return registrationBean;
    }
}

7. 예제: 인증 필터

JWT 인증 필터

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class JwtAuthFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        String authHeader = httpRequest.getHeader("Authorization");

        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            httpResponse.getWriter().write("Unauthorized");
            return;
        }

        String token = authHeader.substring(7); // "Bearer " 이후의 토큰 추출
        if (!validateToken(token)) {
            httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            httpResponse.getWriter().write("Invalid token");
            return;
        }

        chain.doFilter(request, response); // 다음 필터로 요청 전달
    }

    private boolean validateToken(String token) {
        // 토큰 검증 로직 구현
        return "valid-token".equals(token);
    }
}

설정:

@Bean
public FilterRegistrationBean<JwtAuthFilter> jwtAuthFilter() {
    FilterRegistrationBean<JwtAuthFilter> registrationBean = new FilterRegistrationBean<>();
    registrationBean.setFilter(new JwtAuthFilter());
    registrationBean.addUrlPatterns("/secure/*"); // 특정 URL 패턴에만 적용
    registrationBean.setOrder(1);
    return registrationBean;
}

8. 정리

항목 설명
정의 클라이언트 요청과 서버 응답 사이에서 특정 작업을 수행하는 기능.
주요 사용 사례 인증/권한, 로깅, 요청/응답 데이터 변환, CORS 처리, 보안 검사.
동작 방식 요청 → 필터 체인 → 컨트롤러 → 응답 (필터 체인 역순으로 실행).
등록 방법 1. @WebFilter2. FilterRegistrationBean3. @Bean.
순서 지정 방법 @Order 또는 FilterRegistrationBean의 setOrder 메서드.
장점 전역적으로 요청/응답 처리 가능, 공통 로직 분리, 강력한 조작 기능 제공.
단점 컨트롤러 단위로 작동하지 않으므로 세부 로직 구현 시 코드가 복잡해질 수 있음.

9. 결론

필터는 HTTP 요청과 응답을 처리하는 강력한 전처리/후처리 메커니즘으로, 인증, 로깅, 데이터 조작 등 다양한 작업을 수행할 수 있습니다.
스프링 부트에서는 간단한 설정으로 필터를 등록하고, 순서를 지정하여 유연하게 요청/응답 처리를 관리할 수 있습니다.
필터는 전역적으로 동작하기 때문에, 컨트롤러 단위보다 요청 흐름의 초기 단계에서 작업을 수행할 때 적합합니다.

 

API 예외처리

  • API 예외처리는 클라이언트와 서버 간의 상호작용 중 발생할 수 있는 오류를 처리하고, 클라이언트에 일관된 오류 응답을 제공하기 위한 방법입니다.

1. API 예외처리의 목표

  1. 일관성:
    • 클라이언트가 API의 오류를 쉽게 이해하고 처리할 수 있도록, 표준화된 형식의 오류 응답 제공.
  2. 가독성:
    • 명확한 오류 메시지와 상태 코드 전달로 디버깅과 문제 해결을 간소화.
  3. 보안:
    • 민감한 서버 내부 정보를 클라이언트에 노출하지 않음.

2. 기본적인 예외 처리 방식

1) @ExceptionHandler 사용

  • 특정 컨트롤러에서 발생한 예외를 처리하기 위한 메서드를 지정합니다.
  • 단일 컨트롤러 내에서 예외를 처리한 후
  • 계층별로 알맞은 예외를 발생(throw new)시키기만 하면됩니다.

 

스프링 MVC에서 컨트롤러 단위로 예외를 처리할 수 있습니다.

import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.http.ResponseEntity;

@RestController
public class MyController {

    @GetMapping("/example")
    public String example() {
        if (true) {
            throw new IllegalArgumentException("Invalid input!");
        }
        return "Success";
    }

    // 특정 예외 처리
    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException e) {
        return ResponseEntity.badRequest().body("Error: " + e.getMessage());
    }
}

결과:

  • 요청: GET /example
  • 응답: 
  • { "error": "Error: Invalid input!" }
  • 문제점
    1. Controller 코드에 Exception 처리를 위한 책임이 추가된다.(단일 책임 원칙 위반)
    2. 단일 컨트롤러 내의 예외만 처리가 가능하다.(컨트롤러 예외처리 중복코드)
    3. 코드 재사용, 유지보수성 부족
CustomException 사용자 정의 Exception을 만들어서 처리할 수 있다.

2) @ControllerAdvice 사용

@ControllerAdvice는 애플리케이션 전역에서 예외를 처리할 수 있도록 해줍니다.
컨트롤러마다 예외 처리 로직을 반복하지 않아도 됩니다.

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException e) {
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Error: " + e.getMessage());
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleGeneralException(Exception e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Unexpected error occurred!");
    }
}

결과:

  • 모든 컨트롤러에서 발생하는 예외를 처리할 수 있습니다.
  • 예외마다 별도의 처리 로직을 정의할 수 있습니다.

3) ResponseEntityExceptionHandler 상속

스프링의 기본 예외 처리 기능을 확장하여 API 예외 처리를 커스터마이징할 수 있습니다.

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@ControllerAdvice
public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body,
                                                             HttpHeaders headers, HttpStatus status, WebRequest request) {
        return new ResponseEntity<>(new ErrorResponse("INTERNAL_ERROR", ex.getMessage()), status);
    }

    // 사용자 정의 예외 처리
    @ExceptionHandler(CustomException.class)
    public ResponseEntity<Object> handleCustomException(CustomException ex) {
        return new ResponseEntity<>(new ErrorResponse("CUSTOM_ERROR", ex.getMessage()), HttpStatus.BAD_REQUEST);
    }
}

class ErrorResponse {
    private String code;
    private String message;

    public ErrorResponse(String code, String message) {
        this.code = code;
        this.message = message;
    }

    // Getters and setters
}

3. 응답 형식 표준화

JSON 형식의 응답을 사용하여 클라이언트가 오류를 쉽게 이해하도록 일관된 구조를 제공합니다.

표준 오류 응답 예시

{
  "timestamp": "2025-01-01T12:34:56",
  "status": 400,
  "error": "Bad Request",
  "message": "Invalid input",
  "path": "/example"
}

오류 응답 데이터 클래스

import java.time.LocalDateTime;

public class ApiError {
    private LocalDateTime timestamp;
    private int status;
    private String error;
    private String message;
    private String path;

    public ApiError(int status, String error, String message, String path) {
        this.timestamp = LocalDateTime.now();
        this.status = status;
        this.error = error;
        this.message = message;
        this.path = path;
    }

    // Getters and setters
}

ControllerAdvice에서 응답 사용

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<ApiError> handleIllegalArgumentException(IllegalArgumentException e, WebRequest request) {
        ApiError error = new ApiError(
                HttpStatus.BAD_REQUEST.value(),
                "Bad Request",
                e.getMessage(),
                request.getDescription(false)
        );
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
    }
}

4. RestController와 연동

  • @RestController와 @ControllerAdvice를 함께 사용하면, REST API에서 발생하는 모든 예외를 처리할 수 있습니다.
  • 모든 응답은 JSON 형식으로 변환됩니다.

5. 정리 표

방법 설명  장점 단점
@ExceptionHandler 특정 컨트롤러에서 발생하는 예외를 처리. 간단한 예외 처리 가능. 컨트롤러별로 중복 처리 필요.
@ControllerAdvice 애플리케이션 전역에서 발생하는 예외를 처리. 재사용 가능, 코드 중복 제거. 모든 예외에 대해 커스터마이징 필요.
ResponseEntityExceptionHandler 스프링의 기본 예외 처리 기능을 확장. 기본 예외 처리 로직 활용 가능. 복잡한 설정 및 메서드 오버라이딩 필요.
ResponseEntity 사용 API 응답을 JSON 형식으로 일관되게 제공. 클라이언트와의 통신 일관성 유지. 응답 데이터 클래스 설계 필요.

6. 결론

  • API 예외 처리는 클라이언트와의 통신에서 중요한 요소로, 오류가 발생했을 때 클라이언트가 이를 쉽게 이해하고 적절히 처리할 수 있도록 돕습니다.
  • 스프링에서는 @ControllerAdvice, @ExceptionHandler, ResponseEntityExceptionHandler와 같은 도구를 제공하여 효과적으로 예외를 처리하고, 응답을 일관되게 관리할 수 있습니다.
  • 표준화된 JSON 응답 형식을 사용하면 클라이언트와 서버 간의 협업이 더 원활해집니다.

 

 

📌 

  •  

 

 

 

📌 

  •  

 

 

 

📌 

  •  

 

 

 

📌 

  •  

 

 

 

📌 

  •  

 

 

 

📌 

  •  

 

 

 

📌 

  •  

 

 

 

📌 

  •  

 

 

 

📌 

  •  

 

 

 

📌 

  •  

 

 

 

📌 

  •  

 

 

 

📌 

  •  

 

 

 

📌 

  •  

 

 

 

📌 

  •  

 

 

 

📌 

  •  

 

 

 

📌 

  •  

 

 

 

📌 

  •  

 

 

 

📌 

  •  

 

 

 

📌 

  •  

 

 

 

📌 

  •  

 

 

 

📌 

  •  

 

 

 

📌 

  •  

 

 

 

📌 

  •  

 

 

 

📌 

  •  

 

 

 

📌 

  •  

 

 


 

 

 🐳 

  •  

 

 

 

 🐳 

  •  

 

 

 

 🐳 

  •  

 

 


 

소제목 

🧩 부모 타입 변수 = 자식 타입 객체; 는 자동으로 부모 타입으로 변환이 일어납니다.

 


 

소제목 

🎵 클래스가 설계도라면 추상 클래스는 미완성된 설계도입니다.

'Back-End (Web) > Spring' 카테고리의 다른 글

HttpMessageConverter  (0) 2025.01.09
ArgumentResolver  (0) 2025.01.08
[Spring] 의존관계 주입  (0) 2024.12.26
[Spring] Bean 등록  (0) 2024.12.25
[Spring] Spring의 핵심 개념  (0) 2024.12.24

의존관계 주입

📌 객체 간의 의존성을 스프링 컨테이너가 자동으로 관리하고 주입해주는 설계 패턴입니다. 객체가 다른 객체를 필요로 할 때, 직접 생성하지 않고 외부에서 주입받아 사용하도록 설계하는 방식입니다.

  • @Autowired 는 의존성을 자동으로 주입할 때 사용하는 Annotation 이다.
    • 기본적으로 주입할 대상이 없으면 오류가 발생한다.(required = true)

 

의존관계 주입의 기본 개념

  1. 의존성(Dependency):
    • A 객체가 B 객체를 사용해야 한다면, A는 B에 의존하고 있다고 말합니다.
    • 이때 B 객체를 A 내부에서 직접 생성하지 않고, 외부에서 주입받는 것을 의존관계 주입이라고 합니다.
  2. 스프링 DI:
    • 스프링 컨테이너가 객체(빈)를 생성하고 관리하면서 필요한 의존성을 자동으로 주입합니다.
    • 개발자는 직접 의존성을 생성하거나 연결할 필요가 없으며, 컨테이너가 이를 처리합니다.

 

왜 의존성 주입인가?

@Autowired와 스프링 컨테이너가 있기 때문에, 개발자가 의존 객체를 직접 생성하지 않아도 되고, 컨테이너가 이를 대신 처리합니다. 이 점이 의존성 주입의 핵심입니다.

장점

  1. 결합도 감소:
    • MyApp은 MyService의 구체적인 구현체를 몰라도 됩니다. (DIP 원칙 준수)
    • 다른 구현체로 변경할 때 코드 수정이 필요 없습니다.
  2. 유연성 증가:
    • 스프링 컨테이너에서 주입받는 객체를 쉽게 교체하거나 확장할 수 있습니다.
  3. 테스트 용이성:
    • 테스트 환경에서 Mock 객체를 주입할 수 있습니다.

 

1. 생성자 주입

  • 생성자를 통해 의존성을 주입하는 방법.
  • 최초에 한번 생성된 후 값이 수정되지 못한다.[불변, 필수]
public interface MyService {
    void doSomething();
}

// Spring Bean으로 등록
@Service
public class MyServiceImpl implements MyService {
    @Override
    public void doSomething() {
        System.out.println("MyServiceImpl 메서드 호출");
    }
}

// 생성자 주입 방식
@Component
public class MyApp {
		// 필드에 final 키워드 필수! 무조건 값이 있도록 만들어준다.(필수)
    private final MyService myService;

		// 생성자를 통해 의존성 주입, 생략 가능
    @Autowired
    public MyApp(MyService myService) {
        this.myService = myService;
    }

    public void run() {
        myService.doSomething();
    }
    
}

@ComponentScan(basePackages = "com.example.springdependency.test")
public class Main {
    public static void main(String[] args) {

        ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);

        // 등록된 MyApp 빈 가져오기
        MyApp myApp = context.getBean(MyApp.class);

        // 빈 메서드 호출
        myApp.run();
    }
}

-------------------------------------

// 생성자가 두개인 경우 생략이 불가능하다.
@Component
public class MyApp {
		// 필드에 final 키워드 필수! 무조건 값이 있도록 만들어준다.(필수)
    private final MyService myService;
    
    public MyApp(MyService myService, String myRepository) {
        this.myService = myService;
    }

		// 생성자를 통해 의존성 주입
    // @Autowired를 생략하기 위해서는 생성자가 하나여야 한다.
    public MyApp(MyService myService) {
        this.myService = myService;
    }

    public void run() {
        service.doSomething();
    }
    
}
  • 생성자가 하나인 경우 @Autowired 생략이 가능하다.
  • 둘중 어떤 생성자를 사용해야 하는지 Spring은 알지 못한다.

 

 

2. Setter 주입

  • Setter 메서드를 통해 의존성을 주입하는 방법.
@Component
public class MyApp {

    private MyService myService;

    // Setter 주입
    @Autowired
    public void setMyService(MyService myService) {
        this.myService = myService;
    }
    
    public void run() {
        myService.doSomething();
    }

}

선택하거나, 변경 가능한 의존관계에 사용한다.(생성자 주입은 필수 값)

// MyService가 Spring Bean으로 등록되지 않은 경우에도 주입이 가능하다.
@Autowired(required = false)
public void setMyService(MyService myService) {
    this.myService = myService;
}

// 실행 도중 인스턴스를 바꾸고자 하는 경우
// setMyService(); 메서드를 외부에서 호출하면 된다.(이런 경우는 거의 없음)

 

 

3. 필드 주입

  • 필드에 직접적으로 주입하는 방법 (가장 추천되지 않음).
@Component
public class MyApp {

    @Autowired
    private MyService myService;  // 필드에 직접 주입

    public void run() {
        myService.doSomething();
    }
    
}
  • 코드는 간결하지만 Spring이 없으면 사용할 수 없다.
    • 사용하지 않아야 한다.
// Spring을 사용하지 않는 경우 실행이 불가능하다.
public class MainV2 {
    public static void main(String[] args) {
        MyApp myApp = new MyApp();
        myApp.run();
    }
}
  • 외부에서 myService 값을 설정하거나 변경할 방법이 없다.
    • 결국 setter를 만들어야 한다.
  • 순수 Java 코드로 사용할 수 없다. = 테스트 코드 작성이 힘들다.
  • Application의 실행과 관계 없는 @SpringBootTest 테스트 코드나 Spring에서만 사용하는 @Configuration 같은 곳에서 주입할 때 주로 사용한다.

 

4. 일반 메서드 주입

  • 생성자, setter 주입으로 대체가 가능하기 때문에 사용하지 않는다.
@Component
public class MyApp {

    private MyService myService;

    // 일반 메서드 주입
    @Autowired
    public void init(MyService myService) {
        this.myService = myService;
    }
    
    public void run() {
        myService.doSomething();
    }

}

 

의존관계를 자동으로 주입할 객체가 Spring Bean으로 등록되어 있어야 @Autowired 로 주입이 가능하다.

 

 

생성자 주입

📌 과거 setter, 필드 주입도 사용했지만 현재는 DI를 가지고 있는 대부분의 Framework가 생성자 주입 방식을 권장한다.

 

생성자 주입을 선택하는 이유

  • 불변(immutable)
    • 어떤 요리(Web Application)를 만들지 정해졌다면 이미 재료(Bean)와 의존 관계가 결정된다.
    • 객체를 생성할 때 최초 한번만 호출된다.(불변)
    • setter 주입을 사용하면 접근제어자가 public 으로 설정되어 누구나 수정할 수 있게된다.
  • 실수 방지
    • 순수 Java 코드로 사용할 때(주로 테스트 코드) 생성자의 필드를 필수로 입력하도록 만들어준다.(NPE 방지)
    • 컴파일 시점에 오류를 발생 시킨다. 즉, 실행 전에 오류를 알 수 있다.
public class MyApp {
    private MyService myService;

    public MyApp() {
        this.myService = new MyService(); // 직접 생성
    }

    public void run() {
        myService.doSomething();
    }
}

---------------------------------------

@Component // 해당 어노테이션이 MyService 객체를 bean으로 만들어준다. 즉 스프링이 객체 MyService를 저장한다.
public class MyApp {
    private final MyService myService;

    @Autowired
    public MyApp(MyService myService) {
        this.myService = myService; // 외부에서 주입 (직접 선언이 아니라 스프링에 저장된 객체 사용)
    }

    public void run() {
        myService.doSomething();
    }
}

 

  • 위 코드 참고
  • @Autowired: 스프링 컨테이너가 MyService 타입의 빈을 찾아서 자동으로 주입합니다.
  • MyApp은 MyService를 직접 생성하지 않습니다. 대신, 스프링 컨테이너가 제공한 인스턴스를 사용합니다.

 

 

 

Spring Framework에 의존하지 않아도 객체 지향 특성을 가장 잘 사용하는 방법이다.

필드에 final 은 생성자 주입 방식만 사용할 수 있다. 나머지 주입 방식들은 모두 생성 이후에 호출되어 사용할 수 없다.

 

 

@RequiredArgsConstructor

📌 실제 Web Application을 개발하면 대부분이 불변 객체이고 생성자 주입 방식을 선택하게 된다. 이런 반복되는 코드를 편안하게 작성하기 위해 Lombok에서 제공하는 Annotation 이다.

 

@RequiredArgsConstructor

  • final 필드를 모아서 생성자를 자동으로 만들어 주는 역할
  • Annotation Processor 가 동작하며 컴파일 시점에 자동으로 생성자 코드를 만들어준다.
  • 사용 방법
@Component
@RequiredArgsConstructor
public class MyApp {
		// 필드에 final 키워드 필수! 무조건 값이 있도록 만들어준다.(필수)
    private final MyService myService;
    
    // Annotation Processor가 만들어 주는 코드
    // public MyApp(MyService myService) {
    //     this.myService = myService;
    // }

    public void run() {
        myService.doSomething();
    }
    
}
  • 만약 생성자가 필요한 경우가 생긴다면, 생성자 주입 방식을 직접 선언하면 된다.
생성자를 하나 만들고 @Autowired 를 사용한 코드와 똑같이 동작한다.

'Back-End (Web) > Spring' 카테고리의 다른 글

ArgumentResolver  (0) 2025.01.08
[Spring] 스프링 정리  (0) 2025.01.07
[Spring] Bean 등록  (0) 2024.12.25
[Spring] Spring의 핵심 개념  (0) 2024.12.24
[Spring] Layered Architecture  (2) 2024.12.20

@ComponentScan

📌 Spring이 특정 패키지 내에서 @Component, @Service, @Repository, @Controller 같은 Annotation이 붙은 클래스를 자동으로 검색하고, 이를 Bean으로 등록하는 기능이다. 개발자가 Bean을 직접 등록하지 않고도 Spring이 자동으로 관리할 객체들을 찾는다.

 

ComponentScan의 역할

  • Chef가 요리할 재료를 자동으로 식료품 저장고에서 찾아오는 과정, Chef는 스스로 필요한 재료를 찾아 요리에 사용한다.

  • 요리사(개발자)가 직접 재료(Bean)를 찾아서 가져올 필요가 없다.

 

@ComponentScan

  1. 특정 패키지 내에 @Component Annotation이 붙은 클래스를 자동으로 찾아서 Spring Bean으로 등록한다.
    • Annotation을 이용해 Bean을 등록할 수 있어 코드가 간결해지고 유지보수가 쉬워진다.
  2. 스캐닝 범위는 주로 애플리케이션의 루트(최상위) 패키지에서 시작된다.
  3. @SpringBootApplication
    • 스프링 부트 애플리케이션의 시작점을 정의하기 위해 사용하는 애너테이션입니다. 이 애너테이션은 여러 기능을 결합한 복합 애너테이션으로, 스프링 부트 애플리케이션을 간단히 설정하고 실행할 수 있도록 돕습니다.
    • SpringBoot로 프로젝트를 생성하면 main() 메서드가 있는 클래스 상단에 @SpringBootApplication Annotation 이 존재한다.

 

 

  • @ComponentScan의 속성
    • basePackages: 특정 패키지를 스캔할 때 사용, 배열로 여러개를 선언할 수 있다.
      • 예시: @ComponentScan(basePackages = {"com.example", "com.another"})
    • basePackageClasses: 특정 클래스가 속한 패키지를 기준으로 스캔할 수 있다.
      • 예시: @ComponentScan(basePackageClasses = MyApp.class)
    • excludeFilters: 스캔에서 제외할 클래스를 필터링할 수 있다.
      • 예시: @ComponentScan(excludeFilters = @ComponentScan.Filter(SomeClass.class))
    • includeFilters: 특정 조건에 맞는 클래스만 스캔하여 포함할 수 있다.
      • 예시: @ComponentScan(includeFilters = @ComponentScan.Filter(Service.class))

 

@ComponentScan의 동작 순서

1. 스프링 컨테이너가 빈 등록

  • Spring Application이 실행되면 @ComponentScan이 지정된 패키지를 탐색한다.
  • @ComponentScan은 @Component, @Service, @Repository, @Controller 등의 애너테이션이 붙은 클래스를 탐색합니다.
  • 해당 클래스들을 스프링 빈으로 등록합니다.
  • 구현 클래스뿐만 아니라, 인터페이스와 그 구현체도 함께 빈으로 등록됩니다.

2. 의존성 정의 (인터페이스 기반 설계)

  • 일반적으로 **인터페이스(추상화)**를 의존성으로 정의합니다.
    구현체는 스프링이 자동으로 선택하고 주입합니다.

3. 구현체 자동 연결

  • 스프링은 등록된 빈 중에서 의존성으로 선언된 인터페이스에 맞는 구현체를 자동으로 찾아 주입합니다.
  • 이 과정은 타입 매칭을 통해 이루어집니다.

 

 

 

@Configuration, @Bean

📌 Spring Bean을 등록하는 방법에는 수동, 자동 두가지가 존재한다.

 

Spring Bean 등록 방법

  • Spring Bean은 Bean의 이름으로 등록된다.
    • 1. 자동 Bean 등록(@ComponentScan, @Component)
      • @Component 이 있는 클래스의 앞글자만 소문자로 변경하여 Bean 이름으로 등록한다.
// myService 라는 이름의 Spring Bean
@Component
public class MyService {

    public void doSomething() {
        System.out.println("Spring Bean 으로 동작");
    }
    
}
  • @ComponentScan 을 통해 @Component로 설정된 클래스를 찾는다.

 

  • 2. 수동 Bean 등록(@Configuration, @Bean)
    • @Configuration 이 있는 클래스를 Bean으로 등록하고 해당 클래스를 파싱해서 @Bean 이 있는 메서드를 찾아 Bean을 생성한다. 이때 해당 메서드의 이름으로 Bean의 이름이 설정된다.
// 인터페이스
public interface TestService {
    void doSomething();
}

// 인터페이스 구현체
public class TestServiceImpl implements TestService {
    @Override
    public void doSomething() {
        System.out.println("Test Service 메서드 호출");
    }
}

// 수동으로 빈 등록
@Configuration
public class AppConfig {
    
    // TestService 타입의 Spring Bean 등록
    @Bean
    public TestService testService() {
        // TestServiceImpl을 Bean으로 등록
        return new TestServiceImpl();
    }
    
}

// Spring Bean으로 등록이 되었는지 확인
public class MainApp {
    public static void main(String[] args) {
        // Spring ApplicationContext 생성 및 설정 클래스(AppConfig) 등록
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        // 등록된 TestService 빈 가져오기
        TestService service = context.getBean(TestService.class);

        // 빈 메서드 호출
        service.doSomething();
    }
}

 

수동으로 Bean을 등록할 때는 항상 @Configuration과 함께 사용해야 Bean이 싱글톤으로 관리된다. CGLIB 라이브러리와 연관이 있다.
더보기

@Configuration과 싱글톤 관리

  • **@Configuration**은 스프링에서 Java Config 클래스를 정의할 때 사용하는 애너테이션입니다. 이 애너테이션을 붙인 클래스는 스프링 컨테이너가 관리하는 설정 클래스로 동작하며, 해당 클래스에 정의된 Bean은 싱글톤으로 관리됩니다.
  • 싱글톤 관리:
    • @Configuration이 붙은 클래스는 내부적으로 CGLIB 동적 프록시 객체로 변환됩니다.
    • 이 프록시 객체는 @Bean 메서드 호출 시, 이미 생성된 Bean이 있으면 이를 반환하고, 없으면 새로운 Bean을 생성합니다.
    • 이를 통해 동일한 Bean이 여러 번 생성되지 않도록 보장합니다.

 

 

Bean 충돌

📌 Bean 등록 방법에는 수동, 자동 두가지가 존재하고 Bean은 각각의 이름으로 생성된다. 이때 이름이 같은 Bean이 설정되고자 한다면 충돌이 발생한다.

 

같은 이름의 Bean 등록

  • 자동 Bean 등록 VS 자동 Bean 등록
public interface ConflictService {
    void test();
}

// Bean의 이름을 service로 설정
@Component("service")
public class ConflictServiceV1 implements ConflictService {
    @Override
    public void test() {
        System.out.println("Conflict V1");
    }
}

// Bean의 이름을 service로 설정
@Component("service")
public class ConflictServiceV2 implements ConflictService {
    @Override
    public void test() {
        System.out.println("Conflict V2");
    }
}

// componentScan의 범위를 conflict 패키지 하위로 설정
@ComponentScan(basePackages = "com.example.springconcept.conflict")
public class ConflictApp {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(ConflictApp.class);

        // Service 빈을 가져와서 실행
        ConflictService service = context.getBean(ConflictService.class);

        service.test();
    }
}
  • ConflictingBeanDefinitionException 발생

 

수동 Bean 등록 VS 자동 Bean 등록

// conflictService 이름으로 Bean 생성
@Component
public class ConflictService implements MyService {
    @Override
    public void doSomething() {
        System.out.println("ConflictService 메서드 호출");
    }
}

public class ConflictServiceV2 implements MyService {
    @Override
    public void doSomething() {
        System.out.println("ConflictServiceV2 메서드 호출");
    }
}

// 수동으로 Bean 등록
@Configuration
public class ConflictAppConfig {
		
		// conflictService 이름으로 Bean 생성
    @Bean(name = "conflictService")
    MyService myService() {
        return new ConflictServiceV2();
    }

}

@ComponentScan(basePackages = "com.example.springconcept.conflict2")
public class ConflictApp2 {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(ConflictApp2.class);

        // Service 빈을 가져와서 실행
        MyService service = context.getBean(MyService.class);

        service.doSomething();
    }
}

 

  • 수동 Bean 등록이 자동 Bean 등록을 오버라이딩해서 우선권을 가진다.
  • 의도한 결과라면 다행이지만, 아닌 경우(실수)가 대부분이다. → 버그 발생
  • Spring Boot에서는 수동과 자동 Bean등록의 충돌이 발생하면 오류가 발생한다.

Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

 

설정 변경(application.properties)

// 수동, 자동 Bean을 동시에 등록할 때 이름이 같으면 수동 Bean이 오버라이딩
spring.main.allow-bean-definition-overriding=true 

// 기본값
spring.main.allow-bean-definition-overriding=false

 

 

@Qualifier, @Primary

📌 같은 타입의 Bean이 중복된 경우 해결하기 위해 사용하는 Annotation

  • 같은 타입의 Bean 충돌 해결 방법
    1. @Autowired + 필드명 사용
      • @Autowired 는 타입으로 먼저 주입을 시도하고 같은 타입의 Bean이 여러개라면 필드 이름 혹은 파라미터 이름으로 매칭한다.
public interface MyService { ... }

@Component
public class MyServiceImplV1 implements MyService { ... }

@Component
public class MyServiceImplV2 implements MyService { ... }

@Component
public class ConflictApp {

	// 필드명을 Bean 이름으로 설정
	@Autowired
	private MyService myServiceImplV2;
	...
}
  1. @Qualifier 사용
    • Bean 등록 시 추가 구분자를 붙여 준다.
    • 생성자 주입, setter 주입 사용 가능
@Component
@Qualifier("firstService")
public class MyServiceImplV1 implements MyService { ... }

@Component
@Qualifier("secondService")
public class MyServiceImplV2 implements MyService { ... }

@Component
public class ConflictApp {

		private MyService myService;

		// 생성자 주입에 구분자 추가
		@Autowired
		public ConflictApp(@Qualifier("firstService") MyService myService) {
				this.myService = myService;
		}
	
		// setter 주입에 구분자 추가
		@Autowired
		public void setMyService(@Qualifier("firstService") MyService myService) {
				this.myService = myService;
		}
	...
}
  1. @Primary 사용
    • @Primary로 지정된 Bean이 우선 순위를 가진다.
@Component
public class MyServiceImplV1 implements MyService { ... }

@Component
@Primary
public class MyServiceImplV2 implements MyService { ... }

@Component
public class ConflictApp {

		private MyService myService;

		@Autowired
		public ConflictApp(MyService myService) {
				this.myService = myService;
		}
	...
}
  • 실제 적용 사례
    • Database가 (메인 MySQL, 보조 Oracle) 두개 존재하는 경우
      • 기본적으로 MySQL을 사용할 때 @Primary를 사용하면 된다.
      • 필요할 때 @Qualifier로 Oracle을 사용하도록 만들 수 있다.
      • 동시에 사용되는 경우 @Qualifier 의 우선순위가 높다.
같은 타입의 Bean이 여러개 조회되었지만 모든 Bean이 필요하다면, Java의 자료구조 Map, List를 사용하는 방법도 있다.

 

 

수동 VS 자동

📌 Annotation 기반의 Spring에서는 자동 Bean 등록과 의존관계 주입을 사용하는 경우를 주로 사용한다. @Component 뿐만 아니라 @Controller, @Service, @Repository 등 자동으로 쉽게 등록할 수 있는 Annotation들을 지원하고 Spring Boot는 ComponentScan 방식을 기본으로 사용한다.

 

  • 자동 Bean 등록을 사용하는 이유
    1. 다양한 Annotation으로 편리하게 등록할 수 있다.
    2. Spring Boot는 ComponentScan 방식을 기본으로 사용한다.
    3. 간단하지만 OCP, DIP를 준수하며 개발할 수 있다.
  • 수동 Bean 등록을 사용하는 경우
    1. 외부 라이브러리나 객체를 Spring Bean으로 등록할 때
      • 외부 라이브러리에서 제공하는 클래스는 자동 등록이 불가능하다.
    2. 데이터베이스 연결과 같이 비지니스 로직을 지원하는 기술들에 사용한다.
      • 비지니스 로직보다 그 수가 아주 적지만 Application에 광범위하게 적용된다.
      • 설정 정보에 명시되어 있어서 유지보수성이 증가한다.
    3. 같은 타입의 Bean 여러개 중 하나를 명시적으로 선택해야 할 때

꼭 필요한 경우가 아니라면 자동 Bean 등록을 사용하면 된다.

 

'Back-End (Web) > Spring' 카테고리의 다른 글

[Spring] 스프링 정리  (0) 2025.01.07
[Spring] 의존관계 주입  (0) 2024.12.26
[Spring] Spring의 핵심 개념  (0) 2024.12.24
[Spring] Layered Architecture  (2) 2024.12.20
[Spring] Server에서 Client로 Data를 전달하는 방법  (0) 2024.12.19

Spring Container

📌 Spring으로 구성된 애플리케이션에서 객체(Bean)를 생성, 관리, 소멸하는 역할을 담당한다. 애플리케이션 시작 시, 설정 파일이나 Annotation을 읽어 Bean을 생성하고 주입하는 모든 과정을 컨트롤한다. 심지어는 의존성마저 주입한다.

  • 총괄주방장 = shef 라고 보면 편하다.

  • Spring Container를 사용하면 인터페이스에만 의존하는 설계가 가능해진다.
    • OCP, DIP 준수

 

Spring Container의 종류

  • BeanFactory
    • Spring Container의 최상위 인터페이스
    • Spring Bean을 관리하고 조회한다.
  • ApplicationContext
    • BeanFactory의 확장된 형태(implements) -> 진화된 버전
    • Application 개발에 필요한 다양한 기능을 추가적으로 제공한다.
      • 국제화, 환경변수 분리, 이벤트, 리소스 조회
일반적으로 ApplicationContext를 사용하기 때문에 ApplicationContext를 Spring Container라 표현한다.

 

Spring Bean

📌 Spring 컨테이너가 관리하는 객체를 의미한다. 자바 객체 자체는 특별하지 않지만, Spring이 이 객체를 관리하는 순간부터 Bean이 된다. Spring은 Bean을 생성, 초기화, 의존성 주입 등을 통해 관리한다.

  • 모든 객체가 Bean인게 아니라 spring container가 관리하는 객체를 bean이라 하는 것
  • Bean은 new 키워드 대신 사용하는 것이다.
  • Spring Container가 제어한다.

 

Spring Bean의 역할

  • Chef인 Spring Container가 요리할 음식에 사용될 재료(Bean)
    • 요리(Application)의 핵심을 이루는 재료(Bean)

 

  • Spring Bean의 특징
    1. Spring 컨테이너에 의해 생성되고 관리된다.
    2. 기본적으로 Singleton( 애플리케이션 전역에서 단 하나의 인스턴스만 생성하도록 보장하는 디자인 패턴 )으로 설정된다.
    3. 의존성 주입(DI)을 통해 다른 객체들과 의존 관계를 맺을 수 있다.
    4. 생성, 초기화, 사용, 소멸의 생명주기를 가진다.

 

Bean 등록 방법

  • XML, Java Annotation, Java 설정파일 등을 통해 Bean으로 등록할 수 있다.
    • XML
<beans>
    <!-- myBean이라는 이름의 Bean 정의 -->
    <bean id="myBean" class="com.example.MyBean" />
</beans>

--------------------------------------------------------------------

public class MyApp {
    public static void main(String[] args) {
        // Spring 컨테이너에서 Bean을 가져옴
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        MyService myService = context.getBean("myService", MyService.class);
        myService.doSomething();
    }
}
  • Annotation
    • @ComponentScan
    • 개발자가 일일이 빈(Bean)을 설정하지 않아도, 지정한 패키지에서 필요한 클래스들을 자동으로 찾아서 애플리케이션 컨텍스트에 빈(Bean) 등록해 줍니다.
// 이 클래스를 Bean으로 등록
// @Controller, @Service, @Repository
@Component
public class MyService {

    public void doSomething() {
        System.out.println("Spring Bean 으로 동작");
    }
    
}

------------------------------------

@Component
public class MyApp {

    private final MyService myService;

    @Autowired // 의존성 자동 주입
    public MyApp(MyService myService) {
        this.myService = myService;
    }

    public void run() {
        myService.doSomething();
    }
}

---------------------------------------------

// com.example 패키지를 스캔하여 Bean 등록
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        MyApp app = context.getBean(MyApp.class);
        app.run();
    }
    
}

Java 설정파일

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyService();
    }
}

------------------------------------

public class MyApp {
    public static void main(String[] args) {
        // Spring 컨테이너에서 Bean을 가져옴
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        MyService myService = context.getBean(MyService.class);
        myService.doSomething();
    }
}

 

 

 

IOC(제어의 역전, Inversion Of Control)

📌 객체의 생성과 관리 권한을 개발자가 아닌 Spring 컨테이너가 담당하는 것을 말한다. 기본적으로 개발자가 객체를 직접 생성하고 관리했지만, Spring에서는 컨테이너가 객체 생성, 주입, 소멸을 관리한다.

  • 요리사(개발자)는 필요한 재료를 직접 준비하지 않고, Chef가 알아서 필요한 재료(Bean)을 관리하고 요리사에게 가져다준다.

 

IoC 개념

  1. 객체의 생성 및 생명주기 관리를 개발자가 직접 하는 것이 아니라 컨테이너가 담당한다.
  2. 객체 간의 결합도를 낮춰 유연한 코드가 된다.

 

DI(의존성 주입, Dependency Injection)

📌 Spring이 객체 간의 의존성을 자동으로 주입해주는 것을 의미한다. 한 객체가 다른 객체를 사용할 때, 해당 객체를 직접 생성하지 않고 Spring이 주입해주는 방식이다. IOC를 구현하는 방식 중 하나이다.

  • 셰프가 요리를 만들 때 필요한 재료(Bean)를 자동으로 요리사에게 가져다주는 과정
  • 요리사(개발자)는 재료를 찾을 필요 없이, Chef가 알아서 제공해준다.

 

 

의존성 = 추상화가 아닌 이유

의존성이란, 한 객체가 다른 객체를 사용하거나 그 객체의 기능에 의존하는 관계를 말합니다. 그리고 추상화는 의존성을 설계하는 중요한 방식 중 하나입니다. 하지만 의존성이 항상 추상화와 동일한 개념은 아닙니다. 의존성과 추상화의 관계를 설명하겠습니다.

 

  1. 의존성은 설계의 결과, 추상화는 설계의 방법:
    • 의존성은 클래스 간의 관계를 나타냅니다.
    • 추상화는 의존성을 관리하거나 줄이기 위한 설계 기법입니다.
  2. 구체적인 의존성도 존재:
    • 의존성이 항상 추상화된 타입에만 연결되지 않습니다.
    • 구체 클래스에 대한 의존성도 의존성의 한 형태입니다.

 

IOC, DI 주입 개발자, SPRING 비교 

// Service 인터페이스
public interface MyService {
    void doSomething();
}

// Repository 인터페이스
public interface MyRepository {
    void queryDatabase();
}

// Service 구현체
public class MyServiceImpl implements MyService {
    private MyRepository myRepository;

    // 의존성 주입
    public MyServiceImpl(MyRepository myRepository) {
        this.myRepository = myRepository;
    }

    @Override
    public void doSomething() {
        System.out.println("서비스 작업 실행");
        myRepository.queryDatabase();
    }
}

// Repository 구현체
public class MyRepositoryImpl implements MyRepository {
    @Override
    public void queryDatabase() {
        System.out.println("데이터베이스 쿼리 실행");
    }
}

public class MyApp {
    public static void main(String[] args) {
        MyRepository repo = new MyRepositoryImpl();

				// MyRepository repo2 = new MyRepositoryImplV2();

        MyService myService = new MyServiceImpl(repo);
        
				// MyService myService2 = new MyServiceImpl(repo2);

        myService.doSomething();
    }
}

// 새로운 Repository 구현체
public class MyRepositoryImplV2 implements MyRepository {
    @Override
    public void queryDatabase() {
        System.out.println("데이터베이스 쿼리 실행 V2");
    }
}


---------------------------------


// Service 구현체
@Service
public class MyIocService implements MyService {
    
    private final MyRepository myRepository;

    // 생성자 주입(DI 적용)
	@Autowired: 스프링이 자동으로 MyRepository 타입의 빈(Bean)을 찾아 생성자에 주입하도록 지시합니다.
    public MyIocService(MyRepository myRepository) {
        this.myRepository = myRepository;
    }

    @Override
    public void doSomething() {
        System.out.println("IOC 서비스 작업 실행");
        myRepository.queryDatabase();
    }
}

// Repository 구현체
@Repository
public class MyIocRepository implements MyRepository {

    @Override
    public void queryDatabase() {
        // 데이터베이스와 상호작용
        System.out.println("IOC 데이터베이스 쿼리 실행");
    }
}

// Spring Container 관리(IoC 적용)
@ComponentScan(basePackages = "com.example") 
: @ComponentScan으로 지정된 com.example 패키지를 스캔하여 빈으로 등록할 클래스들을 검색합니다.
public class MyIocApp {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MyIocApp.class);

        // Service 빈을 가져와서 실행
        MyService service = context.getBean(MyService.class);
        service.doSomething();
    }
}

// 새로운 Repository 구현체
@Repository
public class MyIocRepositoryV2 implements MyRepository {

    @Override
    public void queryDatabase() {
        // 데이터베이스와 상호작용
        System.out.println("IOC 데이터베이스 쿼리 실행 V2");
    }
}
  • 구현 코드가 변경되어도 클라이언트의 코드에는 영향이 없다.
  • 다른 구현체를 구현하여 Bean으로 등록하면 자유롭게 변경이 가능하다.
    • 위 예시 코드는 @Repository 로 등록된 빈이 중복되어 충돌이 발생한다.
  • 의존성 주입(DI), 제어의 역전(IOC)을 통해 객체 간의 결합도를 낮추고 유연한 설계가 가능해진다.

 

IOC/DI

  1. IoC객체의 제어권을 개발자가 아닌 Spring 컨테이너에게 넘기는 개념으로, Spring이 객체 생성과 관리를 담당한다.
  2. DI는 Spring이 객체 간의 의존성을 자동으로 주입해주는 기법이다.
  3. 의존관계 주입은 객체 간의 결합도를 낮추고 코드의 유연성과 테스트 가능성을 높여준다.

 

 

Singleton Pattern

📌 클래스의 인스턴스가 오직 하나만 생성되도록 보장하는 디자인 패턴이다.

public class MainApp {
    public static void main(String[] args) {
       // 첫 번째 싱글톤 인스턴스 요청, 구현클래스.getInstance();
        Singleton instance1 = SingletonImpl.getInstance();
        instance1.showMessage(); // 인스턴스 주소값 출력

        // 두 번째 싱글톤 인스턴스 요청, 구현클래스.getInstance();
        Singleton instance2 = SingletonImpl.getInstance();
        instance2.showMessage(); // 인스턴스 주소값 출력
        
        // 다른 구현체로 바꾸려면 DIP, OCP 위반
        Singleton instance3 = SingletonImplV2.getInstance();
        instance3.showMessage();
    }
}
  • 위처럼 인스턴스가 클래스마다 1개씩만 배치된다.
  • 다만 보면 알겠지만, 클래스의 개수가 많아지는 것 같은 느낌이 들 것이다.

 

싱글톤 패턴의 문제점

  • 싱글톤 패턴을 구현하기 위한 코드의 양이 많다.
  • 구현 클래스에 의존해야 한다.(DIP, OCP 위반)
  • 유연성이 떨어져서 안티패턴으로 불리기도 한다.

 

Spring의 싱글톤 컨테이너

  • Spring은 Web Application을 만들 때 주로 사용된다.

  • Spring Container는 싱글톤 패턴의 문제점들을 해결하면서 객체를 싱글톤으로 관리한다.
  • Spring Bean은 싱글톤으로 관리되는 객체이다.
  • Spring이 Bean을 등록하는 방법은 기본적으로 싱글톤 이다. 하지만, 요청할 때 마다 새로운 객체를 생성해서 반환하는 기능도 제공한다.

 

Spring Bean이 싱글톤을 사용하는 이유

더보기

스프링에서 Bean을 기본적으로 싱글톤으로 사용하는 이유는 싱글톤 패턴의 장점을 최대한 활용하면서도, 패턴 자체의 단점들을 해결하기 때문입니다. 이를 단계적으로 설명하겠습니다.


싱글톤 패턴의 장점

  1. 리소스 절약:
    • 객체를 한 번만 생성하고 재사용하므로 메모리와 리소스를 절약할 수 있습니다.
  2. 전역 접근 가능:
    • 애플리케이션 전체에서 동일한 인스턴스를 사용할 수 있어 상태 관리나 공유 자원 관리가 용이합니다.

싱글톤 패턴의 단점과 스프링의 해결책

1. 구현이 복잡하다 (코드가 많다)

  • 문제점:
    • 싱글톤 패턴을 구현하려면 추가적인 코드(정적 변수, 동기화 처리 등)가 필요합니다.
    • 이를 잘못 구현하면 멀티스레드 환경에서 안전하지 않을 수 있습니다.
  • 스프링의 해결:
    • 스프링 컨테이너가 싱글톤 관리를 대신합니다.
    • 개발자는 객체 생성 방식을 신경 쓸 필요 없이, @Component, @Service, @Repository 등의 애너테이션만 추가하면 스프링이 자동으로 관리합니다.

2. 구현 클래스에 의존 (DIP, OCP 위반)

  • 문제점:
    • 싱글톤 패턴은 클래스 자체에서 객체를 관리하기 때문에 구체 클래스에 의존하게 됩니다.
    • 이는 **의존성 역전 원칙(DIP)**과 **개방-폐쇄 원칙(OCP)**을 위반할 가능성을 높입니다.
  • 스프링의 해결:
    • 스프링은 **DI(Dependency Injection)**를 사용하여 의존성을 주입합니다.
    • Bean을 인터페이스 기반 설계로 사용할 수 있게 하여 DIP를 준수합니다.
    • 개발자는 객체 생성 방식을 몰라도 컨테이너가 관리하는 객체를 사용할 수 있으므로 OCP를 준수합니다.

3. 테스트 어려움

  • 문제점:
    • 싱글톤 객체는 전역 상태를 공유하기 때문에 테스트에서 객체의 상태를 초기화하거나 Mock 객체로 대체하기 어렵습니다.
  • 스프링의 해결:
    • 스프링은 DI를 통해 의존성을 주입하므로, 테스트 환경에서 Mock 객체를 쉽게 주입할 수 있습니다.
    • 빈의 스코프를 싱글톤 이외로 설정(예: 프로토타입 스코프)할 수도 있습니다.

4. 유연성 부족 (안티패턴)

  • 문제점:
    • 전역 상태를 공유하는 싱글톤 객체는 설계의 유연성을 저하시켜 애플리케이션의 확장과 변경에 제약을 줄 수 있습니다.
  • 스프링의 해결:
    • 스프링은 필요에 따라 Bean의 **스코프(scope)**를 변경할 수 있습니다:
      • 싱글톤(Singleton): 기본 설정, 모든 요청에서 동일한 인스턴스를 공유.
      • 프로토타입(Prototype): 요청 시마다 새로운 인스턴스 생성.
      • 요청(Request): HTTP 요청마다 새로운 인스턴스 생성.
      • 세션(Session): HTTP 세션마다 새로운 인스턴스 생성.
      • 웹 소켓(WebSocket): 웹소켓 연결마다 새로운 인스턴스 생성.

스프링이 싱글톤을 사용하는 이유

스프링은 싱글톤 패턴의 단점을 해결하면서도, 다음과 같은 이유로 싱글톤을 기본으로 사용합니다:

  1. 효율성:
    • 애플리케이션에서 동일한 빈을 여러 번 생성하지 않고, 한 번 생성된 객체를 재사용하여 리소스를 절약합니다.
  2. 글로벌 상태 관리:
    • 데이터베이스 연결, 캐시, 설정 정보 등 전역적으로 공유할 필요가 있는 객체를 관리하기 적합합니다.
  3. 유연성:
    • 스프링은 개발자가 직접 싱글톤 패턴을 구현하지 않아도, 컨테이너가 이를 관리합니다.
    • 필요에 따라 스코프를 변경하거나 테스트 시 Mock 객체로 대체할 수 있습니다.
  4. 단순화된 코드:
    • 개발자는 @Component, @Service 등 애너테이션만으로 빈을 정의하고 사용할 수 있습니다. 싱글톤 관리 로직을 작성할 필요가 없습니다.

결론

스프링은 싱글톤 패턴의 장점을 최대한 활용하면서, **DI(의존성 주입)**와 유연한 스코프 관리를 통해 단점들을 극복했습니다. 이로 인해 스프링의 Bean은 싱글톤을 기본으로 사용하면서도 효율적이고 확장 가능한 구조를 제공합니다.

 

 

Singleton Pattern의 주의

📌 객체의 인스턴스를 하나만 생성하여 공유하는 싱글톤 패턴의 객체는 상태를 유지(stateful)하면 안된다.

  • 지역 변수라면 변수여도 된다. 하지만 요청마다 값이 변경되거나 외부에서 변경 가능한 데이터가 싱글톤 객체에 저장되면 문제가 생길 수 있다.
  • 공유 객체( 여러 사용자나 스레드가 동시에 접근하여 값을 읽거나 수정할 수 있는 데이터 )는 안된다.

 

상태 유지(stateful)의 문제점

  • 데이터의 불일치나 동시성 문제가 발생할 수 있다.
  • 코드예시
public class StatefulSingleton {
    private static StatefulSingleton instance;
    
    // 상태를 나타내는 필드
    private int value;

    // private 생성자
    private StatefulSingleton() {}

    // 싱글톤 인스턴스를 반환하는 메서드
    public static StatefulSingleton getInstance() {
        if (instance == null) {
            instance = new StatefulSingleton();
        }
        return instance;
    }

    // 상태 변경 메서드
    public void setValue(int value) {
        this.value = value;
    }

    // 상태를 반환하는 메서드
    public int getValue() {
        return this.value;
    }
}
public class MainApp {
    public static void main(String[] args) {
        // 클라이언트 1: 싱글톤 인스턴스를 가져와서 상태를 설정
        StatefulSingleton client1 = StatefulSingleton.getInstance();
        client1.setValue(42);
        System.out.println("클라이언트 1이 설정한 값: " + client1.getValue());

        // 클라이언트 2: 동일한 싱글톤 인스턴스를 사용해 상태를 변경
        StatefulSingleton client2 = StatefulSingleton.getInstance();
        client2.setValue(100);
        System.out.println("클라이언트 2가 설정한 값: " + client2.getValue());

        // 클라이언트 1이 다시 값을 확인
        System.out.println("클라이언트 1이 다시 확인한 값: " + client1.getValue());
    }
}
클라이언트 1이 설정한 값: 42
클라이언트 2가 설정한 값: 100
클라이언트 1이 다시 확인한 값: 100
  • value 필드는 공유되는 필드인데, 특정 클라이언트가 값을 변경한다.
  • Spring Bean은 항상 무상태(stateless)로 설계를 해야한다. 아주 중요!
    • 특정 클라이언트에 의존적인 필드가 있거나 변경할 수 있으면 안된다.

 

'Back-End (Web) > Spring' 카테고리의 다른 글

[Spring] 의존관계 주입  (0) 2024.12.26
[Spring] Bean 등록  (0) 2024.12.25
[Spring] Layered Architecture  (2) 2024.12.20
[Spring] Server에서 Client로 Data를 전달하는 방법  (0) 2024.12.19
[Spring] HTTP Message Body & TEXT  (0) 2024.12.18

Layered Architecture

📌 애플리케이션을 세 가지 주요 계층으로 나누어 구조화하는 방법으로 각 계층은 특정한 책임을 갖고 있으며, 계층 간에는 명확한 역할 분담이 이루어져 코드의 재사용성, 유지보수성, 확장성을 높이는 데 도움을 준다.

 

주요 특징

  1. 계층 분리:
    • 시스템을 기능별로 분리하여 모듈화.
  2. 책임 분리:
    • 각 계층은 고유한 책임과 역할을 가짐.
  3. 상호 의존성:
    • 상위 계층은 하위 계층에만 의존하며, 계층 간의 의존성을 제한.
  4. 유지보수 용이:
    • 특정 계층의 변경이 다른 계층에 최소한의 영향을 미침.

 

Layerd Architecture 개요 

 

  • 기존의 MVC 패턴에서 Controller는 역할이 무수히 많다.
    1. 요청에 대한 처리
    2. 예외처리
    3. View Template 응답 or Data 응답
    4. 비지니스 로직 처리
    5. DB 상호작용
  • 문제점
    • Controller에서 요청에 대한 모든 처리를 수행한다. 즉, 책임이 너무 많다.
    • 기능 추가, 수정, 삭제 등의 유지보수가 힘들어진다.
    • 코드의 재사용성이 떨어진다. 메서드로 분리하여도 메서드를 호출하는 중복 코드가 발생한다.

 

Layered Architecture 구조

 

  • Presentation Layer
    • 사용자의 요청을 받고 응답하는 역할을 수행한다.
    • 화면을 응답하거나 데이터를 응답하는 API를 정의한다.
  • Business Layer(Service Layer)
    • 비지니스 로직을 수행한다.
    • 요청을 해석하여 Repository Layer에 전달한다.
    • 일반적으로 하나의 비지니스 로직은 하나의 트랜잭션으로 동작한다.
  • Data Access Layer(Repository Layer)
    • 데이터베이스와 연동되어 실제 데이터를 관리한다.
  • 용어 설명
    • DTO(Data Transfer Object)
      • 계층간 데이터 전달을 위해 사용되는 객체이다.
    • Model
      • Entity
        • 추후 숙련주차에 배울 JPA와 관련이 있다.
        • JPA에서는 Entity라는 형태로 데이터를 반환한다.
    • DAO(Data Access Object)

 

계층 간 의존성

  • 상위 계층은 하위 계층에만 의존합니다.
  • 계층 간의 의존성을 단방향으로 제한하여 결합도를 낮춥니다.

계층 간 의존성 예시

Presentation → Application → Domain → Data Access

 

 

장점

  1. 유지보수성:
    • 계층별 역할이 분리되어, 특정 계층의 변경이 다른 계층에 미치는 영향을 최소화.
  2. 재사용성:
    • 서비스나 데이터 계층을 다른 애플리케이션에서도 재사용 가능.
  3. 테스트 용이성:
    • 계층 단위로 테스트가 가능하여, 단위 테스트와 통합 테스트를 쉽게 수행.
  4. 확장성:
    • 특정 계층에 새로운 기능을 추가하거나 변경하기 쉬움.

 

단점

  1. 복잡성 증가:
    • 계층 간의 통신 코드로 인해 초기 개발이 복잡해질 수 있음.
  2. 성능 문제:
    • 계층 간 호출이 많아지면 성능 저하 가능.
  3. 단순 CRUD에 과한 구조:
    • 작은 애플리케이션에서는 계층형 아키텍처가 불필요한 복잡성을 초래.

 

계층형 아키텍처를 사용할 때 적합한 경우

  1. 복잡한 비즈니스 로직:
    • 다양한 데이터 소스와 복잡한 비즈니스 규칙이 있는 애플리케이션.
  2. 협업 프로젝트:
    • 역할 분리가 명확해 팀 간 작업이 효율적.
  3. 대규모 애플리케이션:
    • 변경 사항을 쉽게 관리하고, 시스템 확장이 필요한 경우.

 

 

 

 

 

Layered Architecture 적용

 

 

 

1. Controller

  • 클라이언트의 요청을 받는 역할을 수행한다.
  • 요청에 대한 처리를 Service Layer에 전달한다.
  • Service에서 처리 완료된 결과를 클라이언트에 응답한다.
  • 사용하는 Annotation : @Controller, @RestController

2. Service

  • 사용자의 요청 사항을 처리한다.
  • DB와 상호작용이 필요한 경우, Repository Layer에게 요청한다.
  • 사용하는 Annotation: @Service

 

3. Repository

  • DB와 상호작용을 수행한다.
    • Connection 연결, 해제
    • CRUD 작업 처리
  • 사용하는 Annotation: @Repository

 

4. DTO(Data Transfer Object)

  • 계층간 데이터 전달을 위해 사용된다.
  • 요청 데이터를 처리하는 객체는 일반적으로 RequestDto로 명명한다.
  • 응답 데이터를 처리하는 객체는 일반적으로 ResponseDto로 명명한다.

 

DTO (Data Transfer Object)

📌 애플리케이션에서 계층 간 데이터 전송을 목적으로 사용하는 단순한 객체입니다.

  • 주로 데이터를 전송하기 위한 속성과 Getter/Setter 메서드만 포함하며, 비즈니스 로직을 포함하지 않습니다.

 

DTO의 주요 역할

  1. 데이터 캡슐화:
    • 데이터 구조를 명확히 정의하여 계층 간 데이터 교환을 표준화.
  2. 안전한 데이터 전달:
    • 데이터 모델(Entity)과 직접 연관되지 않아, 민감한 데이터 보호 가능.
  3. 데이터 변환 및 제한:
    • 클라이언트가 필요로 하는 데이터만 전달하도록 제한.
  4. API 설계에 유용:
    • RESTful API에서 응답(Response) 또는 요청(Request) 데이터를 정의.

 

 

DTO의 구조

DTO는 일반적으로 다음과 같은 형태로 작성됩니다:

  • 속성 필드
  • Getter/Setter 메서드
  • (선택적) 생성자, toString(), equals(), hashCode()
public class UserDTO {
    private Long id;
    private String name;
    private String email;

    // 기본 생성자
    public UserDTO() {}

    // 생성자
    public UserDTO(Long id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }

    // Getter와 Setter
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }

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

    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}

 

DTO의 사용 예

a. 요청(Request) DTO

클라이언트가 서버에 데이터를 보낼 때 사용.

Request DTO 예제

public class CreateUserRequest {
    private String name;
    private String email;

    // Getter와 Setter
}

 

Controller

@RestController
@RequestMapping("/users")
public class UserController {

    @PostMapping
    public ResponseEntity<String> createUser(@RequestBody CreateUserRequest request) {
        // 요청 데이터 처리
        return ResponseEntity.ok("User created: " + request.getName());
    }
}

 

요청 데이터

{
    "name": "John Doe",
    "email": "john.doe@example.com"
}

 

b. 응답(Response) DTO

서버가 클라이언트에 데이터를 반환할 때 사용.

 

Response DTO 예제

public class UserResponse {
    private Long id;
    private String name;
    private String email;

    public UserResponse(Long id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }

    // Getter만 제공
}

 

Controller

@RestController
@RequestMapping("/users")
public class UserController {

    @GetMapping("/{id}")
    public ResponseEntity<UserResponse> getUser(@PathVariable Long id) {
        // 서비스에서 가져온 데이터
        UserResponse response = new UserResponse(id, "John Doe", "john.doe@example.com");
        return ResponseEntity.ok(response);
    }
}

 

응답 데이터

{
    "id": 1,
    "name": "John Doe",
    "email": "john.doe@example.com"
}

 

Entity와 DTO의 차이

  Entity DTO
역할 데이터베이스 테이블과 직접 매핑. 데이터 전송용 객체.
책임 비즈니스 로직 및 데이터베이스 작업 포함. 단순히 데이터 전송 및 표현.
위치 Service, Repository 계층에서 사용. Controller와 Service 계층 간 전송.
직렬화 필요에 따라 가능. 주로 직렬화되어 전송(JSON, XML 등).
민감한 데이터 포함 가능. 민감한 데이터는 제외.

 

DTO 변환 (Entity ↔ DTO)

DTO는 Entity와 직접적으로 사용되지 않으며, 변환 과정을 거칩니다.

Service 계층에서 변환 예

@Service
public class UserService {

    public UserResponse getUserResponse(UserEntity entity) {
        return new UserResponse(
            entity.getId(),
            entity.getName(),
            entity.getEmail()
        );
    }
}

 

 

Spring Boot에서 DTO 자동 변환

Spring Boot에서 DTO 변환을 쉽게 하기 위해 ModelMapper 또는 MapStruct와 같은 라이브러리를 사용할 수 있습니다.

a. ModelMapper 예제

의존성 추가 (Maven)

<dependency>
    <groupId>org.modelmapper</groupId>
    <artifactId>modelmapper</artifactId>
    <version>3.1.0</version>
</dependency>

변환 코드

import org.modelmapper.ModelMapper;

@Service
public class UserService {

    private final ModelMapper modelMapper = new ModelMapper();

    public UserDTO convertToDTO(UserEntity entity) {
        return modelMapper.map(entity, UserDTO.class);
    }
}

 

DTO의 장점

  1. 데이터 보호:
    • 데이터베이스와 직접 연결된 Entity를 노출하지 않아 민감한 데이터 보호.
  2. 역할 분리:
    • Entity와 DTO를 분리하여 비즈니스 로직과 데이터 전송 책임을 명확히 구분.
  3. API 표준화:
    • 클라이언트 요청과 응답 형식을 명확히 정의 가능.
  4. 유지보수성 향상:
    • 데이터 전송 형식 변경이 Entity와 독립적으로 이루어짐.

 

결론

 

  • DTO(Data Transfer Object)는 계층 간 데이터를 주고받을 때 사용되는 단순한 객체입니다.
  • 데이터 캡슐화, 보안성 강화, API 설계 표준화 등의 장점으로, 특히 Spring MVC와 RESTful API 설계에서 널리 사용됩니다.
  • DTO를 활용하여 애플리케이션의 구조를 더 유연하고 유지보수 가능하게 설계할 수 있습니다.

정적 리소스

📌 웹 애플리케이션에서 변하지 않는 파일들을 의미한다. 예를 들어, HTML, CSS, JavaScript, 이미지 파일들(JPG, PNG, GIF) 등이 정적 리소스에 해당한다.

  • 정적인 HTML, CSS, JS, Image 등을 변경 없이 그대로 반환한다.

 

정적 리소스의 특징

  1. 고정된 내용:
    • 요청이 올 때마다 동일한 콘텐츠를 제공합니다.
    • 실행되거나 동적으로 변환되지 않습니다.
  2. 캐싱 가능:
    • 정적 리소스는 자주 요청되므로 브라우저에서 캐싱하여 성능을 최적화할 수 있습니다.
  3. 서버 부담 감소:
    • 서버는 정적 리소스를 변환하거나 처리하지 않으므로, 부하가 적습니다.
  4. 브라우저에서 직접 렌더링:
    • 클라이언트(브라우저)가 직접 처리할 수 있는 파일 형식으로 제공됩니다.

 

 

Spring Boot에서 정적 리소스 제공

  • Spring Boot는 기본적으로 정적 리소스를 제공하기 위한 구조를 내장하고 있습니다.

1. 정적 리소스 경로

Spring Boot는 다음 디렉토리에서 정적 리소스를 자동으로 로드합니다:

디렉토리 설명
/static src/main/resources/static에 있는 파일을 정적 리소스로 제공.
/public src/main/resources/public에 있는 파일을 정적 리소스로 제공.
/resources src/main/resources에 있는 파일.
/META-INF/resources JAR 파일의 META-INF 디렉토리 내부의 파일.

 

2. 정적 리소스 예제

 

 

 

정적 리소스와 동적 리소스 비교

  정적 리소스 동적 리소스
변경 여부 고정된 콘텐츠 (HTML, CSS, JS, 이미지 등). 요청에 따라 내용이 동적으로 생성됨.
처리 방식 서버가 파일 그대로 응답. 서버가 요청 데이터를 처리하여 응답 생성.
요청-응답 속도 빠름 (파일 그대로 반환). 비교적 느림 (처리 후 반환).
예시 HTML, CSS, JS, 이미지 파일. REST API 응답, 템플릿 엔진 출력.

 

 

정적 리소스와 컨트롤러 경로 충돌

Spring Boot는 기본적으로 정적 리소스를 우선 처리합니다.
예:

  • /index.html 정적 파일이 있으면, 해당 파일을 반환.
  • /index를 처리하는 컨트롤러가 있다면, 정적 리소스 대신 컨트롤러가 처리.

컨트롤러 우선 처리

application.properties에서 다음 설정으로 컨트롤러를 우선 처리하도록 변경 가능합니다:

spring.mvc.static-path-pattern=/**

 

 

정적 리소스 최적화

  1. Gzip 압축:
    • 정적 리소스를 Gzip으로 압축하여 전송 속도를 개선.
    properties
     
     
  2. Content Delivery Network (CDN):
    • 정적 리소스를 CDN에 배포하여 전 세계적으로 빠르게 제공.
  3. 캐싱:
    • 브라우저 캐싱을 활용하여 불필요한 네트워크 요청을 줄임.
server.compression.enabled=true
server.compression.mime-types=text/html,text/xml,text/plain,text/css,application/javascript

 

 

요약

  • 정적 리소스는 서버가 변경 없이 그대로 반환하는 콘텐츠입니다.
  • Spring Boot는 /static, /public 등 기본 경로에서 정적 리소스를 자동으로 제공합니다.
  • 정적 리소스는 속도가 빠르고 서버 부하가 적으므로, 이미지, CSS, JS 등의 파일 전송에 최적입니다.
  • 필요 시 경로와 캐싱 설정을 커스터마이징할 수 있습니다.
  • Spring Boot의 정적 리소스 경로
    • 아래 경로들에 정적 리소스가 존재하면 서버에서 별도의 처리 없이 파일 그대로 반환된다.
    1. /static
    2. /public
    3. /META-INF/resources
    4. src/main/resources
      1. /static

Spring Boot Directory 구조

  • src/main/resources/static/hello/world.html 디렉토리 구조라면
    • http://localhost:8080/hello/world.html URL로 리소스에 접근이 가능하다.
    • /static 대신 /public 혹은 /META-INF/resources 도 사용 가능하다.

 

View Template (동적 리소)

📌 웹 애플리케이션에서 서버가 클라이언트에 응답으로 제공할 HTML 페이지를 동적으로 생성하기 위해 사용하는 템플릿 파일입니다. 데이터를 표시하기 위해 동적인 콘텐츠와 정적인 HTML 구조를 결합하는 데 사용됩니다.

  • Spring에서는 Thymeleaf, JSP와 같은 템플릿 엔진을 사용해 View Template을 작성할 수 있고, View Template은 서버에서 데이터를 받아 이를 HTML 구조에 맞게 삽입한 후, 최종적으로 클라이언트에게 전송되는 HTML 문서로 변환하여 사용자에게 동적으로 생성된 웹 페이지를 제공한다.
  • 📌 View Template
    • View Template은 Model을 참고하여 HTML 등이 동적으로 만들어지고 Client에 응답된다.
    • Spring Boot는 기본적으로 View Template 경로(src/main/resources/templates)를 설정한다.
    • build.gradle에 Thymeleaf 의존성을 추가하면 ThymeleafViewResolver와 필요한 Spring Bean들이 자동으로 등록된다. 
  • SSR(Server Side Rendering)을 사용할 때 View가 반환된다.

View Template의 역할

  • 정적 HTML과 동적 데이터 결합:
    • 서버에서 HTML 파일에 동적 데이터를 삽입하여 사용자 맞춤 콘텐츠를 생성.
  • 서버 사이드 렌더링 (SSR):
    • 브라우저에 표시될 HTML을 서버에서 렌더링하고 클라이언트로 전송.
  • UI 레이아웃 관리:
    • 레이아웃, 반복 구조, 조건부 표시 등과 같은 동적 UI 요소를 효율적으로 관리.

 

Spring MVC에서 View Template

Spring MVC에서는 Model-View-Controller 패턴을 기반으로 View Template 엔진을 사용하여 HTML을 생성합니다.

Spring MVC에서의 동작 흐름

  1. Controller:
    • 클라이언트의 요청을 처리하고, 데이터를 Model 객체에 담아 View로 전달.
  2. View Template:
    • Model 데이터를 기반으로 HTML을 렌더링.
  3. Client:
    • 완성된 HTML 페이지를 클라이언트로 전송하여 브라우저에 표시.

 

pring Boot에서 지원하는 View Template 엔진

Thymeleaf Spring Boot에서 가장 널리 사용되는 템플릿 엔진. 자연스러운 HTML과의 통합 제공.
JSP Java 기반의 전통적인 템플릿 엔진. Servlet Container에서 실행.
Freemarker 강력한 기능의 템플릿 엔진. 다양한 커스터마이징 가능.
Mustache 간단하고 가벼운 템플릿 엔진. JavaScript에서도 동일 문법 사용 가능.
Groovy Templates Groovy 언어 기반의 템플릿 엔진.

 

 

Thymeleaf 예제 (Spring Boot 기본 설정)

a. Maven 의존성 추가

Spring Boot Starter를 사용하면 Thymeleaf가 기본적으로 포함됩니다:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

 

b. 프로젝트 구조

 

c. Controller 코드

@Controller
public class HomeController {

    @GetMapping("/")
    public String home(Model model) {
        model.addAttribute("title", "Welcome to Thymeleaf");
        model.addAttribute("message", "Hello, Thymeleaf!");
        return "home"; // home.html을 렌더링
    }
}

 

d. Thymeleaf View Template (home.html)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title th:text="${title}">Default Title</title>
</head>
<body>
    <h1 th:text="${message}">Default Message</h1>
</body>
</html>

 

결과

  1. 클라이언트가 /로 요청.
  2. Controller가 Model에 데이터를 담아 home.html로 전달.
  3. Thymeleaf가 데이터를 HTML에 삽입.
  4. 클라이언트로 다음과 같은 HTML 응답이 전송:
<!DOCTYPE html>
<html>
<head>
    <title>Welcome to Thymeleaf</title>
</head>
<body>
    <h1>Hello, Thymeleaf!</h1>
</body>
</html>

 

 

@Controller의 응답으로 String을 반환하는 경우

  • @ResponseBody 가 없으면 View Resolver가 실행되며 View를 찾고 Rendering한다.
@Controller
public class ViewTemplateController {
	
	@RequestMapping("/response-view")
  public String responseView(Model model) {
      // key, value
      model.addAttribute("data", "sparta");

      return "thymeleaf-view";
  }
	
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Hello</title>
</head>
<body>
    <h1>Thymeleaf</h1>
    <h2 th:text="${data}"></h2>
</body>
</html>

 

ex) http://localhost:8080/response-view

  • @ResponseBody 가 있으면 HTTP Message Body에 return 문자열 값이 입력된다.

Postman

 

반환 타입이 void인 경우

  • 잘 사용하지 않는다.
  • @Controller + (@ResponseBody, HttpServletResponse, OutputStream)과 같은 HTTP Message Body를 처리하는 파라미터가 없으면 RequestMapping URL을 참고하여 View Name으로 사용한다.
@Controller
public class ViewTemplateController {
	
	// thymeleaf-view.html 과 Mapping된다.
  @RequestMapping("/thymeleaf-view")
  public void responseViewV2(Model model) {
      model.addAttribute("data", "sparta");
  }
	
}

 

  • 예시와 같은 경우에는 viewTemplate(viewName)을 RequestMapping URL 주소로 찾는다.

 

 

HTTP Message Body

📌 HTTP Message Body는 HTTP 요청(Request) 또는 응답(Response)에서 실제 데이터를 담는 부분입니다.

  • REST API를 만드는 경우 Server에서 Client로 HTML을 전달하는 방식이 아닌 HTTP Message Body에 직접 Data를 JSON 형식으로 담아 전달한다.
  • 정적 HTML, View Template 또한 HTTP Message Body에 담겨서 전달된다. 현재 설명하는 Response의 경우는 정적 HTML, View Template을 거치지 않고 직접 HTTP Response Message를 만들어 전달하는 경우를 말하는것.

 

HttpServletResponse 사용

@Controller
public class ResponseBodyController {
	
	@GetMapping("/v1/response-body")
	public void responseBodyV1(
														HttpServletResponse response
													) throws IOException {
		
		response.getWriter().write("data");
	
	}
}

Postman

  • Response Body에 data 라는 문자열이 입력되어 응답된다.
  • 기존 Servlet을 다룰 때 코드와 형태가 같다.

 

ResponseEntity<> 사용

@GetMapping("/v2/response-body")
public ResponseEntity<String> responseBodyV2() {
		
	return new ResponseEntity<>("data", HttpStatus.OK);
}

Postman

  • Response Body에 data라는 문자열과 HttpStatus.OK에 해당하는 상태 코드를 반환한다.
  • ResponseEntity는 HttpEntity 를 상속받았다.
    • HttpEntity는 HTTP Message의 Header, Body 모두 가지고 있다.

 

@ResponseBody(TEXT, JSON) 사용

@Data
@NoArgsConstructor // 기본 생성자
@AllArgsConstructor // 전체 필드를 인자로 가진 생성자
public class Tutor {

    private String name;
    private int age;

}
// TEXT 데이터 통신
@ResponseBody
@GetMapping("/v3/response-body-text")
public String responseBodyText() {
		
	return "data"; // HTTP Message Body에 "data"
}

 

Postman

// JSON 데이터 통신
@ResponseBody
@GetMapping("/v3/response-body-json")
public Tutor responseBodyJson() {
		
	Tutor tutor = new Tutor("wonuk", 100);
		
	return tutor; // HTTP Message Body에 Tutor Object -> JSON
}

Postman

  • View를 사용하는 것이 아닌 HTTP Message Converter를 통해 HTTP Message Body를 직접 입력할 수 있다. → ResponseEntity와 같음
  • @ResponseStatus 를 사용하여 상태 코드를 지정할 수 있다.

 

@ResponseStatus(HttpStatus.OK)
@ResponseBody
@GetMapping("/v4/response-body")
public Tutor responseBodyV4() {
		
	Tutor tutor = new Tutor("wonuk", 100);
		
	return tutor;
}

Postman

  • 단, 응답 코드를 조건에 따라서 동적으로 변경할 수는 없다.

ex) 1번의 경우 OK, 2번의 경우 Created

 

 

ResponseEntity<Object>(JSON)

@ResponseBody
@GetMapping("/v5/response-body")
public ResponseEntity<Tutor> responseBody() {
		
	Tutor tutor = new Tutor("wonuk", 100);
	
	return new ResponseEntity<>(tutor, HttpStatus.OK);
}

Postman

  • ResponseEntity<>두 번째 파라미터에 Enum을 사용하여 상태 코드를 바꿀 수 있다.
  • HTTP Message Converter를 통하여 JSON 형태로 변환되어 반환된다.
  • 동적으로 응답 코드를 변경할 수 있다.
@ResponseBody
@GetMapping("/v5/response-body")
public ResponseEntity<Tutor> responseBody() {
		
	Tutor tutor = new Tutor("wonuk", 100);
	
	if (조건) {
		return new ResponseEntity<>(tutor, HttpStatus.OK);
	} else {
		return new ResponseEntity<>(tutor, HttpStatus.BAD_REQUEST);
	}
	
}
  • HttpEntity를 상속받았다.

 

 

 

정리

 

Client에서 Server로 Data를 전달하는 세가지 방법

  1. GET - Query Param, Query String

ex) http://localhost:8080/tutor?name=wonuk&age=100

  • 사용하는 어노테이션
    • @RequestParam, @ModelAttribute
  1. POST - HTML Form(x-www-form-urlencoded)
POST /form-data
content-type: application/x-www-form-urlencoded

**key1=value1&key2=value2**
  • 사용하는 어노테이션
    • @RequestParam, @ModelAttribute
  1. HTTP Request Body

ex) 데이터(JSON, TEXT, XML 등)를 직접 HTTP Message Body에 담아서 사용한다.

  • 사용하는 어노테이션
    • @RequestBody

 

 

Server(Spring)에서 HTTP 응답을 Client에 전달하는 세가지 방법

  1. 정적 리소스
    • 정적인 HTML, CSS, JS, Image 등을 변경없이 그대로 반환한다.

  1. View Template
    • SSR(Server Side Rendering)을 사용할 때 View가 반환된다.
    • 사용하는 어노테이션
      • @Controller
       

 

  1. HTTP Message Body
    • 응답 데이터를 직접 Message Body에 담아 반환한다.
    • 사용하는 어노테이션
      • @ResponseBody, ResponseEntity<Object>
       

 

  • 요청
    • @RequestParam, @ModelAttribute, @RequestBody
  • 응답
    • 정적 리소스, View Template(@Controller), @ResponseBody, ResponseEntity<Object>

'Back-End (Web) > Spring' 카테고리의 다른 글

[Spring] Spring의 핵심 개념  (0) 2024.12.24
[Spring] Layered Architecture  (2) 2024.12.20
[Spring] HTTP Message Body & TEXT  (0) 2024.12.18
[Spring] @RequestParam & @ModelAttribute  (0) 2024.12.17
[Spring] Request Mapping  (1) 2024.12.16

JSON

📌 데이터를 표현하기 위한 가볍고 간단한 텍스트 기반의 데이터 교환 형식입니다. 키-값 쌍으로 데이터를 구조화하며, 사람과 기계가 읽고 쓰기 쉽도록 설계되었습니다. 웹 애플리케이션과 서버 간의 데이터 교환에서 널리 사용됩니다.

  • Json은 @RestController 에서 가장 많이 사용되는 데이터 형식이다. 현재 대부분의 API는 Request, Response 모두 JSON 형태로 통신한다.
  • Json 형태로 Data를 전송할 때는 Request Header의 content-type이 꼭 application/json 이여야 한다.

 

JSON의 특징

  1. 가독성:
    • 간단한 문법으로 사람이 읽기 쉽습니다.
  2. 경량성:
    • XML에 비해 데이터 표현이 간결하여 네트워크 트래픽이 적습니다.
  3. 언어 독립성:
    • 대부분의 프로그래밍 언어에서 JSON을 쉽게 생성하고 파싱할 수 있습니다.
  4. 구조화된 데이터 표현:
    • 객체, 배열, 숫자, 문자열 등 다양한 데이터 타입을 지원합니다.

 

JSON의 데이터 타입

문자열 "hello" 큰따옴표로 묶인 텍스트.
숫자 123, 45.67 정수 및 실수 지원.
불리언 true, false 논리 값.
객체 { "key": "value" } 키-값 쌍의 집합.
배열 [1, 2, 3] 여러 값의 순서 있는 리스트.
null null 값이 없음.

 

JSON 처리 (Java 예제)

a. Java 객체 → JSON 변환

Spring Boot에서 Jackson 라이브러리를 사용하여 JSON 데이터를 처리할 수 있습니다.

import com.fasterxml.jackson.databind.ObjectMapper;

public class JsonExample {
    public static void main(String[] args) throws Exception {
        ObjectMapper objectMapper = new ObjectMapper();

        // 객체 생성
        User user = new User("John", 30);

        // 객체를 JSON으로 변환
        String json = objectMapper.writeValueAsString(user);
        System.out.println(json);
    }
}

class User {
    private String name;
    private int age;

    // Constructor, Getters, Setters
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Getters and Setters
}

 

JSON과 XML 비교

  JSON XML
가독성 간결하고 읽기 쉬움 태그가 많아 다소 복잡.
데이터 크기 작음 크기가 큼.
파싱 속도 빠름 느림.
데이터 표현 객체 및 배열을 자연스럽게 표현 객체와 배열 표현이 불편함.

 

 

JSON의 장점

  1. 간단하고 가볍다:
    • 데이터 표현이 직관적이고 XML에 비해 크기가 작음.
  2. 언어 독립적:
    • JavaScript뿐만 아니라 Python, Java, PHP 등 다양한 언어에서 지원.
  3. API 표준:
    • RESTful API에서 사실상의 표준 형식으로 사용.

 

JSON 요청

1. HttpServletRequest 사용

더보기
@Data
public class Tutor {
	private String name;
	private int age;
}

@RestController
public class JsonController {
	
	private ObjectMapper objectMapper = new ObjectMapper();

	@PostMapping("/v1/request-body-json")
	public void requestBodyJsonV1(
				HttpServletRequest request, 
				HttpServletResponse response
	) throws IOException {

		// request body message를 Read
		ServletInputStream inputStream = request.getInputStream();
		// UTF-8 형식의 String으로 변환한다.
		String requestBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);

		// String requestBody를 ObjectMapper를 사용하여 변환 "{\"name\":\"wonuk\", \"age\":10}"
		Tutor tutor = objectMapper.readValue(requestBody, Tutor.class);
		
		// 응답
		response.getWriter().write("tutor" + tutor);
	}
}
  • Postman
    • Content-Type 헤더 확인 : application/json

 

  • HttpServletRequest를 사용하여 HTTP Message Body 데이터를 Read하여 문자로 변환한다.
  • 문자로 만들어진 JSON을 Jackson 라이브러리의 objectMapper를 사용하여 Object로 변환

 

 

 

 

 

 

 

2. @RequestBody 사용

더보기
@RestController
public class JsonController {
	
	private ObjectMapper objectMapper = new ObjectMapper();

	@PostMapping("/v2/request-body-json")
  public String requesBodytJsonV2(@RequestBody String requestBody) throws IOException {

      Tutor tutor = objectMapper.readValue(requestBody, Tutor.class);

      return "tutor.getName() = " + tutor.getName() + "tutor.getAge() = " + tutor.getAge();
  }
    
}
Postman

@RequestBody를 사용하여 HTTP Request Body의 Data에 접근한다.

3. ObjectMapper 제거

더보기
@RestController
public class JsonController {
	
	@PostMapping("/v3/request-body-json")
	public String requestBodyJsonV3(@RequestBody Tutor tutor) {
		
		Tutor requestBodyTutor = tutor;

		return "tutor = " + requestBodyTutor;
	}
}
Postman

 

위 Controller가 동작하는 이유는 무엇인가요?
  • @RequestBody
    • @RequestBody 어노테이션을 사용하면 Object를 Mapping할 수 있다.
    • HttpEntity<>, @RequestBody를 사용하면 HTTPMessageConverter가 Request Body의 Data를 개발자가 원하는 String이나 Object로 변환해준다.
    • JSON to Object의 Mapping 또한 가능하다.
      • MappingJackson2HttpMessageConverter 의 역할
      • 쉽게 설명하면 HTTP Message Converter가 ObjectMapper를 대신 실행한다.

4. @RequestBody는 생략할 수 없다.

더보기

@RequstParam, @ModelAttribute는 생략이 가능하다.

 

@Slf4j
@RestController
public class JsonController {
	
	@PostMapping("/v4/request-body-json")
  public String requestBodyJsonV4(Tutor tutor) { // @RequestBody 생략시 @ModelAttribute가 된다.

      Tutor requestBodyTutor = tutor;

      return "tutor.getName() = " + requestBodyTutor.getName() + " tutor.getAge() = " + requestBodyTutor.getAge();
  }
	
}
Postman
  • 생략하면 @ModelAttribute가 된다.
    • 요청 파라미터를 처리하도록 설정된다.
  • Request Header의 contentType은 꼭 application/json 이여야 한다.
    • 위 설정 정보를 기반으로 MessageConverter가 실행된다.

 

5. HttpEntity 사용

더보기
@RestController
public class JsonController {
	
	@PostMapping("/v5/request-body-json")
  public String requestBodyJsonV5(
          HttpEntity<Tutor> httpEntity
  ) {
      // 값을 꺼내서 사용해야한다!
      Tutor tutor = httpEntity.getBody();

      return "tutor.getName() = " + tutor.getName() + " tutor.getAge() = " + tutor.getAge();
  }
	
}

 

Postman
  • HttpEntity<Tutor>
    • Generic Type으로 Tutor가 지정되어 있기 때문에 해당 Class로 반환된다.

 

6. @ResponseBody

더보기
@Controller
public class JsonController {
	
	@ResponseBody // @RestController = @Controller + @ResponseBody
	@PostMapping("/v6/request-body-json")
    public Tutor requestJson(@RequestBody Tutor tutor) {
        return tutor;
  }
	
}
Postman

 

  • View를 조회하지 않고 Response Body에 Data를 입력해서 직접 반환한다.
  • 요청 뿐만이 아니라 응답에도 HttpMessageConverter가 동작한다.
    • MaapingJackson2HttpMessageConverter 적용
    • 응답 객체인 Tutor가 JSON으로 변환되어 반환된다.
  • HttpEntity를 사용해도 된다.

 

요약

  1. 요청 데이터는 @RequestBody를 사용해서 바인딩 하면 된다.
  2. @RequestBody 는 생략이 불가능하다.
    • @ModelAttribute가 적용되기 때문
  3. HttpMessageConverter 가 요청 응답 데이터를 모두 변환할 수 있다.
    • JSON은 MappingJackson2HttpMessageConverter 를 사용한다.
    • Request Header의 Content-Type은 application/json 이어야 한다.
      • Header로 어떤 Converter가 동작할지 판별한다.

 

 

쿼리 파라미터(Query Parameter)

📌 HTTP 요청 URL의 일부로, Key-Value 형태로 데이터를 전달하는 방법

  • 쿼리 파라미터는 URL 경로 끝에 **?**로 시작하며, 여러 개의 파라미터는 **&**로 구분됩니다.
구조
<URL>?<key1>=<value1>&<key2>=<value2>

 

특징

  1. 클라이언트가 서버로 데이터를 전달할 때 사용.
  2. GET 요청에서 주로 사용되며, 요청 URL에 데이터를 포함.
  3. 데이터가 URL에 노출되므로 민감한 정보를 보내는 데 적합하지 않음.
  4. 여러 파라미터를 전달할 수 있음.

 

 

HTML 폼 데이터(Form Data)

📌 HTML 폼(Form)을 통해 서버로 전달되는 Key-Value 형태의 데이터입니다.

  • 폼 데이터는 주로 POST 요청의 HTTP 요청 본문(body)에 포함됩니다.

 

구조

HTML 폼을 통해 데이터를 보낼 때, Key-Value 쌍으로 데이터를 전달합니다. 데이터는 HTTP 요청 본문에 포함되거나, GET 요청의 경우 쿼리 파라미터로 전송됩니다.

 

특징

  1. POST 요청에서 주로 사용되며, 데이터가 HTTP 요청 본문에 포함.
  2. GET 요청에서는 폼 데이터가 쿼리 파라미터로 전달.
  3. 데이터를 숨길 수 있으나, 민감한 데이터는 암호화(HTTPS) 필요.
  4. 주로 사용자 입력 데이터를 서버로 전달할 때 사용.

 

HTML 폼 예제

<form action="/submit" method="POST">
    <label for="name">Name:</label>
    <input type="text" id="name" name="name">
    
    <label for="email">Email:</label>
    <input type="email" id="email" name="email">
    
    <button type="submit">Submit</button>
</form>

 

POST 요청 본문 예시:

name=John&email=john.doe@example.com

 

Spring 컨트롤러에서 처리:

@PostMapping("/submit")
public String handleForm(
    @RequestParam String name,
    @RequestParam String email) {
    return "Name: " + name + ", Email: " + email;
}

 

 

쿼리 파라미터와 폼 데이터의 차이

  쿼리 파라미터 (Query Parameter) HTML 폼 데이터 (Form Data)
데이터 위치 URL 경로 끝 (쿼리 스트링) HTTP 요청 본문(body) 또는 URL 쿼리
주요 HTTP 메서드 GET POST (또는 GET)
노출 여부 데이터가 URL에 노출됨 POST 요청 시 URL에 노출되지 않음
사용 목적 짧고 간단한 데이터 전달 사용자 입력 데이터 전송
데이터 크기 제한 브라우저에 따라 URL 길이 제한 (2,000~8,000자) 요청 본문에 데이터를 보내므로 제한 없음
보안성 민감한 데이터 전달에 부적합 POST로 전달 시 상대적으로 안전 (HTTPS 권장)

 

 

예제 비교

쿼리 파라미터

GET /search?keyword=spring&category=books

 

폼 데이터

POST /submit
Content-Type: application/x-www-form-urlencoded
Content-Length: 27

name=John&email=john.doe@example.com

 

결론

  • 쿼리 파라미터는 주로 GET 요청에서 사용되며, URL에 간단한 데이터를 전달.
  • 폼 데이터는 주로 POST 요청에서 사용되며, 대량의 사용자 입력 데이터를 서버로 보낼 때 적합.

 

 

 

@RequestParam

📌 HTTP 요청의 쿼리 파라미터(Query Parameter)나 HTML 폼 데이터(Form Data)를 컨트롤러 메서드의 매개변수로 매핑하는 데 사용됩니다. 주로 클라이언트가 요청 시 전달하는 Key-Value 형식의 데이터를 처리할 때 사용됩니다.

  • URL에서 파라미터 값과 이름을 함께 전달하는 방식으로 주로 HTTP 통신 Method 중 GET 방식의 통신을 할 때 많이 사용한다. @Requestparam을 사용하면 요청 파라미터 값에 아주 쉽고 간편하게 접근(Parameter Binding)할 수 있습니다.
  • ?뒤에 오는 URL을 Query String, Query Parameter, Request Param이라 합니다.
  • GET 요청에서 쿼리 파라미터를 처리하거나, POST 요청에서 폼 데이터를 처리하는 데 사용됩니다.

 

사용 목적

  • 쿼리 파라미터(Query Parameter):
    • URL에 포함된 Key-Value 형식의 데이터를 매핑.
    • 예: GET /api/search?keyword=spring
  • 폼 데이터(Form Data):
    • HTML 폼을 통해 전송된 데이터를 매핑.
    • 예: POST 요청 본문에 name=John&email=john.doe@example.com 데이터가 포함.

주요 속성

  설명 기본값
name 요청 파라미터의 이름을 명시. 매개변수 이름과 동일
required 요청 파라미터가 필수인지 여부. true
defaultValue 요청 파라미터가 없을 때 사용할 기본값. 없음

@RequestParam과 다른 어노테이션 비교

어노테이션 설명
@RequestParam 요청의 쿼리 파라미터 또는 폼 데이터를 매핑.
@PathVariable URL 경로의 변수 값을 매핑.
@RequestBody HTTP 요청 본문(body)의 데이터를 매핑 (JSON, XML 등).

 

주의 사항

  1. 필수 속성 처리:
    • 기본적으로 @RequestParam은 요청 파라미터가 없으면 에러를 발생시킵니다.
      필요한 경우 required = false를 명시하거나, defaultValue를 설정하세요.
  2. 타입 변환:
    • Spring은 문자열 값을 기본적으로 매핑하며, 필요한 경우 자동으로 타입 변환을 지원합니다.
      단, 변환할 수 없는 타입이 지정되면 예외가 발생합니다.

 

@Slf4j
@Controller
public class RequestParamControllerV2 {

	@ResponseBody
	@GetMapping("/v1/request-param")
	public String requestParamV1 (
					@RequestParam("name") String userName,
					@RequestParam("age") int userAge													
	) {
		// logic
		log.info("name={}", userName);
    log.info("age={}", userAge);
		return "success";
	}

}
  1. @Controller + @ResponseBody
    • View를 찾는 것이 아니라 ResponseBody에 응답을 작성한다(=@RestController)
  2. @RequestParam
    • 파라미터 이름으로 바인딩한다.
  3. @RequestParam(”속성값”)
    • 속성값이 파라미터 이름으로 매핑된다.

Postman
출력 결과

 

“속성값”과 변수명이 같으면 생략이 가능하다.

ex) @RequestParam("name") String name

// GET http://localhost:8080/v2/request-param?name=sparta&age=100
@ResponseBody
@GetMapping("/v2/request-param")
public String requestParamV2 (
				@RequestParam String name,
				@RequestParam int age													
) {
	// logic
	log.info("name={}", name);
  log.info("age={}", age);
	return "success";
}

 

 

 

 

@RequestParam 사용법

1. 어노테이션, 속성값 모두 생략

  • @RequestParam은 생략이 가능하다.
// GET http://localhost:8080/v3/request-param?name=sparta&age=100
@ResponseBody
@GetMapping("/v3/request-param")
public String requestParamV3 (
				String name,
				int age													
) {
	// logic
	log.info("name={}", name);
  log.info("age={}", age);
	return "success";
}
  • 생략하면 @RequestParam(required=false) 필수 여부 속성이 default로 설정된다.
  • 단, 요청 파라미터와 이름이 완전히 같아야 한다.
  • 단순 타입(int, String, Integer 등)이어야 한다.

 

위의 방식은 권장하지 않습니다. 명시적으로 표시되어있지 않으면 팀의 협의가 있지 않는 경우 다른 개발자들에게 혼동을 주게 됩니다. 최소 @RequestParam String name 속성 값 생략 형태를 쓰면 됩니다.

 

2. required 속성 설정

  • 파라미터의 필수 값을 설정한다.
  • API 스펙을 규정할 때 사용한다.
@ResponseBody
@GetMapping("/v4/request-param")
public String requestParam (
				@RequestParam(required = true) String name, // 필수
				@RequestParam(required = false) int age	// 필수가 아님										
) {
	// logic
	log.info("name={}", name);
  log.info("age={}", age);
	return "success";
}

 

  • @RequestParam을 사용하면 기본 Default값은 True이다.
    • True로 설정된 파라미터 값이 요청에 존재하지 않으면 400 BadRequest(클라이언트 측 에러)
  • Exception이 발생하지 않는 경우

ex) http://localhost:8080/v4/request-param?name=sparta&age=100

  • Exception이 발생하는 경우

ex) http://localhost:8080/v4/request-param?age=100

 

ex) http://localhost:8080/v4/request-param

  • required = false 설정이 되어있으면 해당 파라미터는 없어도 된다.
    • 주의! http://localhost:8080/v4/request-param?name=sparta 요청한다면?

  • 500 Error가 발생한다.
  • int Type에는 null을 넣을 수 없다. 0이라도 들어가야 한다.

 

따라서 보통 null을 허용하는 Integer로 사용하거나 default 옵션을 사용한다.

@ResponseBody
@GetMapping("/v4/request-param")
public String requestParam (
				@RequestParam(required = true) String name, // 필수
			  @RequestParam(required = false) Integer age										
) {
	// logic
	log.info("name={}", name);
  log.info("age={}", age);
	return "success";
}

 

  • 파라미터 Key값만 있고 Value가 없는 경우 http://localhost:8080/request-param?name=

  • null과 빈 문자열 “”은 다르다!
  • 위 형태는 빈 문자열 “” 로 인식한다, True지만 통과가 되어버린다. 주의해야 한다.

 

 

3. default 속성 적용

  • 파라미터의 기본 값을 설정한다.
@ResponseBody
@GetMapping("/v5/request-param")
public String requestParam (
				@RequestParam(required = true, defaultValue = "sparta") String name,
		    @RequestParam(required = false, defaultValue = "1") int age											
) {
	// logic
	log.info("name={}", name);
  log.info("age={}", age);
	return "success"	
}

 

  • name Parameter 의 값이 없으면 기본적으로 “sparta”으로 설정한다

ex) http://localhost:8080/v5/request-param?age=100

  • age Parameter의 값이 없으면 기본적으로 1 으로 설정한다.

ex) http://localhost:8080/v5/request-param?name=wonuk

ex) http://localhost:8080/v5/request-param

  • 주의! defaultValue 속성을 설정하게 되면 “” 빈 문자열의 경우에도 기본 값이 설정된다.

ex) http://localhost:8080/v5/request-param?name&age

 

4. Map 사용

  • Parameter를 Map형태로 조회가 가능하다.
@ResponseBody
@GetMapping("/v6/request-param")
public String requestParamV6(
        @RequestParam Map<String, String> map
) {
    // logic
    log.info("name={}", map.get("name"));
    log.info("age={}", map.get("age"));

    return "success";
}

 

  • Map 형태(key=value)로 조회가 가능하다

ex) http://localhost:8080/v6/request-param?name=sparta&age=100

  • MultiValueMap 형태(key=[value1, value2])로 조회가 가능하다.
@ResponseBody
@GetMapping("/v6/request-param")
public String requestParamV6(
        @RequestParam MultiValueMap<String, String> map
) {
    // logic
    log.info("name={}", map.get("name"));
    log.info("age={}", map.get("age"));

    return "success";
}
  • ex) http://localhost:8080/v6/request-param?
  • name=sparta&name=wonuk&name=tutor&age=100

💡 파라미터 Map의 Value가 1개인 경우에는 Map, 여러 개인 경우 MultiValueMap을 사용한다. 하지만 대부분의 파라미터 값은 한 개만 존재한다.

 

 

 

@ModelAttribute

📌 HTTP 요청 데이터(Form 데이터, 쿼리 파라미터 등)를 객체에 바인딩하거나, 뷰에 데이터를 전달하는 데 사용됩니다.

  • 요청 파라미터를 받아 필요한 Object로 바인딩 해준다. 주로 HTML 폼에서 전송된 데이터를 바인딩하고 HTTP Method POST인 경우 사용된다.
  • 매개변수에 사용: 요청 데이터를 객체로 매핑.
  • 메서드에 사용: 뷰에 공통 데이터를 추가.

 

주요 역할

1. 요청 데이터를 객체로 바인딩

  • 클라이언트에서 전달된 **요청 데이터(쿼리 파라미터, 폼 데이터)**를 자동으로 객체 필드에 바인딩합니다.
  • 주로 POST 요청의 폼 데이터를 처리할 때 사용됩니다.

2. 뷰(View)에 데이터 전달

  • 컨트롤러 메서드에서 반환 값이 아닌, 뷰에 데이터를 추가합니다.
  • 뷰에 공통적으로 전달해야 하는 데이터를 설정할 때 유용합니다.

 

 

특징 및 동작

1. 객체 생성 및 데이터 바인딩

  • Spring은 @ModelAttribute를 사용하면 다음 단계를 수행합니다:
    1. 지정된 클래스의 객체를 생성합니다.
    2. 요청 데이터(쿼리 파라미터, 폼 데이터)를 객체 필드에 매핑합니다.

2. 요청 방식에 따른 동작

  • GET 요청: 객체를 초기화하거나 뷰에 데이터를 전달.
  • POST 요청: 폼 데이터를 객체에 바인딩.

3. 생략 가능

  • @ModelAttribute는 생략 가능하며, 매개변수가 사용자 정의 객체라면 Spring이 자동으로 @ModelAttribute를 적용합니다.

 

주요 속성

  설명
value @ModelAttribute("name") 형식으로 모델 객체의 이름을 지정.
뷰에 데이터 전달 메서드에 사용하면 데이터를 뷰에 전달.
객체 바인딩 매개변수에 사용하면 요청 데이터를 객체에 자동 바인딩.

 

 

주의 사항

  1. 필드 이름 매칭:
    • 요청 데이터의 Key와 객체 필드 이름이 동일해야 바인딩됩니다.
    • Key와 필드 이름이 다르면 바인딩되지 않습니다.
  2. 유효성 검증:
    • @Valid와 함께 사용하여 요청 데이터를 검증할 수 있습니다
  3. 중복 데이터 처리:
    • 동일한 이름의 데이터를 여러 메서드에서 설정하면, 마지막으로 설정된 데이터가 우선합니다.
@PostMapping("/register")
public String registerUser(@Valid @ModelAttribute User user, BindingResult result) {
    if (result.hasErrors()) {
        return "form";
    }
    return "success";
}

 

 

기존 코드

@Data는 @Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgsConstructer를 자동으로 설정해주는 역할을 한다. 테스트 용도로만 사용하고 실무에서는 잘 사용하지 않는다.

 

  • ex) http://localhost:8080/v1/tutor + x-www-form-urlencoded
@Data
public class Tutor {

	private String name;
	private int age;

}

@Controller
public class ModelAttributeController {

	@ResponseBody
  @PostMapping("/v1/tutor")
  public String requestParamV1(
          @RequestParam String name,
          @RequestParam int age
  ) {
      Tutor tutor = new Tutor();
      tutor.setName(name);
      tutor.setAge(age);

      return "tutor name = " + name + " age = " + age;
  }

}

 

  • @RequestParam 의 Mapping을 사용하게 되면 위와 같은 객체를 생성하는 코드가 포함된다.
    • @ModelAttribute 는 해당 과정을 자동화 한다.
  • Postman
POST /v1/tutor
content-type: application/x-www-form-urlencoded

name=wonuk&age=100

 

 

@ModelAttribute 적용

  • ex) http://localhost:8080/v2/tutor+ x-www-form-urlencoded
@ResponseBody
@PostMapping("/v2/tutor")
public String modelAttributeV2(
				@ModelAttribute Tutor tutor													
) {
		
	String name = tutor.getName();
	int age = tutor.getAge();

	return "tutor name = " + name + " age = " + age;
}
  • Postman
POST /v2/tutor
content-type: application/x-www-form-urlencoded

name=wonuk&age=100

 

  • @ModelAttirubte 동작 순서
    1. 파라미터에 @ModelAttribute가 있으면 파라미터인 Tutor 객체를 생성한다.
    2. 요청 파라미터 이름으로 객체 필드의 Setter를 호출해서 바인딩한다.
      1. 파라미터 이름이 name 이면 setName(value); 메서드를 호출한다.
      2. 파라미터 이름과 필드 이름이 반드시 같아야 한다!
  • @Data의 Setter가 없다면?
@Getter
public class Tutor {

	private String name;
	private int age;

}

  • 객체 필드에 값이 set되지 않는다.

 

  • 파라미터의 타입이 다른 경우
    • 만약 요청 파라미터 age 에 int가 아닌 String 이 전달된다면?
    ex) http://localhost:8080/v2/tutor+ x-www-form-urlencoded
POST /v2/tutor
content-type: application/x-www-form-urlencoded

name=wonuk&age=nbcamp

  • BindException 발생
  • 이런 경우 때문에 **Validation(검증)**이 필요하다.

 

@ModelAttirubte 생략

  • @ModelAttribute와 지난 시간에 배운 @RequestParam은 모두 생략이 가능하다.

ex) http://localhost:8080/v3/tutor+ x-www-form-urlencoded

POST /v3/tutor
content-type: application/x-www-form-urlencoded

name=wonuk&age=100
@ResponseBody
@PostMapping("/v3/tutor")
public String modelAttributeV3(Tutor tutor) {

	String name = tutor.getName();
	int age = tutor.getAge();

	return "tutor name = " + name + " age = " + age;
}
  • Spring에서는 @RequestParam이나 @ModelAttribute가 생략되면
    • String, int, Integer 와 같은 기본 타입은 @RequestParam과 Mapping한다. V4
@ResponseBody
@PostMapping("/v4/tutor")
public String requestParamV2(
        String name,
        int age
) {
    return "tutor name = " + name + " age = " + age;
}

 

  • 나머지 경우들(객체)은 모두 @ModelAttribute 와 Mapping한다. V3

 

 

@RequestParam vs @ModelAttribute 차이

 

주요 차이점

  @RequestParam @ModelAttribute
목적 특정 **단일 파라미터(쿼리 파라미터, 폼 데이터)**를 매핑. 요청 데이터를 객체 형태로 매핑.
대상 데이터 단일 Key-Value 쌍 (예: ?key=value) 여러 Key-Value 쌍을 객체의 필드에 매핑.
적용 대상 개별 매개변수. 객체.
데이터 출처 쿼리 파라미터, 폼 데이터. 폼 데이터, 쿼리 파라미터, 경로 변수 등.
기본값 설정 defaultValue 속성을 통해 가능. 객체 필드에 기본값을 설정하거나, 빈 객체 생성.
뷰 데이터 전달 뷰 데이터 전달 기능 없음. 뷰에 데이터를 전달할 수 있음 (메서드 레벨 사용).
생략 가능 여부 생략 불가능 (명시적으로 사용해야 함). 매개변수에서 생략 가능 (Spring이 자동으로 객체를 매핑).
주요 사용 사례 단순 쿼리 파라미터 또는 단일 데이터 처리. 폼 데이터나 복잡한 데이터 구조(객체) 처리.

 

사용 목적에 따른 선택

@RequestParam

  • 요청 데이터가 단순한 Key-Value 쌍이고, 각각 개별적으로 처리할 경우.
  • 쿼리 파라미터나 단일 폼 데이터 값에 적합.

@ModelAttribute

  • 요청 데이터가 객체로 표현 가능한 경우.
  • 복잡한 폼 데이터(여러 필드가 있는 객체)를 처리하거나, 뷰와 데이터를 쉽게 연동할 때 유리.

 

요약

상황 선택
단일 쿼리 파라미터 처리. @RequestParam
복잡한 폼 데이터를 객체로 매핑. @ModelAttribute
기본값 설정이 필요한 경우. @RequestParam
뷰에 데이터를 전달해야 하는 경우. @ModelAttribute
객체 기반 데이터 처리가 필요한 경우. @ModelAttribute

 

결론

  • **@RequestParam**은 단일 데이터(Key-Value) 처리에 적합하며, 간단한 요청에 사용됩니다.
  • **@ModelAttribute**는 폼 데이터나 요청 데이터를 객체 형태로 처리하거나, 뷰와 연동된 데이터를 전달할 때 유용합니다.
  • @RequestParam, @ModelAttribute는 GET + Query Parameter와, POST HTML Form Data를 바인딩하는 방법이다.

 

HTTP Message Body (요청)

📌 HTTP 요청(Request) 또는 응답(Response)에서 실제 데이터를 담는 부분입니다.

  • 주로 클라이언트와 서버 간에 전송되는 JSON, XML, HTML, 바이너리 파일, 텍스트 등 다양한 콘텐츠를 포함합니다.

 

특징

  • 이제부터 배울 내용은 HTTP Message Body에 직접적으로 Data가 전달되는 경우이다.
    • Request Body의 Data를 바인딩하는 방법이다.
  • REST API에서 주로 사용하는 방식이다.
  • HTTP Method POST, PUT, PATCH에서 주로 사용한다.
    • GET은 Request Body가 존재할 수는 있지만 권장하지 않는다.
  • JSON, XML, TEXT 등을 데이터 형식으로 사용한다.

 

 

HTTP Message 구조

 

 

HTTP Request, Response 예시

  • Server에서 Request로 전달받은 Data를 처리하기 위해서 바인딩 해야 한다.

ex) JSON → Object

 

 

 

TEXT

📌  HTTP Request Body에 Data가 전송되는 경우 HttpMessageConverter를 통해 바인딩된다.

  • 현대에는 Restful API를 주로 사용하고 있어서 대부분의 경우 JSON 형식으로 통신한다.

 

HttpServletRequest 예시

  • request.getInputStream();
@Slf4j
@Controller
public class RequestBodyStringController {
	
	@PostMapping("/v1/request-body-text")
  public void requestBodyTextV1(
          HttpServletRequest request,
          HttpServletResponse response
  ) throws IOException {

      ServletInputStream inputStream = request.getInputStream();
      String bodyText = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
      
      response.getWriter().write("response = " + bodyText);

  }

}
  • Request → Body → raw → Text

  • Request Header Content-Type : text/plain

 

I/O 예시

  • InputStream(읽기) 파라미터 지원
    • HTTP Request Body Data 직접 조회
  • OutputStream(쓰기) 파라미터 지원
    • HTTP Response Body 직접 결과 출력
@PostMapping("/v2/request-body-text")
public void requestBodyTextV2(
				InputStream inputStream,
				Writer responseWriter
) throws IOException { 
		
	String body = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
	
	responseWriter.write("response = " + bodyText);
}

Postman

HttpEntity 예시

  • HttpMessageConverter 사용
  • HttpEntity를 사용하면 HttpMessageConverter를 사용한다.
@PostMapping("/v3/request-body-text")
public HttpEntity<String> requestBodyTextV3(HttpEntity<String> httpEntity) { 
		
	// HttpMessageConverter가 동작해서 아래 코드가 동작하게됨
	String body = httpEntity.getBody();
		
	return new HttpEntity<>("response = " + body); // 매개변수 = Body Message
	
}

 

Postman

  • Spring의 HttpMessageConverter 덕분에 간편하게 Request Data에 접근할 수 있다.
    1. HttpEntity를 사용하면 HttpMessageConverter가 동작하여 자동으로 매핑된다.
    2. 요청 뿐만이 아닌 응답까지 HttpEntity 하나만으로 사용이 가능해진다.

 

 

HttpEntity

📌 HTTP 요청(Request)와 응답(Response)의 헤더(Header)와 본문(Body)을 캡슐화하는 데 사용됩니다.

  • HttpEntity는 HTTP Header, Body 정보를 편리하게 조회할 수 있도록 만들어준다.

 

주요 특징

  • 헤더(Header)와 바디(Body) 모두를 포함:
    • HTTP 요청 또는 응답의 헤더와 바디를 관리.
  • Immutable 객체:
    • HTTP 메시지를 불변(Immutable) 상태로 다룸.
  • Spring의 HTTP 클라이언트와 서버 모두에서 사용 가능:
    • REST API 호출에서 요청을 보낼 때, 응답 데이터를 받을 때 활용.

 

HttpEntity 역할

  1. Http Request Body Message를 직접 조회한다
  2. Request 뿐만 아니라 Response도 사용할 수 있도록 만들어준다.
  3. Response Header 또한 사용할 수 있다.
  4. Request Parameter를 조회하는 기능들과는 아무 관계가 없다.
  5. View를 반환하지 않는다.

 

주요 클래스

HttpEntity는 Spring에서 두 가지 주요 클래스의 기반이 됩니다:

  1. RequestEntity:
    • HTTP 요청을 표현.
    • 요청의 헤더, URL, HTTP 메서드 등을 캡슐화.
    • HTTP Request Method, URL 정보가 추가 되어있다.
  2. ResponseEntity:
    • HTTP 응답을 표현.
    • 응답의 상태 코드, 헤더, 바디를 관리.
    • HTTP Response 상태 코드 설정이 가능하다.
@Controller
public class RequestBodyStringController {
	
	@PostMapping("/v4/request-body-text")
  public HttpEntity<String> requestBodyTextV4(RequestEntity<String> httpEntity) {

      // HttpMessageConverter가 동작해서 아래 코드가 동작하게됨
      String body = httpEntity.getBody();
      // url, method 사용 가능

      return new ResponseEntity<>("response = " + body, HttpStatus.CREATED); // Body Data, 상태코드
  }

}

 

Postman

  • 위 방법을 적용해도 불편하다.
    • Data를 httpEntity에서 꺼내어 사용해야 한다.
Spring은 Http RequestBody Message를 읽어서 String이나 Object로 자동으로 변환해준다. 이때 HttpMessageConverter가 사용된다. 

 

 

@RequestBody, @ResponseBody

📌 Spring에서 @RequestBody, @ResponseBody 어노테이션을 사용하면 각각 Request, Response 객체의 Body에 편하게 접근하여 사용할 수 있다.

@Controller // @RestController = @Controller + @ResponseBody
public class RequestBodyStringController {
	
  @ResponseBody
  @PostMapping("/v5/request-body-text")
  public String requestBodyTextV5(
          @RequestBody String body,
          @RequestHeader HttpHeaders headers
  ) {
      // HttpMessageConverter가 동작해서 아래 코드가 동작하게됨
      String bodyMessage = body;

      return "request header = " + headers + " response body = " + bodyMessage;
  }
}

Postman

  • @RequestBody
    • 요청 메세지 Body Data를 쉽게 조회할 수 있다.
  • @RequestHeader
    • 요청 헤더 정보 조회
  • @ResponseBody
    • 응답 메세지 바디에 값을 쉽게 담아서 전달할 수 있도록 해준다.
    • View가 아닌 데이터를 반환한다.

 

요약

  1. 요청 파라미터, HTML Form Data에 접근하는 경우
    • @RequestParam, @ModelAttribute 를 사용한다.
  2. Http Message Body에 접근하는 경우
    • @RequestBody를 사용한다. (JSON, XML, TEXT)

 

 

HTTPMessageConverter

📌 HTTP 요청과 응답의 본문(Body)을 Java 객체와 직렬화 가능한 데이터(JSON, XML, 텍스트 등)로 변환하는 역할을 합니다.

  • Spring MVC는 클라이언트와 서버 간 데이터를 효율적으로 교환하기 위해 HTTPMessageConverter를 사용합니다.
  • MappingJackson2HttpMessageConverter는 JSON을 처리하는 대표적인 HTTPMessageConverter의 구현체이다.

 

HttpMessageConverter의 역할

  • 데이터를 Obejct로 변환한다. 대표적으로 JSON을 변환한다.

 

  1. 요청 본문 → Java 객체 변환 (@RequestBody 사용 시):
    • HTTP 요청의 JSON, XML, 텍스트 데이터를 Java 객체로 변환합니다.
  2. Java 객체 → 응답 본문 변환 (@ResponseBody 사용 시):
    • Java 객체를 JSON, XML, 텍스트 등으로 직렬화하여 클라이언트로 전송합니다.

 

 

  • @RequestBody
    • 요청 데이터 + Request Header를 참고하여 Object로 변환한다.
      • HTTP Request Body(JSON Data) → Converter(Jackson) → Object
      • Reqeust Header → Content-Type : application/json(전달할 데이터 형식)
  • @ResponseBody
    • 응답 데이터 + Accept Header를 참고하여 원하는 데이터 형식으로 변환한다.
      • Object → Converter(Jackson) → HTTP Response Body(JSON Data)
      • Request Header → Accept : application/json(허용할 데이터 형식)

 

동작 원리

Spring MVC는 요청과 응답 처리 시 다음 과정을 수행합니다:

  1. 요청 처리:
    • 클라이언트가 HTTP 요청 본문에 데이터를 포함시켜 보냅니다.
    • Spring은 HttpMessageConverter를 사용하여 요청 데이터를 Java 객체로 변환.
  2. 응답 처리:
    • 컨트롤러에서 반환한 Java 객체를 HttpMessageConverter가 JSON, XML 등으로 직렬화하여 응답 본문에 포함.

 

주요 HTTPMessageConverter 구현체

Spring은 다양한 데이터 형식에 따라 여러 HttpMessageConverter 구현체를 제공합니다:

구현체 데이터 형식 설명
MappingJackson2HttpMessageConverter JSON Jackson 라이브러리를 사용하여 JSON 변환 처리.
GsonHttpMessageConverter JSON Gson 라이브러리를 사용하여 JSON 변환 처리.
Jaxb2RootElementHttpMessageConverter XML JAXB를 사용하여 XML 변환 처리.
StringHttpMessageConverter Plain Text 문자열 데이터를 처리.
FormHttpMessageConverter Form Data (application/x-www-form-urlencoded) HTML 폼 데이터를 처리.
ByteArrayHttpMessageConverter Binary Data (byte[]) 바이너리 데이터를 처리.

 

장점

  1. 자동화:
    • 요청과 응답 데이터를 자동으로 변환하여 개발자의 부담을 줄임.
  2. 유연성:
    • JSON, XML, Plain Text 등 다양한 데이터 형식을 지원.
  3. 확장 가능성:
    • 사용자 정의 HttpMessageConverter로 맞춤형 데이터 처리 가능

 

요약

  • **HttpMessageConverter**는 HTTP 요청 및 응답의 본문 데이터를 처리하는 Spring의 핵심 컴포넌트입니다.
  • 요청 데이터를 Java 객체로 변환하거나, Java 객체를 JSON, XML 등으로 변환하여 클라이언트에 전달합니다.
  • Spring은 JSON 변환을 기본적으로 지원하며, 필요에 따라 사용자 정의 구현체를 추가할 수 있습니다.

'Back-End (Web) > Spring' 카테고리의 다른 글

[Spring] Server에서 Client로 Data를 전달하는 방법  (0) 2024.12.19
[Spring] HTTP Message Body & TEXT  (0) 2024.12.18
[Spring] Request Mapping  (1) 2024.12.16
[Spring] Spring Annotation  (1) 2024.12.15
[Spring] Spring MVC 패턴  (1) 2024.12.14

@RequestMapping

📌클라이언트의 요청 URL과 이를 처리할 컨트롤러의 클래스나 메서드를 매핑하는 데 사용됩니다.

  • 특정 URL로 Request를 보내면 들어온 요청을 Controller 내부의 특정 Method와 Mapping 하기 위해 사용한다.
  • Client로부터 요청이 왔을 때 어떤 Controller가 호출될지 Mapping하는것은 단순히 URL로 Mapping 하는것이 아니라 여러가지 요소(URL, Method 등)를 조합하여 Mapping한다.

 

@RequestMapping과 Handler Adapter의 차이

@RequestMapping Handler Adapter
HTTP 요청과 컨트롤러 메서드를 연결하는 어노테이션 DispatcherServlet과 핸들러(컨트롤러) 사이를 연결하는 구현체
메타데이터로써 동작 (어떤 요청이 어떤 메서드와 연결될지) 요청을 실행 가능한 형태로 변환하고 핸들러 실행.
개발자가 컨트롤러 메서드에 명시적으로 사용. Spring 내부에서 동작하며, 개발자가 직접 호출하지 않음.

 

주요 기능

  • 요청 URL과 매핑: 특정 URL 경로로 들어오는 HTTP 요청을 처리할 메서드나 클래스와 연결.
  • HTTP 메서드 지정: GET, POST, PUT, DELETE 등 특정 HTTP 메서드에만 반응하도록 설정.
  • 요청 조건 추가: 요청 헤더, 파라미터 등 특정 조건에 맞는 요청만 처리 가능.

 

요청 종류

  • @RequestMapping
    1. Spring Boot 3.0 버전 이하
      • URL path /example, /example**/** 모두 허용(Mapping)한다.
    2. Spring Boot 3.0 버전 이상(현재 버전)
      • URL path /example 만 허용(Mapping)한다.
    3. 속성값들을 설정할 때 배열 형태로 다중 설정이 가능하다
    ex) @RequestMapping**({**”/example”, “/example2”, “/example3”**})**
    1. HTTP Method POST, GET, PUT, PATCH, DELETE, HEAD 모두 허용한다
    2. method 속성으로 HTTP 메서드를 지정하면 지정된것만 허용한다.
package com.example.springbasicannotation.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

// 응답 데이터를 반환한다.
@RestController
public class RequestMappingController {

    // HTTP Method 는 GET만 허용한다.
    @RequestMapping(value = "/v1", method = RequestMethod.GET)
    public String exampleV1() {
        // logic
        return "this is sparta!";
    }

}

 

실행결과

 

  • 만약, 속성으로 설정된 HTTP Method로 요청이 오지 않는다면?
    • localhost:8080/v1 + POST, PUT, PATCH, DELETE 의 경우

  • HTTP 응답 상태코드 405(Client측 에러), Method Not Allowed Exception 반환

 

  • @GetMapping

  1. Target(ElementType.METHOD) Method Level에 해당 어노테이션을 적용한다 라는 의미
  2. 내부적으로 @RequestMapping(method = RequestMethod.GET) 을 사용하고 있다.

 

코드예시

// Post, GET, Put, Patch, Delete 모두 가능
@GetMapping(value = "/v2")
public String exampleV2() {
	// logic
	return "this is sparta!";
}
  • Spring이 제공하는 Annotation들의 내부에 다 선언되어 있다.
    • 대부분의 필요한 기능들이 이미 만들어져 있다. 사용 하면된다.
  • @RequestMapping 보다는 직관적이고 축약된 @GetMapping, @PostMapping 형식을 일반적으로 사용한다.
  • 실행 결과

 

 

  • @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping
    • 모두 위의 @GetMapping과 같은 구조를 가지고 있다.

 

 

 

 

 

  • @RequestMapping는 언제 사용하는가?

  • @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping의 Target은 Method Level 이다.
  • 반면에 @RequestMapping의 Target은 class, method 레벨에 적용이 가능하다.
  • Restful API의 계층 구조

메소드 레벨(Method Level)이란?

메소드 레벨클래스의 메서드에 적용할 수 있는 어노테이션의 사용 범위를 의미합니다.
즉, 메서드 레벨의 어노테이션은 클래스 내부의 특정 메서드에만 적용되어 동작합니다.

 

ex) users/{userId}, category/{categoryId}/product/{productId}

  • prefix로 선언할 URL을 class 레벨에 적용하는 것에 주로 사용된다.
@RequestMapping("/prefix")
@RestController
public class RequestMappingController {
	// Post, GET, Put, Patch, Delete 모두 가능
	@GetMapping(value = "/v3")
	public String exampleV3() {
		// logic
		return "this is sparta!";
	}

}

 

 

 

 

@PathVariable

📌 URL 경로에 포함된 변수를 메서드의 매개변수로 전달하기 위해 사용됩니다. 주로 RESTful 웹 서비스에서 동적인 요청 처리를 위해 사용됩니다.

  • HTTP 특성 중 하나인 비연결성을 극복하여 데이터를 전달하기 위한 방법 중 하나이다. URL로 전달된 값을 파라미터로 받아오는 역할을 수행한다.

 

동작 원리

  • URL 경로에 **변수(placeholder)**를 정의하고, 해당 값을 컨트롤러 메서드의 매개변수로 전달합니다.
  • @PathVariable은 요청 경로에서 변수 값을 추출하여 메서드 파라미터에 바인딩합니다.

 

  • @PathVariable
    1. 경로 변수를 중괄호에 둘러싸인 값으로 사용할 수 있다.
    ex) user/{id}
    1. 기본적으로 @PathVariable로 설정된 경로 변수는 반드시 값을 가져야 하며 값이 없으면 응답 상태코드 404 Not Found Error가 발생한다.
    2. 최근 Restful API를 설계하는 것이 API의 기준이 되며 해당 어노테이션의 사용 빈도가 높아졌다.
더보기

Restful API를 설계하게 되면 URL path 만으로 어떤 Resource을 사용하는지, HTTP Method 만으로 어떤 기능이 동작되는지 쉽게 알아볼 수 있다.

 

Restful API

https://restfulapi.net/resource-naming/

 

  • Create - POST
  • Read - GET
  • Update - PUT, PATCH
  • Delete - DELETE
  • Restful API 설계 예시
    • postId글의 comment 댓글 작성
      • POST + posts/{postId}/comments
    • postId글의 comment 댓글 전체 조회
      • GET + posts/{postId}/comments
    • postId글의 commentId 댓글 단 건 조회
      • GET + posts/{postId}/comments/{commentId}
    • postId글의 commentId 댓글 수정
      • PUT + posts/{postId}/comments/{commentId}
    • postId글의 commentId 댓글 삭제
      • DELETE + posts/{postId}/comments/{commentId}

 

  • @PathVariable 규칙
    1. 파라미터 변수명과 PathVariable 변수명이 같으면 속성 값 생략 가능
@RequestMapping("/posts")
@RestController
public class PathVariableController {
	
	// postId로 된 post 단건 조회
	@GetMapping("/{postId}")
	public String pathVariableV1(@PathVariable("postId") Long data) {
		// logic
		String result = "PathvariableV1 결과입니다 : " + data;
		return result;
	}
}

 

@RequestMapping("/posts")
@RestController
public class PathVariableController {
	
	// 변수명과 같다면 속성값 생략가능
	@GetMapping("/{postId}")
	public String pathVariableV2(@PathVariable Long postId) {
		// logic
		String result = "PathvariableV2 결과입니다 : " + postId;
		return result;
	}
	
}

 

 

 

2. @PathVariable 다중 사용 가능

 

@RestController
public class PathVariableController {
	
	@GetMapping("/{postId}/comments/{commentId}")
	public String pathVariableV3(
																@PathVariable Long postId,
																@PathVariable Long commentId
															) {
		// logic
		String result = "PathvariableV3 결과입니다 postId : " + postId + "commentsId : " + commentId;
		return result;
	}
	
}

@RequestMapping("/posts/{postId}")
@RestController
public class PathVariableController {
	
	@GetMapping("/comments/{commentId}")
	public String pathVariableV4(
																@PathVariable Long postId,
																@PathVariable Long commentId
															) {
		// logic
		String result = "PathvariableV4 결과입니다 postId : " + postId + "commentsId : " + commentId;
		return result;
	}
	
}

 

 

특정 파라미터 매핑

📌 속성 설정을 통하여 특정 헤더, 특정 파라미터와 Mapping 할 수 있다.

 

Parameter 추가 매핑

  • 특정 파라미터와 매핑하는 방법
package com.example.springbasicannotation.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ParameterController {

    // parms 속성값 추가
    @GetMapping(value = "/users", params = "gender=man")
    public String params() {
        // logic
        String result = "params API가 호출 되었습니다.";
        return result;
    }

}

 

  • 실제 URL GET http://localhost:8080/users**?gender=man** 파라미터가 있어야 호출된다.
  • 실행결과

  • 파라미터가 없다면?

400 Bad Request 클라이언트 측 에러

 

  • 속성 작성 규칙
    1. params = "gender"
      • params의 key값은 커스텀이 가능하다
      • value는 없어도 된다.
    2. params = "!gender"
      • gender가 없어야 한다.
    3. params = "gender=man"
      • gender=man 이어야 한다.
    4. params = "gender!=man"
      • params의 value값이 man가 아니여야 한다.
    5. params = {"gender=man", "gender=woman"}
      • 배열로 속성 값을 여러 개 설정이 가능하다.

 

특정 Header 매핑

  • 특정 Header와 매핑하는 방법
@RestController
public class ParameterController {
	
	// headers 속성값 추가
  @PostMapping(value = "/users", headers = "Content-Type=application/json")
  public String headers() {
      // logic
      String result = "headers API가 호출 되었습니다.";
      return result;
  }
	
}
  • Postman → Body → raw → JSON

 

  • Postman → Headers → hidden

 

 

  • HTTP Header를 사용하기 때문에 Postman으로 테스트 해야 한다.

ex) key=Content-Type / value=application/json

  • 실행결과

  • 속성 작성 규칙은 위 params 속성 값의 규칙과 같다.

 

MediaType 매핑, consume(수용)

  • HTTP Header Content-Type(요청)과 매핑 된다.
@RestController
public class ParameterController {
	
	// consumes 속성값 추가
  @PostMapping(value = "/users", consumes = "application/json") // MediaType.APPLICATION_JSON_VALUE
  public String consumes() {
      // logic
      String result = "consumes API가 호출 되었습니다.";
      return result;
  }
	
}

 

  • consumes 속성 value값으로는 이미 Spring에서 제공되는 Enum인
  • MediaType.APPLICATION_JSON_VALUE 형태로 사용한다.

 

  • Postman → Body → raw → JSON

  • Postman → Body → raw → JSON

 

  • 파라미터가 없거나 다르다면?

  • HTTP 상태코드 405 Unsupported Media Type Exception 발생
  • 속성 작성 방법
    1. consumes=”application/json”
      • application/json 미디어 타입 허용
    2. consumes=”!application/json”
      • application/json 제외 미디어 타입 허용
    3. consumes=”application/*”
      • application/ 으로 시작하는 모든 미디어 타입 허용
    4. consumes=”*\\/*”
      • 모두 허용

 

MediaType 매핑 produces(제공)

  • 요청 헤더의 Accept 값에 따라서 produces 하는 값이 변한다.
@RestController
public class ParameterController {
	
	// produces 속성값 추가
  @GetMapping(value = "/users", produces = "text/plain")
  public String produces() {
      // logic
      String result = "text/plain 데이터 응답";
      return result;
  }
	
}
  • HTTP 요청 Accept Header에 Media Type이 있어야한다.

  • **/** : 전체 Media Type 허용
  • 실행결과

 

  • consumes 속성 사용법과 같다.
  • 위에 나온 모든 MediaType은 Spring이 제공하는 Enum을 사용하면 된다.

ex) produces = “application.json" → produces = MediaType.APPLICATION_JSON_VALUE

 

 

Spring이 지원하는 Parameter

📌 어노테이션 기반 Spring의 Controller는 다양한 파라미터를 쉽게 사용할 수 있도록 지원한다.

 

  • HTTP 헤더 조회
    • Spring에서 요청 Header에 쉽게 접근할 수 있다.
      • HttpServletRequest와 같이 파라미터로 다룰 수 있다.
    • Controller 예시
// 로깅
@Slf4j
@RestController
public class RequestHeaderController {

    @GetMapping("/request/headers")
    public String headers(
            HttpServletRequest request, // Servlet에서 사용한것과 같음
            HttpServletResponse response, // Servlet에서 사용한것과 같음
            @RequestHeader MultiValueMap<String, String> headerMap,
            @RequestHeader("host") String host,
            @CookieValue(value = "cookie", required = false) String cookie,
            HttpMethod httpMethod,
            Locale locale
    ) {
		    // Servlet
        log.info("request={}", request);
        log.info("response={}", response);
        
        // @RequestHeader
        log.info("headerMap={}", headerMap);
        log.info("host={}", host);
        
        // @CookieValue
        log.info("cookie={}", cookie);
        
        // HttpMethod
        log.info("httpMethod={}", httpMethod);
        
        // Locale
        log.info("Locale={}", locale);

        return "success";
    }
}

 

  • Postman API 호출

 

 

 

Log 출력 결과

  1. request
    • HttpServletRequest 객체 주소 값
  2. response
    • HttpServletRequest 객체 주소 값
  3. headerMap :
hashMap={
	user-agent=[PostmanRuntime/7.35.0], 
	accept=[*/*], 
	postman-token=[5f324c1c-7902-4750-9e01-2c4d093e8ad6],
	host=[localhost:8080],
	accept-encoding=[gzip, deflate, br],
	connection=[keep-alive]
}

 

  1. host
    • host 정보
  2. cookie
    • Header의 Cookie 값
  3. httpMethod
    • 호출에 사용한 HttpMethod
  4. Locale
    • 위치 정보를 나타내는 헤더
    • 우선순위가 존재한다.

 

 

MultiValueMap

Map과 유사하게 Key, Value 형식으로 구현되어 있지만 하나의 Key가 여러 Value를 가질 수 있다 HTTP Header, Reqeust Parameter와 같이 하나의 Key에 여러 값을 받을 때 사용한다.

ex )  key1=value1&key1=value2

MultiValueMap<String, String> linkedMultiValuemap = new LinkedMultiValueMap();

// key1에 value1 저장
linkedMultiValuemap.add("key1", "value1");
// key1에 value2 저장
linkedMultiValuemap.add("key1", "value2");

// key1에 저장된 모든 value get
List<String> values = linkedMultiValuemap.get("key1");

 

'Back-End (Web) > Spring' 카테고리의 다른 글

[Spring] HTTP Message Body & TEXT  (0) 2024.12.18
[Spring] @RequestParam & @ModelAttribute  (0) 2024.12.17
[Spring] Spring Annotation  (1) 2024.12.15
[Spring] Spring MVC 패턴  (1) 2024.12.14
[Spring] Spring Boot  (0) 2024.12.10

Lombok

📌 Java의 보일러플레이트 코드(반복적이고 지루한 코드)를 줄여주는 라이브러리입니다.

  • 간단한 어노테이션으로 getter, setter, equals, hashCode, toString, logger 생성 등을 자동으로 생성하여 코드의 간결성을 높이고 가독성을 향상시킵니다.
import lombok.ToString;

@ToString
public class Example {
    private String name;
    private int age;
}


=

@Override
public String toString() {
    return "Example(name=" + name + ", age=" + age + ")";
}

 

Lombok의 장점

  1. 코드 간소화:
    • getter, setter, equals, toString 등 반복적인 코드를 줄여줍니다.
  2. 가독성 향상:
    • 핵심 로직에만 집중할 수 있어 코드가 더 읽기 쉬워집니다.
  3. 유지보수성 개선:
    • 코드 중복이 줄어들어 수정할 곳이 줄어듭니다.

 

Lombok 사용 시 주의사항

  1. 의존성 문제:
    • Lombok은 컴파일 시점에 코드를 생성하므로, IDE와 빌드 환경이 Lombok을 지원해야 합니다.
  2. 타인의 코드 리뷰:
    • Lombok을 사용하면 실제 코드가 IDE에서 보이지 않아, 팀원이 Lombok을 모른다면 코드 리뷰가 어려울 수 있습니다.
  3. Java 표준 아님:
    • Lombok은 Java 표준 라이브러리가 아니기 때문에, 프로젝트 환경에 따라 사용을 제한할 수 있습니다.

 

 

@Slf4j

📌 Lombok에서 제공하는 어노테이션으로, SLF4J(Simple Logging Facade for Java) 기반의 로깅(Logger) 객체를 자동으로 생성합니다.

  • 로깅은 애플리케이션 개발에서 로그 메시지를 기록하여 디버깅, 모니터링, 운영 상태를 파악하는 데 매우 중요한 도구입니다.
  • Slf4j는 인터페이스이고 그 구현체로 Logback같은 라이브러리를 선택한다. 실제 개발에서는 Spring Boot가 기본으로 제공하는 Logback을 대부분 사용한다. (Slf4j는 껍데기 실제 로깅은 구현체가 한다.)
  • 헷갈릴까봐 기술하면, Logback이 Slf4j의 구현체 중 하나로, Spring Boot에서 기본 로깅 프레임워크로 사용된다.

 

Logging

  • Thread 정보, 클래스 이름과 같은 부가 정보를 함께 확인할 수 있다.
  • 실제 운영 환경에서는 System.out.println();을 사용하여 Console에 정보를 출력하지 않고, 별도의 로깅 라이브러리를 사용하여 로그를 출력한다.
  • Log Level 설정을 통하여 Error 메세지만 출력하도록 하도록 하기도 하고 로그 메세지를 일자별로 모아서 저장하여 외부 저장소에 보관하기도 한다.
    • Log Level
      • TRACE > DEBUG > INFO > WARN > ERROR
  • 이게 왜 있나 싶을 수 있는데, 서버는 출력을 터미널에 띄울 수 없다보니 따로 서버내에서 기록하게 한다. 실제로 개발자 환경에 console에서 그 결과를 확인할 수 있다. 더 나아가 오류를 미리 기록하여 모니터링을 돕는다. 서버에 적재하지 않는 프로그램이면 이렇게 하지 않겠지만 서버는 24시간 돌아가니 사람이 24시간 감시할거 아니면 저런 장치가 필요하다.

 

사용시 주의점

package com.example.springbasicannotation.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

@Slf4j
@RestController
public class Slf4jController {

    @RequestMapping("/logging")
    public String logging() {

        String sparta = "Sparta";
        // TRACE -> DEBUG -> INFO -> WARN -> ERROR
        log.trace("문자 trace={}", sparta);
        log.debug("문자 debug={}", sparta);

        // default
        log.info("문자 info={}", sparta);// 문자 연산을 진행하지 않는다.
        log.warn("문자 warn={}", sparta);
        log.error("문자 error={}", sparta);

        log.info("문자 info " + sparta); // 문자 연산을 먼저 해버린다.
        return "success";
    }

}

# com.example.springbasicannotation 하위 경로들의 로그 레벨을 설정한다.
logging.level.com.example.springbasicannotation=TRACE

 

  • Postman 호출

Default Level(INFO) API 호출

 

출력결과

 

level=TRACE

 

 

 

@Controller VS @RestController

📌 Annotation 기반의 Spring에서 Controller(Handler)를 만들 때 사용하는 어노테이션

  • 둘이 기능은 같지만, 데이터 반환 방식응답 처리 방식이 다르다.

 

주요 차이점

특징 @Controller @RestController
주요 용도 페이지(View) 기반 웹 애플리케이션 RESTful API 개발
기본 반환 타입 View 이름(HTML, JSP 등) JSON, XML 등 데이터
View Resolver View Resolver를 통해 뷰 파일로 매핑 사용하지 않음
응답 내용 HTML 페이지를 렌더링하여 반환 데이터 자체를 HTTP 응답으로 반환
애너테이션 결합 단일 어노테이션 @Controller + @ResponseBody

 

언제 사용해야 할까?

  • @Controller:
    • HTML, JSP 같은 페이지 기반 웹 애플리케이션 개발.
    • 템플릿 엔진(Thymeleaf, JSP)과 함께 사용.
  • @RestController:
    • JSON, XML 데이터를 반환하는 RESTful API 개발.
    • 클라이언트(브라우저, 앱 등)가 데이터를 요청하고 처리하는 백엔드 서비스 개발.

 

@Controller

📌MVC(Model-View-Controller) 아키텍처에서 사용되는 컨트롤러.

  • 클라이언트 요청을 처리하고, **View(HTML, JSP 등)**를 반환하는 데 사용.
    • View가 있는 경우에 사용한다.
  • 주로 페이지 기반의 웹 애플리케이션 개발에 적합.
  • 즉, Template Engine인 Thymeleaf, JSP 등을 사용하는 경우

특징

  1. View Resolver와 함께 사용:
    • @Controller는 뷰 이름을 반환하며, Spring의 View Resolver가 이 이름을 실제 뷰 파일 경로로 변환.
    • 예: return "home"; → /WEB-INF/views/home.jsp
  2. 주요 반환 타입:
    • 문자열(String): 뷰 이름.
    • ModelAndView: 데이터와 뷰 정보를 함께 반환.

 

@Controller
public class WebController {

    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("message", "Hello, World!");
        return "hello"; // 뷰 이름 반환 (예: hello.jsp)
    }
}

응답 흐름

  1. 클라이언트가 /hello로 요청.
  2. hello() 메서드가 실행되고, 뷰 이름 "hello" 반환.
  3. View Resolver가 "hello"를 실제 뷰 파일 경로(/WEB-INF/views/hello.jsp)로 매핑.
  4. 해당 JSP 파일이 렌더링되어 클라이언트에게 HTML 응답 반환.

 

  • @Target(ElementType.Type)
    • Class, Interface, Annotation, Enum, Record Declaration(Java16) 에 적용할 수 있다.
  • @Retention(RetentionPolicy.RUNTIME)
    • 클래스 파일(.class)에 저장되고, JVM에 의해 런타임 시점에 읽을 수 있다.
  • @Document
    • Javadoc 등의 문서화 도구에 의해 문서화되어야 함을 나타낸다.
  • @Component
    • Spring Bean에 등록한다.
    • 싱글톤으로 관리된다.
더보기


Thymeleaf 예시

  • SpringBoot build.gradle 의존성 추

 

  • main/resources/templates 가 기본 경로로 설정된다.
  • resources/templates/sparta.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Hello</title>
</head>
<body>
<h2>Thymeleaf Template Sample</h2>
</body>
</html>
  • 동작 순서
  • return 값이 String이면 ThymeleafViewResolver 에 의해 View Name으로 인식된다.

 

  • 호출결과
    • http://localhost:8080/view

 

 

 

 

@RestController

📌 RESTful 웹 서비스를 개발하기 위해 사용되는 컨트롤러.

  • 응답할 Data가 있는 경우에 사용한다.
  • 클라이언트 요청을 처리하고, JSON, XML 등 데이터 포맷으로 응답.
  • 페이지 대신 **데이터(API)**를 반환하는 데 사용.
  • 현재는 대부분 @RestController를 사용하여 API가 만들어진다. (Restful API)
  • return 값으로 View를 찾는것이 아니라 HTTP Message Body에 Data를 입력한다.

 

특징

  1. 데이터 반환에 특화:
    • 메서드의 반환값은 **자동으로 HTTP 응답 본문(body)**에 직렬화되어 클라이언트에 전달.
    • JSON이 기본 반환 포맷(SPRING BOOT에서는 기본적으로 Jackson 라이브러리를 사용).
  2. 주요 반환 타입:
    • 객체 또는 컬렉션: JSON으로 변환.
    • 문자열(String): 텍스트 응답.
  3. @Controller + @ResponseBody의 결합:
    • @RestController는 내부적으로 @Controller와 @ResponseBody를 합친 것과 같음.

 

코드 예제

@RestController
public class ApiController {

    @GetMapping("/api/hello")
    public Map<String, String> hello() {
        Map<String, String> response = new HashMap<>();
        response.put("message", "Hello, World!");
        return response; // JSON으로 변환되어 반환
    }
}

응답 흐름

  1. 클라이언트가 /api/hello로 요청.
  2. hello() 메서드가 실행되고, Map<String, String> 객체 반환.
  3. Spring Boot가 반환된 객체를 JSON으로 변환.
  4. JSON 응답을 클라이언트로 전달.

 

  • @Controller에 @ResponseBody가 결합된 어노테이션
  • @RestController는 @Controller와 달리 각 메서드마다 @ResponseBody를 추가하지 않아도 된다

 

 

 

더보기

 

 

호출 결과

 

 

결론

  • @Controller는 View를 반환하는 데 초점을 맞추고 있고, @RestController는 데이터(API 응답)를 반환하는 데 사용됩니다.
  • 개발 목적에 따라 적절한 어노테이션을 선택하여 사용하면 됩니다.
    예를 들어:
    • HTML 페이지 제공: @Controller + View Resolver.
    • REST API 제공: @RestController.

 

 

Spring  Annotation 자세히 보기

 

@Component

  • Spring Bean에 등록하는 역할을 수행한다.
    • Spring Bean은 애플리케이션의 구성 요소를 정의하는 객체이다.
    • WAS가 Servlet 코드를 읽어 컨테이너에 등록했던 것을 떠올려 봅시다.
    • Servlet Container(네트워크 기초 자료)
더보기
  • Servlet Container(네트워크 기초 자료)
  • 📚 Servlet을 지원하는 WAS 내부에는 서블릿 컨테이너가 있다. 서블릿 컨테이너는 서블릿을 초기화, 생성, 관리, 호출, 종료하는 역할을 수행한다.  
    • Servlet의 생명주기
      • Servlet은 서블릿 컨테이너가 생성 및 관리한다.
      • 즉, WAS(서블릿 컨테이너 포함)가 종료될 때 Servlet도 함께 종료된다.
    • Servlet 객체 생성시점
      • 개발자가 직접 인스턴스화 하여 사용하는것이 아닌, 코드만 작성하면 서블릿 컨테이너가 생성한다.
       

 

서블릿 예시 코드

@WebServlet(name="ExampleServlet", urlPatterns = "/example")
public class ExampleServlet extends HttpServlet { // HttpServlet을 상속받아 구현한다.
	
	@Override
	protected void service(
		HttpServletRequest request,  // HTTP 요청 정보를 쉽게 사용할 수 있게 만드는 Servlet
		HttpServletResponse response // HTTP 응답 정보를 쉽게 제공할 수 있게 만드는 Servlet
	) {
		// application logic
	}

}
@WebServlet(name="Example2Servlet", urlPatterns = "/example2")
// 위와 같은 코드

@WebServlet(name="Example3Servlet", urlPatterns = "/example3")
// 위와 같은 코드

@WebServlet(name="Example4Servlet", urlPatterns = "/example4")
// 위와 같은 코드

 

  • Servlet Container가 하는 일
    1. 서블릿을 초기화, 생성, 관리, 호출, 종료하는 역할을 수행
      1. Servlet 객체를 싱글톤으로 관리한다.
    2. 동시 요청에 대한 처리를 위해 Multi Thread를 지원한다.
💬 Q. 싱글톤이 무엇인가요?

A. 싱글톤은 객체를 하나만 생성하여 생성된 인스턴스를 공유하여 사용하는것을 의미합니다. 특정 클래스의 인스턴스가 여러개 생성되지 않도록 하여 자원의 낭비를 방지하고, 인스턴스를 공유함으로써 상태를 일관되게 유지하기 위함입니다. 하지만, 공유 변수 사용을 주의해야 합니다.

  • @Indexed
    • 클래스가 컴포넌트 스캔의 대상으로 Spring Bean에 더 빠르게 등록되도록 도와준다.
  • Spring Bean, 컴포넌트 스캔에 대해서는 숙련주차 강의에서 자세히 다룰 예정

 

@Target

  • @Target 이 선언된 하위 어노테이션이 어떤 범위에 적용되는지 설정한다.
  • ElementType Enum 속성

  • 각각의 Enum마다 적용되는 범위가 상단에 주석으로 설명되어 있다.

 

@Retention

  • @Retention 하위의 어노테이션이 얼마나 오래 유지되는지를 결정한다.
  • RetentionPolicy Enum 속성

  1. SOURCE
    • 소스 코드(.java)에서만 유지된다.
    • 컴파일러에 의해 클래스 파일로 저장되지 않는다.
  2. CLASS
    • 컴파일된 클래스 파일(.class)에 저장되지만, JVM이 실행 시 읽지 않는다. (주석과 같음)
    • Default 값이다.
  3. RUNTIME
    • 클래스 파일(.class)에 저장되고, JVM에 의해 런타임 시점에 읽을 수 있다.
    • 즉, 실제 런타임 시점의 코드에 반영되어 영향을 준다.

 

@Documented

  • Javadoc 등의 문서화 도구에 의해 문서화되어야 함을 나타낸다.

'Back-End (Web) > Spring' 카테고리의 다른 글

[Spring] @RequestParam & @ModelAttribute  (0) 2024.12.17
[Spring] Request Mapping  (1) 2024.12.16
[Spring] Spring MVC 패턴  (1) 2024.12.14
[Spring] Spring Boot  (0) 2024.12.10
[Spring] Spring Framework  (2) 2024.12.09

+ Recent posts