728x90
반응형

요약 한 줄
스프링 MVC는 DispatcherServlet이 중앙에서 요청을 받아 HandlerMapping → HandlerAdapter → (Controller) → ViewResolver → View 순으로 흐름을 조율하고, 중간에 필터/인터셉터/예외처리/리졸버들이 참여하는 파이프라인입니다.

목차

  1. 전체 흐름(빅픽처)
  2. 주요 컴포넌트 한 줄 정의
  3. 요청~응답 단계별 상세
  4. 인터셉터 vs 필터 vs AOP
  5. 예외 처리 흐름(@ExceptionHandler, @ControllerAdvice, HandlerExceptionResolver)
  6. 확장 포인트(커스터마이징 체크리스트)
  7. 실전 코드 스니펫 모음
  8. 면접/시험 포인트 & 체크리스트

1) 전체 흐름(빅픽처)

HTTP 요청
  ↓ (Servlet Filter 체인)
DispatcherServlet
  ↓ HandlerMapping(매핑 찾기)
HandlerExecutionChain(핸들러 + 인터셉터들)
  ↓ HandlerAdapter(호출 어댑터)
@Controller/@RestController 메서드 실행
  ↓ (Model/ModelAndView or @ResponseBody)
ViewResolver(뷰 선택) ─ 또는 HttpMessageConverter(바디 변환)
  ↓ View 렌더링(템플릿/정적리소스) or JSON 직렬화
HTTP 응답

  

2) 주요 컴포넌트 한 줄 정의

  • DispatcherServlet: 프론트 컨트롤러. 요청/응답의 트래픽 경찰.
  • HandlerMapping: 어떤 컨트롤러 메서드가 이 URL/HTTP 메서드를 처리하는지 탐색.
  • HandlerAdapter: 찾아낸 핸들러를 실행 가능한 형태로 호출.
  • HandlerMethodArgumentResolver: 컨트롤러 파라미터(@RequestParam, @PathVariable, @RequestBody, 커스텀 타입 등) 주입.
  • HttpMessageConverter: HTTP 바디 ↔ 객체(JSON, XML 등) 직렬화/역직렬화.
  • ViewResolver: 뷰 이름을 템플릿(View)으로 해석(Thymeleaf, JSP 등).
  • HandlerExceptionResolver: 예외를 적절한 응답/뷰로 변환.
  • HandlerInterceptor: 컨트롤러 전/후/완료 시점 가로채기(로깅, 인증 등).

3) 요청~응답 단계별 상세

  1. 필터 체인
    • 서블릿 컨테이너 레벨. 인코딩 처리, 보안(스프링 시큐리티), 공통 로깅 등.
  2. DispatcherServlet 진입
    • doDispatch()에서 실질 처리 시작.
    • HandlerMapping으로 HandlerExecutionChain(핸들러 + 인터셉터 목록) 획득.
  3. 인터셉터(preHandle)
    • 인증/인가, 트랜잭션 컨텍스트, 트레이싱 ID 부여 등.
  4. HandlerAdapter → 컨트롤러 실행
    • 메서드 파라미터는 ArgumentResolver들이 채워줌.
    • 바디(JSON 등)는 HttpMessageConverter로 역직렬화.
  5. 핸들러 반환값 처리
    • 템플릿 렌더: ModelAndView → ViewResolver로 뷰 탐색 후 렌더.
    • REST 응답: @ResponseBody/ResponseEntity → HttpMessageConverter로 JSON 직렬화.
  6. 인터셉터(postHandle, afterCompletion)
    • 모델 가공, 뷰 렌더 이후 리소스 정리, 예외 로깅 등.
  7. 예외 발생 시
    • @ExceptionHandler(로컬) → @ControllerAdvice(글로벌) → HandlerExceptionResolver 순으로 처리.

4) 인터셉터 vs 필터 vs AOP

구분                                    적용 레벨                          주요 시점                                                       사용 예

Filter 서블릿 컨테이너 DispatcherServlet 전/후 인코딩, 보안, CORS, 공통 로깅
Interceptor 스프링 MVC 컨트롤러 전/후/완료 로그인 체크, 요청 컨텍스트, 성능 측정
AOP 스프링 빈 메서드 메서드 호출 단위 트랜잭션, 캐시, 로깅 단면화
 

인증/인가는 필터/시큐리티, 컨트롤러 전후 로직은 인터셉터, 서비스/리포지토리横단 관심사는 AOP가 깔끔합니다.

5) 예외 처리 흐름

우선순위: 로컬 @ExceptionHandler → 글로벌 @ControllerAdvice → HandlerExceptionResolver

  • REST라면 적절한 상태코드 + JSON 바디로 통일.
  • HTML 뷰 프로젝트면 공통 오류 페이지로 렌더.

6) 확장 포인트 체크리스트

  • WebMvcConfigurer로 인터셉터/리졸버/메시지컨버터 추가
  • HandlerMethodArgumentResolver로 커스텀 파라미터 주입(예: @CurrentUser)
  • MessageConverter로 Protobuf/CSV 등 포맷 지원
  • LocaleResolver/TimeZone로 지역화
  • ContentNegotiation으로 ?format=json/Accept 헤더 대응
  • ViewResolver 우선순위 조정(Thymeleaf, JSP, JSON 등)
  • ExceptionResolver 또는 @ControllerAdvice로 에러 표준화
  • 정적 리소스 캐싱과 ResourceHandler 튜닝

7) 실전 코드 스니펫

7-1. 간단 컨트롤러

@RestController
@RequestMapping("/api")
public class HelloController {
  @GetMapping("/hello")
  public Map<String, Object> hello(@RequestParam String name) {
    return Map.of("message", "Hello " + name);
  }
}

7-2. 인터셉터 등록

@Component
public class LogInterceptor implements HandlerInterceptor {
  @Override
  public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) {
    req.setAttribute("startAt", System.currentTimeMillis());
    return true;
  }
  @Override
  public void afterCompletion(HttpServletRequest req, HttpServletResponse res, Object handler, Exception ex) {
    long took = System.currentTimeMillis() - (long) req.getAttribute("startAt");
    System.out.println("[TIME] " + req.getRequestURI() + " -> " + took + "ms");
  }
}

@Configuration
public class WebConfig implements WebMvcConfigurer {
  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LogInterceptor()).addPathPatterns("/api/**");
  }
}

7-3. 커스텀 ArgumentResolver (예: @CurrentUser)

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUser {}

@Component
public class CurrentUserResolver implements HandlerMethodArgumentResolver {
  @Override public boolean supportsParameter(MethodParameter parameter) {
    return parameter.hasParameterAnnotation(CurrentUser.class);
  }
  @Override public Object resolveArgument(MethodParameter p, ModelAndViewContainer mav,
      NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
    return webRequest.getNativeRequest(HttpServletRequest.class).getAttribute("user");
  }
}

@Configuration
class MvcConfig implements WebMvcConfigurer {
  @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
    resolvers.add(new CurrentUserResolver());
  }
}

7-4. 글로벌 예외 처리

@ControllerAdvice
public class GlobalExceptionHandler {
  @ExceptionHandler(IllegalArgumentException.class)
  public ResponseEntity<Map<String, Object>> handleBadRequest(IllegalArgumentException e) {
    return ResponseEntity.badRequest().body(Map.of(
      "error", "BAD_REQUEST",
      "message", e.getMessage()
    ));
  }
}

7-5. 메시지 컨버터 추가(예: CSV)

@Configuration
public class HttpMsgConfig implements WebMvcConfigurer {
  @Override
  public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.add(0, new CsvHttpMessageConverter()); // 우선 등록
  }
}

8) 면접/시험 포인트 & 체크리스트

  • DispatcherServlet이 하는 일 3줄로 설명할 수 있는가?
  • HandlerMapping/Adapter/ExceptionResolver/MessageConverter 역할 구분 가능한가?
  • 인터셉터 vs 필터 vs AOP 적용 위치를 사례로 설명할 수 있는가?
  • REST에서 Content NegotiationHttpMessageConverter의 연결을 아는가?
  • 예외를 일관된 응답 스키마로 표준화하는가?
728x90
728x90
반응형

1. 스프링의 시작은 책 한 권에서

스프링(Spring)은 단순한 프레임워크 그 이상입니다. 그 시작은 2002년, Rod Johnson이 집필한 『Expert One-on-One J2EE Design and Development』라는 책에서 등장한 소스 코드에서 비롯됩니다. 당시 **EJB(Enterprise JavaBean)**는 무거운 구조와 복잡성으로 인해 '겨울'과도 같았죠. Rod는 이를 비판하며 '단순함'을 추구했고, 그 철학이 '봄(Spring)'이라는 이름으로 이어진 것입니다.

❝ EJB의 겨울을 지나, 스프링의 봄이 온다 ❞

스프링은 2003년 6월에 처음 세상에 공개되었고, 오픈소스 프로젝트로서 Apache License 2.0을 따릅니다. 2022년 11월 기준으로는 6.0.0 버전까지 출시되어 있으며, 주요 기능은 이제 Spring Boot를 중심으로 구현되고 있습니다.

2. 왜 스프링이 중요한가?

  • POJO 기반: 복잡한 컴포넌트 없이 순수한 Java 객체로 개발이 가능.
  • DI/IoC 컨테이너: 객체의 생성과 의존성 관리를 프레임워크가 자동으로 처리.
  • 모듈화: AOP(Aspect-Oriented Programming)를 통해 핵심 로직과 부가 기능을 깔끔히 분리.
  • JVM 기반 호환성: Java는 물론 Kotlin 등 다양한 언어와도 호환.
  • 강력한 생태계: Spring MVC, Spring Data, Spring Security, Spring Cloud 등 수많은 확장 가능성.
  • 전자정부 프레임워크 기반: 국내 공공기관 시스템의 표준으로도 사용.

3. 스프링을 제대로 활용하려면?

  • Spring Boot로 시작하면 빠르고 간단한 설정이 가능
  • IntelliJ IDEA는 Spring에 최적화된 대표 IDE (JetBrains 공식 지원)
  • 테스트/배포 자동화까지 생각한다면 Swagger, Docker, GitHub Actions와 연동 추천

✍️ 요약문

스프링은 복잡했던 Java EE의 시대에 ‘단순함’이라는 가치를 제시하며 등장한 웹 프레임워크입니다. Rod Johnson의 철학에서 출발한 이 프로젝트는 이제 전 세계 수많은 기업과 공공기관이 사용하는 강력한 인프라가 되었습니다. 만약 Java 기반 백엔드를 시작하고 싶다면, Spring은 그 자체로도 가장 강력한 출발점입니다.

728x90
728x90
반응형

Java에서 반복적으로 작성해야 하는 메서드들, 예를 들어 getter, setter, toString, 생성자 같은 것들로 인해 코드가 길어지는 것이 고민이신가요?

Lombok은 이러한 반복 코드를 어노테이션 한 줄로 자동 생성해주는 강력한 라이브러리입니다. 이 글에서는 Lombok의 주요 어노테이션을 한 번에 정리해드립니다.


💡 Lombok 설정 방법

// build.gradle
configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

dependencies {
    annotationProcessor 'org.projectlombok:lombok'
    compileOnly 'org.projectlombok:lombok'

    testAnnotationProcessor 'org.projectlombok:lombok'
    testCompileOnly 'org.projectlombok:lombok'
}

🧩 주요 Lombok 어노테이션 정리

🔹 @Getter, @Setter

  • 클래스나 필드에 사용 가능
  • 접근자(getter), 설정자(setter) 자동 생성
@Getter
@Setter
private String name;

🔹 @NoArgsConstructor, @AllArgsConstructor, @RequiredArgsConstructor

  • 생성자 자동 생성
    • @NoArgsConstructor → 기본 생성자
    • @AllArgsConstructor → 모든 필드 포함 생성자
    • @RequiredArgsConstructor → final 필드 + @NonNull 필드만 포함
@RequiredArgsConstructor
private final String name;

🔹 @ToString

  • toString() 메서드를 자동 생성
  • 특정 필드를 제외할 수 있음
@ToString(exclude = "password")
private String password;

🔹 @EqualsAndHashCode

  • equals() 및 hashCode() 자동 생성
  • callSuper = true 옵션으로 부모 클래스 필드 포함 여부 설정 가능

🔹 @Data

  • @Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgsConstructor 를 한 번에 적용

✅ 정리

어노테이션기능 요약
@Getter / @Setter 필드의 getter/setter 생성
@NoArgsConstructor 기본 생성자 생성
@AllArgsConstructor 전체 필드 생성자
@RequiredArgsConstructor 필수(final, @NonNull) 생성자
@ToString toString() 자동 생성
@EqualsAndHashCode equals/hashCode 자동 생성
@Data 위 5개를 모두 한 번에 생성
728x90
728x90
반응형

스프링에서 수동으로 빈을 등록 할때 @Configuration 클래스 안에 @Bean을 사용한다. 왜 그런지 살펴보자.

● @Configuration 안에 @Bean을 사용하는 이유, proxyBeanMethods

 - @Bean 어노테이션을 이용한 수동 빈 등록

스프링에선 일반적으로 컴포넌트 스캔을 이용해 자동으로 빈을 등록하는 방법을 이용한다. 하지만 @Bean 어노테이션을 사용해 수동으로 빈을 등록해야 할 때도 있다.

  • 개발자가 직접 제어가 불가능한 라이브러리를 활용할 때
  • 애플리케이션 전 범위적으로 사용되는 클래스를 등록할 때
  • 다형성을 활용하여 여러 구현체를 등록해야 할 때

 @Bean을 이용한 수동 빈 메소드는 스프링 빈 안에만 구현되어 있다면 모두 동작한다. 하지만 스프링은 @Bean은 반드시 @Configuration 어노테이션 활용하도록 하는데, 그 이유는 @Configuration에 특별한 부가 기능이 적용되기 때문이다.

  @Configuration 에 적용되는 프록시 패턴

- @Configuration 어노테이션 안에는 @Component 어노테이션이 붙어 있어 @Configuration이 붙어있는 클래스 역시 스프링 빈으로 등록이 된다. 그럼에도 스프링이 @Configuration을 만든 이유는 CGLib으로 프록시 패턴을 적용해 수동으로 등록하는 스프링 빈이 반드시 싱글톤으로 생성됨을 보장하기 위해서다.

public class Resource {}

위 클래스를 @Component를 이용해 자동으로 빈 등록을 한다면 스프링이 해당 크래스의 객체의 생성을 제어하게 되고(제어의 역전, IoC) 하나의 객체만 생성되도록 컨트롤 할 수 있다. 하지만 위의 클래스를 @Bean을 이용해 직접 빈으로 등록해준다고 하면, 우리는 다음과 같이 해당 빈 등록 메소드를 여러 번 호출할 수 있게 된다.

@Configuration
public class MyBeanConfiguration { 

    @Bean 
    public Resource resource() {
        return new Resource(); 
    } 
    
    @Bean 
    public MyFirstBean myFirstBean() { 
        return new MyFirstBean(resource()); 
    } 
    
    @Bean 
    public MySecondBean mySecondBean() { 
        return new MySecondBean(resource()); 
    } 
}

 실수로 위와 같이 빈을 생성하는 메소드를 여러 번 호출 했다면 불필요하게 여러 개의 빈이 생성이 된다. 스프링은 이러한 문제를 방지하고자 @Configuration이 있는 클래스를 객체로 생성할 때 CGLib을 사용해 프록시 패턴을 적용한다. 그래서 @Bean이 있는 메소드를 여러 번 호출하여도 항상 동일한 객체를 반환하여 싱글톤을 보장한다.

@Configuration
public class MyBeanConfigurationProxy extends MyBeanConfiguration {

    private Object source;

    @Override
    public Resource resource() {
        if (resource == null) {
            source = super.resource();
        }
        return source;
    }

    @Override
    public MyFirstBean myFirstBean() {
        return super.myFirstBean();
    }

    @Override
    public MySecondBean mySecondBean() {
        return super.mySecondBean();
    }
}

 CGLib은 상속을 통해 프록시를 구현하므로 위와 같이 프록시가 구현 됬다고 이해 할 수 있다. 물론 이렇게 생성이 되는건 아니라 내부 클래스를 사용하는 등의 차이가 있으므로 이해를 돕기 위한 코드로 보면 된다.

  싱글톤 여부를 제어하기 위한 proxyBeanMethods

 대부분 @Bean에 의한 수동 빈 등록을 할 때 싱글톤으로 생성되기를 원한다. 하지만 @Bean 메소드를 호출할 때 의도적으로 매번 다른 객체가 생성되기를 바랄 수 있다. 그럴 경우엔 @Configuration 어노테이션이 갖고 있는 proxyBeanMethods를 false로 주면 된다.

@Configuration(proxyBeanMethods = false)
public class MyBeanConfigurationProxy extends MyBeanConfiguration {

    private Object source;

    @Override
    public Resource resource() {
        if (resource == null) {
            source = super.resource();
        }
        return source;
    }

    @Override
    public MyFirstBean myFirstBean() {
        return super.myFirstBean();
    }

    @Override
    public MySecondBean mySecondBean() {
        return super.mySecondBean();
    }
}

https://mangkyu.tistory.com/234

 

[Spring] @Configuration 안에 @Bean을 사용해야 하는 이유, proxyBeanMethods - (2/2)

Spring에서 수동으로 빈을 등록할 때에는 @Configuration 클래스 안에서 @Bean을 사용해야 합니다. 이번에는 왜 @Configuraiton 클래스 안에서 @Bean을 사용해야 하는지 살펴보도록 하겠습니다. 1. @Configuration.

mangkyu.tistory.com

 

728x90
728x90
반응형

 기존의 스프링 MVC에서는 xml을 활용하여 Bean을 등록하고 있었다.

 하지만 프로젝트의 규모가 커짐에 따라 사용하는 요소들은 xml에 등록하는 것이 상당히 번거로워 져서 어노테이션 기반의 Bean 등록 방법이 탄생하게 되었다. Bean을 등록하기 위한 @Bean, @Component, @Configuration 어노테이션에 대하여 알아보자.

● Spring Bean이란?

- 스프링에서 스프링의 DI Container에 의해 관리되는 POJO(Plain Old Java Object)를 Bean이라고 부른다. 이러한 Bean들이 스프링을 구성하는 핵심 요소이다.

  • POJO(Plain Old Java Object)로써 스프링 애플리케이션을 구성하는 핵심 객체이다.
  • 스프링 IoC 컨테이너(또는 DI 컨테이너)에 의해 생성 및 관리된다.
  • class, id, scope, constructor-arg 등 주요 속성으로 지닌다.

스프링 빈 구성요소

- class : Bean으로 등록할 Java 클래스

- id : Bean의 고유 식별자

- scope : Bean을 생성하기 위한 방법(singleton, prototype 등)

- constructor-arg : Bean 생성 시 생성자에 전달할 파라미터

- property : Bean 생성 시 setter에 전달할 인수

 

Spring Bean 등록 방법(@Bean, @Configuration, @Component)

다음과 같은 클래스를 스프링 컨테이너에 등록 해보자.

public class Resource {}

 이 클래스를 빈으로 등록하기 위해선 명시적으로 설정 클래스에서 @Bean 어노테이션을 사용해 수동으로 스프링 컨테이너에 빈을 등록하는 방법이있다. @Configuration 어노테이션을 클래스에 붙여주면 된다. @Bean을 사용해 수동으로 빈을 등록할 때에는 메소드 이름으로 빈 이름이 결정된다.

@Configuration
public class ResourceConfig {

	@Bean
    public Resource resource() {
    	return new Resource();
    }
}

 스프링 컨테이너는 @Configuration이 붙어있는 클래스를 자동으로 등록해두고, 해당 클래스를 파싱해서 @Bean이 있는 메소드를 찾아 빈을 생성한다. 다른 임의의 클래스를 만들어 @Bean을 붙인다고 되는것이 아니고, 반드시 @Bean을 사용하는 클래스에는 @Configuration 어노테이션을 활용하여 해당 클래스에서 Bean을 등록하고자 함을 명시해 줘야 한다.

수동으로 빈을 직접 등록해야 하는 상황이라면 다음과 같은 기준으로 한다.

  • 개발자가 직접 제어가 불가능한 라이브러리를 활용할 때
  • 애플리케이션 전 범위적으로 사용되는 클래스를 등록할 때
  • 다형성을 활용하여 여러 구현체를 등록해야 할 때

 만약 우리가 객체를 JSON메시지로 변경하기 위해 Gson과 같은 외부 라이브러리를 사용한다고 하자. 그러면 해당 클래스를 싱글톤 빈으로 등록해 줘야 1개의 객체만 생성하여 여러 클래스가 공유함으로 메모리 상의 이점을 얻을 수 있다. 그런데 해당 클래스는 우리가 만든게 아니라 가져다 사용하는 클래스 일 뿐 불가피하게 @Bean으로 등록 해줘야만 한다.

@Bean을 사용하는 이점은 한 눈에 파악하여 유지보수하기가 쉽기 때문이다. 예를 들자면 구현체를 빈으로 등록 할때 어떠한 구현체들이 빈으로 등록됬는지 파악하기 위해서 @Configuration 클래스만 찾아보면 되기 때문이다.

 

Component

수동으로 직접 빈을 등록 하는 작업은 빈으로 등록하는 클래스가 많아 질수록 상당한 시간을 차지하고 생산력 저하로 이루어질 것이다. 이때 스프링에서 특정 어노테이션이 있는 클래스를 찾아 빈으로 등록해 주는 컴포넌트 스캔 기능이 있다.

 스프링은 컴포넌트 스캔(Component Scan)을 이용해 @Component 어노테이션이 있는 클래스들을 찾아 빈으로 등록을 한다. 우리가 직접 개발한 클래스를 빈으로 편리하게 등록 해야만 하는 경우에는 @Component 어노테이션을 활요하면 된다.

public class Resource {}

 

@Component를 갖는 어노테이션으로는 @Controller, @Service, @Repository 이 있으며 @Configuration도 @Component를 갖고 있다.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {

    @AliasFor(annotation = Component.class)
    String value() default "";

    boolean proxyBeanMethods() default true;

}

 스프링은 기본적으로 컴포넌트 스캔을 이용한 자동 빈 등록 방식을 권장한다. 또한 직접 개발한 클래스는 @Component를 통해 해당 클래스를 빈으로 등록함을 노출하는 것이 좋다. 이유는 해당 클래스에 있는 @Component만 보면 해당 빈이 등록이 되도록 설정 되었는지를 파악 할수 있기 때문이다. 위와 같이 수동으로 해야하는 경우를 제외한 대부분의 경우 자동 등록 방식을 사용하자.

그리고 @Component를 이용하면 Main 또는 App 클래스에서 @ComponentScan으로 컴포넌트 스캔 범위를 지정해 줘야한다. 그러나 SpringBoot를 이용한다면 @SpringBootConfiguration에 기본적으로 포함되어 있어 별도의 설정이 필요 없다.

간단 정리

 - @Bean, @Configuration

  • 수동으로 스프링 컨테이너에 빈을 동록하는 방법
  • 개발자가 직접 제어가 불가능한 라이브러리를 빈으로 등록할 때 불가피하게 사용
  • 유지보수성을 높이기 위해 애플리케이션 전 범위적으로 사용되는 클래스나 다형성을 활용하여 여러 구현체를 빈으로 등록 할 때 사용
  • 1개 이상의 @Bean을 제공하는 클래스의 경우 반드시 @Configuration을 명시해 줘야 싱글톤이 보장된다.

 - @Component

  • 자동으로 스프링 컨테이너에 빈을 등록하는 방법
  • 스프링의 컴포넌트 스캔 기능이 @Component 어노테이션이 있는 클래스를 자동으로 찾아 빈으로 등록한다.
  • 대부분의 경우 @Component를 이용한 자동 등록 방식을 사용하는 것이 좋다.
  • @Component 하위 어노테이션으로 @Configuration, @Controller, @Service, @Repository  등이 있다.

https://mangkyu.tistory.com/75?category=761302 

 

[Spring] 빈 등록을 위한 어노테이션 @Bean, @Configuration, @Component 차이 및 비교 - (1/2)

기존의 Spring MVC에서는 xml을 활용하여 Bean을 등록하고 있었다. 하지만 프로젝트의 규모가 커짐에 따라 사용하는 요소들을 xml에 등록하는 것이 상당히 번거로워 져서 어노테이션(Annotation, @)를 활

mangkyu.tistory.com

 

728x90
728x90
반응형

컨트롤러를 지정하기 위한 어노테이션은 @Controller @RestController가 있다. 두 어노테이션의 차이 점은 HTTP Response Body가 생성되는 방식이다.

 

● @Controller

-  Controller로 View 반환하기

 전통적인 Sprinv MVC의 컨트롤러인 @Controller는 주로 View를 반환하기 위해 사용한다. 아래와 같은 과정을 통해 Spring MVC Container는 Client의 요청으로부터 View를 반환한다.

Controller View Data 반환

  • Client는 URI 형식으로 웹 서비스에 요청을 보낸다.
  • DispatcherServlet이 요청을 위임할 HandlerMapping을 찾는다.
  • HandlerMapping을 통해 요청을 Controller로 위임한다.
  • Controller는 요청 처리후 ViewName을 반환한다.
  • DispatcherServlet은 ViewResolver를 통해 ViewName에 해당하는 View를 찾아 사용자에게 반환한다.

Controller가 반환한 뷰의 이름으로부터 View를 렌터딜하기 위해서는 ViewResolver가 사용되며, ViewResolver 설정에 맞게 View를 찾아 렌더링 한다.

Controller로 Data 반환하기

바로 Data를 반환해야 하는 경우도 있다. 컨트롤러에서는 데이터를 반환하기 위해 @ResponseBody 어노테이션을 활용해 주어야 한다. 이를 통해 Controller도 JSON 형태로 데이터를 반환 할 수 있다.

Controller JSON Data 반환

 

  • Client는 URI 형식으로 웹 서비스에 요청을 보낸다.
  • DispatcherServlet이 요청을 위임할 HandlerMapping을 찾는다.
  • HandlerMapping을 통해 요청을 Controller로 위임한다.
  • Controller는 요청을 처리한 후에 객체를 반환한다.
  • 반환되는 객체는 Json으로 Serialize되어 사용자에게 반환된다.

컨트롤러를 통해 객체를 반환할 경우 일반적으로 ResponseEntity로 감싸서 반환한다. 그리고 객체를 반환하기 위해선 viewResolver 대신 HttpMessageConverter가 동작한다. HttpMessageConverter에는 여러 Converter가 등록되어 있고, 반환해야 하는 데이터에 따라 사용되는 Converter가 달라진다.

스프링은 클라이언트의 HTTP Accept 헤더와 서버의 컨트롤러 반환 타입 정보 둘을 조합해 적합한 HttpMessageConverter를 선택하여 처리한다. MessageConverter가 동작하는 시점은 HandlerAdapter와 Controller가 요청을 주고 받는 시점이다. 

@Controller
    @RequiredArgsConstructor
    public class UserController {

        private final UserService userService;

        @GetMapping(value = "/users")
        public @ResponseBody ResponseEntity<User> findUser(@RequestParam("userName") String userName) {
            return ResponseEntity.ok(userService.findUser(user));
        }

        @GetMapping(value = "/users/detailView")
        public String detailView(Model model, @RequestParam("userName") String userName) {
            User user = userService.findUser(userName);
            model.addAttribute("user", user);
            return "/users/detailView";
        }
    }

 findUser는 User 객체를 ResponseEntity로 감싸서 반환하고 있고, User를 JSON으로 반환하기 위해 @ResponseBody라는 어노테이션을 붙여주고 있다. detailView 함수에서는 View를 전달해주고 있기 때문에 String을 반환값으로 설정해주었다.

 

● @RestController

 - @Controller에 @ResponseBody가 추가된 것이다. 주 용도는 JSON 형태로 객체 데이터를 반환하는 것이다. 최근엔 데이터를 응답으로 제공하는 REST API를 개발 할 때 사용되며 객체를 ResponseEntity로 감싸서 반환한다.

RestController 동작 방식

 

  • 클라이언트는 URI 형식으로 웹 서비스에 요청을 보낸다.
  • DispatcherServlet이 요청을 위임할 HandlerMapping을 찾는다.
  • HandlerMapping을 통해 요청을 Controller로 위임한다.
  • Controller는 요청을 처리한 후에 객체를 반환한다.
  • 반환되는 객체는 Json으로 Serialize되어 사용자에게 반환된다.
@RestController
    @RequestMapping("/user")
    @RequiredArgsConstructor
    public class UserController {

        private final UserService userService;

        @GetMapping(value = "/users")
        public User findUser(@RequestParam("userName") String userName){
            return userService.findUser(user);
        }

        @GetMapping(value = "/users")
        public ResponseEntity<User> findUserWithResponseEntity(@RequestParam("userName") String userName){
            return ResponseEntity.ok(userService.findUser(user));
        }
    }

 findUser는 User 객체를 그대로 반환하고 있다. 이 경우 문제는 클라이언트가 예상하는 HttpStatus를 설정해 줄수 없다는 것이다. 예를 들어 어떤 객체의 생성 요청이라면 201 CREATED를 기대 했지만 객체를 그대로 반환하면 HttpStatus를 설정해줄 수 없다. 그래서 객체를 상황에 맞는 ResponseEntity로 감싸서 반환해 주어야 한다.

https://mangkyu.tistory.com/49?category=761302 

 

[Spring] @Controller와 @RestController 차이

Spring에서 컨트롤러를 지정해주기 위한 어노테이션은 @Controller와 @RestController가 있습니다. 전통적인 Spring MVC의 컨트롤러인 @Controller와 Restuful 웹서비스의 컨트롤러인 @RestController의 주요한 차..

mangkyu.tistory.com

 

728x90
728x90
반응형

클라이언트에서 받은 요청을 객체로 바인딩 하기 위해 사용하는 방법에 알아보자

@RequestParam

- 1개의 HTTP 요청 파라미터를 받기 위해 사용한다. 필수 여부가 true로 설정되어 있기에 반드시 해당 파라미터가 반드시 해당 파라미터가 전송되어야 하며, 파라미터가 전송되니 않으면 400 에러가 발생한다. 반드시 필요한 값이 아니라면 required를 false로 설정해 주면 된다.(defaultValue 옵션을 사용하면 기본값을 지정할 수 있다.)

 

 @RequestBody

- 클라이언트가 전송하는 JSON(application/json) 형태의 HTTP Body를 Java 객체로 변환 시켜주는 역할을 한다. 그렇게 때문에 Body가 존재하지 않는 HTTP Get 메소드에 @RequestBody를 활용하려고 하면 에러가 발생한다. @RequestBody로 받는 데이터는 스프링에서 관리하는 MessageConverter들 중 하나인 MappingJackson2HttpMessageConverter를 통해  Java 객체로 변환되는데, 이는 ObjectMapper 라는 클래스를 사용한다. 물론 데이터 형식이 Json이 아닐 수도 있다.

ObjectMapper 동작 과정

더보기

ObjectMapper는 JSON 메시지를 자바 객체로 변환하는 과정에서 객체의 기본 생성자를 통해 객체를 생성하고, 내부적으로 Reflection을 사용한다. 그래서 반드시 Setter가 필요한 것은 아니다. Getter나 Setter 혹은 JsonInclude 등 필드에 있는 변수들의 이름을 찾기 위한 메소드들이 필요하다. 그러므로 기본생성자 + Getter로 클래스를 구현 하면 Setter가 없어도 값이 바인딩된다.

 

@ModelAttribute

- 클라이언트가 전송하는 폼(form)형태의 HTTP Body와 요청 파라미터들은 생성자나 Setter로 바인딩 하기 위해 사용된다. 매핑 시키른 파라미터의 타입이 객체으 타입과 일치하는지 등을 포함한 다양한 검증(Validation) 작업이 추가적으로 진행 되는 데, Long형 index에 "1번"을 String형을 넣을려고 한다면, BindException이 발생하게 된다.

특정 Parameter 값 만 가져올 수도 있다. 만약 다음과 같은 형태로 전송된 데이터에서 @ModelAttribute("writer") String writer을 사용하면 writer 변수에서 얻어진다.

ModelAttribute 동작 과정

더보기

Reflection을 사용해 필드를 인자로 받는 생성자가 있는지 검사한다. 만약 있다면 해당 생성자를 이용해 값을 세팅하고 없다면 Setter로 값을 세팅한다. 예를 들어 모든 필드를 인자로 받는 생성자가 있다면 해당 생성자로 값을 주입한다. 만약 없다면 일부 필드를 인자로 받는 생성자를 찾아 사용하고, 없으면 기본 생성자를 사용해 객체를 생성한다. 그리고 생성자로 주입하지 못한 남은 변수들은 Setter를 이용해 값을 할당한다. 예를 들어 다음의 경우에도 요청만 정상적으로 왔다면 모든 값이 바인딩되는 것이다. 아래의 경우라면 먼저 Board(1, "글쓴이")를 이용해 Board 객체가 생성되고, 그 다음에 Setter를 통해 contents에 값이 주입된다.

@Getter
@Setter
@ToString
public class Board {

    private int index;
    private String writer;
    private String contents;

    public Board(int index) {
        this.index = index;
    }

    public Board(int index, String writer) {
        this.index = index;
        this.writer = writer;
    }
}

RequestBody, ModelAttribute, RequestParam 활용 예시

 - Model 객체

@Getter
@Setter
@ToString
public class Board {

    private int index;
    private String writer;
    private String contents;

}

  - Spring에서 활용 예시

@RestController
    @RequestMapping("/board")
    public class BoardController {

        @PostMapping("/requestBody")
        public ResponseEntity<Board> requestBody(@RequestBody Board board) {
            // @RequestBody는 MessageConverter를 통해 Json 형태의 HTTP Body를 Java 객체로 변환시킨다.
            return ResponseEntity.ok(board);
        }

        @PostMapping("/modelAttribute")
        public ResponseEntity<Board> modelAttribute(@ModelAttribute Board board) {
            // @ModelAttribute는 폼(form) 형태의 HTTP Body와 요청 파라미터들을 객체에 바인딩시킨다.
            return ResponseEntity.ok(board);
        }

        @GetMapping("/list")
        public ResponseEntity<List<Board>> requestParam(
                @RequestParam(value = "searchKeyWord1", required = false, defaultValue = "MangKyu") String searchKeyWord) {

            // searchKeyWord는 required가 false이기 때문에 없을 수도 있으므로, 없다면 기본값이 할당된다.
            return ResponseEntity.ok(boardService.getBoardList(searchKeyWord1));
        }
    }

ModelAttribute는 multipart/form-data 형태의 HTTP Body와 파라미터들을 생성자나 Setter로 바인딩 시킨다고 했다. 그렇기에 만약 요청이 다음과 같은 경우라도 객체의 모든 데이터는 정상적으로 바인딩된다.

Request URL: http://localhost:8080/board/modelAttribute?contents=Contents

Request Body:
  - type: application/x-www-form-urlencoded
  - data: {
      "index": 1,
      "writer": "toyeka"
    }
    
    
Response Body:
  - data: {
      "index": 1,
      "writer": "toyeka",
      "contents: "Contents"
    }

 

간단 정리

 - RequestParam

  • 1개의 HTTP 파라미터를 얻기 위해 사용되며 기본값을 지정 할수 있다.
  • 필수 여부가 true이기 때문에 반드시 필요한 경우가 아니라면 required=false 설정이 필요하다.

 - RequestBody

  • JSON(application/json) 형태의 HTTP Body 데이터를 MessageConverter를 통해 Java 객체로 변환시킨다.
  • 기본 생성자로 객체를 만들고, Getter나 Setter 등의 메소드로 필드를 찾아 Reflection으로  값을 설정한다.

 - ModelAttribute

  • 폼 형태(form)의 HTTP Body와 요청 파라미터들을 객체에 바인딩한다.
  • 기본적으로 생성자로 값이 설정되고, 생성자로 설정되지 않는 필드는 Setter로 설정된다.

https://mangkyu.tistory.com/72?category=761302 

 

[Spring] @RequestBody, @ModelAttribute, @RequestParam의 차이

이번에는 Spring에서 Client로 받은 요청을 객체로 바인딩하기 위해 사용하는 방법들에 대해서 알아보도록 하겠습니다. 1. RequestBody, ModelAttribute, RequestParam이란? [ @RequestParam ] @RequestParam은..

mangkyu.tistory.com

 

728x90
728x90
반응형

목차

  1. 서블릿이란? (쉬운 비유)
  2. 서블릿의 핵심 특징
  3. 서블릿 컨테이너(예: Tomcat)의 역할
  4. 서블릿 동작 흐름(요청→매핑→doGet/doPost→응답)
  5. 서블릿 생명주기(init → service → destroy)
  6. JSP는 뭐고, 서블릿과 어떻게 다른가
  7. 실전 예제: HelloServlet (Annotation 기반)
  8. 자주 하는 실수 & 베스트 프랙티스
  9. 마무리 체크리스트(면접/시험 대비)

1) 서블릿이란? (쉬운 비유)

사용자(브라우저)가 “로그인해줘!”라고 부탁하면, 서블릿이 그 요청을 받아 DB를 확인하고 결과 페이지(응답)를 만들어 돌려주는 자바 클래스입니다.

  • “요청을 받아 응답을 만든다” = Controller 역할에 딱 맞음.

2) 서블릿의 핵심 특징

  • 동적 웹 컴포넌트: 요청 시점에 필요한 내용을 만들어 응답.
  • HttpServlet 상속: javax.servlet.http.HttpServlet(Jakarta EE에선 jakarta.servlet.http.HttpServlet)을 상속.
  • 스레드 기반 동작: 컨테이너가 요청마다 스레드를 재사용/관리.
  • MVC에서 Controller로 자주 사용.
  • (구)HTML을 자바 코드로 println 하던 시대 → 유지보수 어려워서 JSP/템플릿과 분리해서 씀.

3) 서블릿 컨테이너의 역할 (Tomcat 대표)

  • 웹 서버와 통신을 대신 처리(소켓, HTTP 파싱 등).
  • 생명주기 관리: 로딩→init()→요청 시 service()→종료 시 destroy().
  • 멀티스레드 처리: 요청마다 스레드 할당/관리.
  • 선언적 보안: 설정 파일/어노테이션 기반으로 인증·인가 처리.

4) 서블릿 동작 흐름

  1. 클라이언트가 URL 요청 → 컨테이너가 HttpServletRequest/Response 생성
  2. URL 매핑(web.xml 또는 @WebServlet)으로 대상 서블릿 찾기
  3. service()가 호출되어 메서드 분기(doGet/doPost 등)
  4. 서블릿이 응답을 만들어 HttpServletResponse로 전송
  5. 컨테이너가 마무리 정리

5) 서블릿 생명주기 (Life Cycle)

  • init(): 서블릿 최초 로딩 시 1회 호출(공유 리소스 초기화에 적합)
  • service(): 매 요청마다 호출되어 doGet()/doPost()로 분기
  • destroy(): 컨테이너 종료/교체 시 1회 호출(정리 작업)
[로딩] → init() → (요청 n번) service() → ... → destroy() → [언로드]

6) JSP는 무엇이고, 서블릿과 어떻게 다른가

  • JSP(Java Server Pages): HTML 안에 자바를 삽입해 화면 구성에 특화.
  • 최종적으로 JSP도 서블릿으로 변환되어 실행됨.
  • 일반적으로 서블릿=로직/제어, JSP=뷰 로 분리해서 MVC 구조를 만든다.

7) 실전 예제: HelloServlet (Annotation 기반)

// jakarta.servlet-api를 사용하는 최신 톰캣 기준 예시
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {

        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html; charset=UTF-8");

        PrintWriter out = resp.getWriter();
        out.println("<!DOCTYPE html>");
        out.println("<html><head><meta charset='UTF-8'><title>Hello</title></head>");
        out.println("<body><h1>안녕하세요, Servlet!</h1>");
        out.println("<p>요청 URI: " + req.getRequestURI() + "</p>");
        out.println("</body></html>");
    }
}

Tip: 구형 프로젝트는 web.xml 매핑을 쓰지만, 요즘은 @WebServlet("/path") 어노테이션 매핑이 간편합니다.

8) 자주 하는 실수 & 베스트 프랙티스

  • 인스턴스 변수에 상태 저장 금지: 서블릿은 다중 스레드가 공유합니다.
  • 문자 인코딩 설정: resp.setContentType("text/html; charset=UTF-8") 등 필수.
  • 비즈니스 로직 분리: DAO/Service로 분리하고 서블릿은 흐름 제어만.
  • 에러/예외 처리: 공통 에러 페이지, 필터/인터셉터로 로깅과 인증 처리.
  • JSP에서 스크립틀릿 최소화: EL/JSTL, 템플릿 엔진(Thymeleaf 등)로 표현만 담당.

9) 마무리 체크리스트 (면접/시험 포인트)

  • 서블릿과 JSP의 관계 설명 가능?
  • 컨테이너 역할(생명주기·스레드·보안) 술술 말할 수 있는지?
  • Life Cycle 순서/용도 정확히 아는지?
  • URL 매핑(@WebServlet vs web.xml) 차이 이해했는지?

SEO용 FAQ

Q1. 서블릿은 왜 필요한가요?
A. HTTP 요청을 자바로 처리해 동적 페이지를 만드는 표준 컨트롤러이기 때문입니다.

Q2. JSP랑 뭐가 달라요?
A. JSP는 화면 템플릿, 서블릿은 로직/흐름 제어. JSP도 결국 서블릿으로 변환돼 실행됩니다.

Q3. 스프링 MVC랑 관계는?
A. 스프링 MVC도 내부적으로 서블릿(DispatcherServlet)을 사용합니다. 서블릿 개념을 알면 스프링이 쉬워집니다.

728x90

+ Recent posts