멈추지 않는 기록

[일지] 240310 본문

웹 개발/Spring Boot

[일지] 240310

pangil_kim 2025. 3. 11. 00:33
728x90
스프링 구조 이해

Controller, Service, Repository는 스프링 애플리케이션의 3계층 구조를 이루는 핵심 구성 요소다.

 

- Controller: 클라이언트의 요청을 받아 처리하는 역할을 한다. HTTP 요청을 받고, 필요한 데이터를 가공한 후 응답을 반환한다. @RestController 또는 @Controller를 사용한다.

- Service: 비즈니스 로직을 담당하는 계층이다. Controller에서 받은 요청을 처리하고, 필요한 경우 Repository를 호출하여 데이터를 조회·변경한다. @Service를 사용한다.

- Repository: 데이터베이스와 직접적으로 소통하는 계층이다. JPA, MyBatis 등을 이용해 데이터를 저장·조회·수정·삭제하는 역할을 한다. @Repository 또는 JpaRepository 인터페이스를 활용한다.

 

스프링 컴포넌트 이해

스프링에서는 대부분의 클래스들이 컴포넌트로 관리되며, 이를 스프링 컨테이너가 관리하는 빈(Bean)이라고 부른다.

 

- @Component를 사용하면 해당 클래스가 스프링 빈으로 등록된다.

- @Controller, @Service, @Repository도 @Component를 확장한 애노테이션이며, 역할별로 구분하여 사용한다.

 

스프링 컨테이너는 애플리케이션 실행 시 이러한 컴포넌트들을 스캔하고 관리한다.

 

Entity, Dto 이해

스프링에서 데이터 객체는 Entity DTO로 나뉜다.

 

- Entity: 데이터베이스 테이블과 1:1로 매핑되는 객체다. 주로 @Entity 애노테이션을 사용하며, JPA를 통해 DB의 데이터를 직접 조회·수정할 때 사용한다.

- DTO (Data Transfer Object): 데이터를 전송하기 위한 객체다. Entity는 직접 노출하지 않고, 필요한 필드만 DTO로 변환하여 Controller와 Service 사이에서 데이터를 주고받는다. @Getter, @Setter, @AllArgsConstructor 등을 활용하여 만든다.

 

Entity는 데이터베이스와 직접 연관되며, DTO는 데이터 이동을 위해 사용된다는 차이점이 있다.

 


 

스프링 스큐리티의 구조

필터는 Spring Context의 외부에, 인터셉터는 내부에 있는 것을 볼 수 있다.

1. 필터 (Filter)

- 요청(request)이 지나갈 때, 컨트롤러가 실행될 때, 그리고 응답(response)이 나갈 때 데이터를 변경하거나 조회할 수 있다.

- spring 컨테이너 밖에 존재한다.

 

2. 인터셉터 (Interceptor)

- 뷰가 완성될 때도 데이터를 확인할 수 있다.

- 요청(request)과 응답(response)의 값을 수정, 입력, 조회, 삭제할 수 있다.

- spring 컨테이너 안에 존재한다.

 

즉, 필터나 인터셉터를 활용하여 특정 동작을 구현할 수 있다. 이를 기반으로 인터셉터를 커스터마이징하여 스프링 시큐리티와 유사한 구조를 구현해보고자 한다.

 

 

 

인터셉터 구현

DefaultInterceptor.java

: DefaultInterceptor는 HandlerInterceptor 인터페이스를 구현(implements) 받아서 preHandle, postHandle, afterCompletion 메서드의 내용을 직접 정의하는 것이다.

package com.thc.sprboot.interceptor;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import java.util.Enumeration;

public class DefaultInterceptor implements HandlerInterceptor {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    // 컨트롤러 실행 전에 호출되는 메서드
    // 요청(request)이 컨트롤러로 전달되기 전에 가로채서 필요한 작업을 수행할 수 있음
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        logger.info("preHandle / request [{}]", request);

        // 요청 속성 설정 (컨트롤러에서 사용할 수 있음)
        request.setAttribute("hahah", "112233");

        // 요청 속성(attribute) 목록을 가져와서 로그로 출력
        Enumeration<String> requestNames = request.getAttributeNames();
        while (requestNames.hasMoreElements()) {
            String name = requestNames.nextElement();
            logger.info("preHandle request / {} = {} ", name, request.getAttribute(name));
        }

        // 요청 헤더(header) 목록을 가져와서 로그로 출력
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String name = headerNames.nextElement();
            logger.info("preHandle header / {} = {} ", name, request.getHeader(name));
        }

        return true; // true를 반환하면 요청이 컨트롤러로 전달됨 (false이면 요청을 차단)
    }

    // 컨트롤러 실행 후에 호출되는 메서드
    // 컨트롤러가 정상적으로 실행된 후, 응답(response)이 클라이언트로 전달되기 전에 실행됨
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        logger.info("postHandle / request [{}]", request);
    }

    // 요청 처리가 완료된 후 실행되는 메서드
    // 응답이 클라이언트로 전달된 후 실행되며, 리소스 해제나 로그 기록 등의 작업을 수행할 수 있음
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        logger.info("afterCompletion / request [{}]", request);
    }

}

1) 생성 메서드

- preHandle() : 컨트롤러 실행 전에 요청을 가로채서 '헤더 정보, 요청 속성' 등을 로깅하고, 특정 속성을 설정한다.

- postHandle() : 컨트롤러 실행 후에 요청을 처리한다.

- afterCompletion() : 요청 처리가 완료된  후 실행된다.

 

2) 요청 흐름

: 클라이언트 요청 → DispatcherServlet  Interceptor (preHandle)  Controller  Interceptor (postHandle)  DispatcherServlet (응답 반환) → Interceptor (afterCompletion) → 클라이언트.

 

 

 

preHandle을 살펴보자 (requestNames / headerNames)

1) 요청 속성(attribute) 확인

 // 요청 속성(attribute) 목록을 가져와서 로그로 출력
Enumeration<String> requestNames = request.getAttributeNames();
while (requestNames.hasMoreElements()) {
    String name = requestNames.nextElement();
    logger.info("preHandle request / {} = {} ", name, request.getAttribute(name));
}

(1) 설명

- request.getAttributeNames()를 사용하여 현재 요청(request)에 설정된 모든 속성(attribute) 목록을 가져옴.

- while (requestNames.hasMoreElements())를 통해 각 속성의 이름(name)을 하나씩 가져옴.

- request.getAttribute(name)을 사용하여 해당 속성의 값을 가져온 후, 로그(logger)를 통해 출력.

- 이 과정은 인터셉터에서 요청에 어떤 속성이 설정되어 있는지 확인하는 용도로 사용됨.

 

(2) 예시

- 예를 들어, 다른 인터셉터나 필터에서 request.setAttribute("userId", 1234);처럼 속성을 추가하면,
- 이 코드에서 "preHandle request / userId = 1234"라는 로그가 출력됨.

 

2) 요청 헤더(header) 확인

// 요청 헤더(header) 목록을 가져와서 로그로 출력
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
    String name = headerNames.nextElement();
    logger.info("preHandle header / {} = {} ", name, request.getHeader(name));
}

(1) 설명

- request.getHeaderNames()를 사용하여 현재 요청(request)에 포함된 모든 헤더(header) 목록을 가져옴.

- while (headerNames.hasMoreElements())를 통해 각 헤더의 이름(name)을 하나씩 가져옴.

- request.getHeader(name)을 사용하여 해당 헤더의 값을 가져온 후, 로그(logger)를 통해 출력.

- 이 과정은 클라이언트가 요청할 때 어떤 헤더를 포함했는지 확인하는 용도로 사용됨.

 

(2) 예시

- 예를 들어, 요청에 Authorization: Bearer abc123 헤더가 포함되어 있다면,
- 이 코드에서 "preHandle header / Authorization = Bearer abc123"라는 로그가 출력됨.

 

 

 

인터셉터 등록

WebMvcConfig.java

: WebMvcConfigurer 인터페이스를 구현하여 인터셉터를 등록하는 역할을 한다.

package com.thc.sprboot.config;
 
 import com.thc.sprboot.interceptor.DefaultInterceptor;
 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 WebMvcConfig implements WebMvcConfigurer {
 
     //인터셉터 설정을 위함
     @Override
     public void addInterceptors(InterceptorRegistry registry) {
         registry.addInterceptor(new DefaultInterceptor())
                 .addPathPatterns("/api/**")
                 .excludePathPatterns("/resources/**", "/api/auth/accessToken", "/api/user/signup", "/api/user/login"); //인터셉터가 실행되지 않아야 하는 url 패턴
     }
 
 }

1-1) @Configuration

- @Configuration이 선언되어 있어 Spring이 이 클래스를 설정 파일로 인식한다.

 

1-2) WebMvcConfig

- WebMvcConfig WebMvcConfigurer를 구현하고 있으며, 이는 Spring MVC의 설정을 커스터마이징할 수 있도록 제공되는 인터페이스이다.

- Spring이 애플리케이션을 실행할 때 WebMvcConfig를 스캔하여 WebMvcConfigurer의 메서드들을 호출한다.

 

2) 주요 메서드

- addInterceptor(Interceptor interceptor) : 새로운 인터셉터를 등록할 때 사용

- addPathPatterns(String... patterns) : 인터셉터가 적용될 URL 패턴을 지정한다.

- excludePathPatterns(String... patterns) : 인터셉터가 적용되지 않을 URL 패턴을 지정한다.

 

3) 동작 구조

(1) addInterceptors 메서드가 호출되면서 InterceptorRegistry에 DefaultInterceptor가 등록된다.

(2) addPathPatterns("/api/**") 설정에 따라 /api/로 시작하는 모든 요청이 DefaultInterceptor를 거쳐 처리된다.

(3) excludePathPatterns 설정에 있는 /resources/**, /api/auth/accessToken 등의 요청은 인터셉터를 거치지 않고 바로 처리된다.

-> 즉, 클라이언트가 /api/** 요청을 보내면 WebMvcConfig에서 등록된 DefaultInterceptor가 실행되어 요청을 가로채고, 제외된 경로는 인터셉터 없이 처리된다.

 


 

인터셉터 활용

NoticeRestController.java

: REST API 컨트롤러로, 클라이언트의 요청을 처리하는 역할을 한다.

    @PostMapping("")
    public ResponseEntity<NoticeDto.CreateResDto> create(@RequestBody NoticeDto.CreateReqDto params, HttpServletRequest request) {

        String hahah = (String) request.getAttribute("hahah");
        System.out.println("hahah :" + hahah);

        //return ResponseEntity.status(HttpStatus.CREATED).body(noticeService.create(params));
        return ResponseEntity.ok(noticeService.create(params));
    }

1) 의미

- create 메서드에서 요청 객체에서 "hahah" 값을 가져와 출력한다.

- 이는 preHandle에서 설정한 값이 컨트롤러에서 그대로 유지되는 것을 보여준다.

 

2) 사용법

(1) 메서드의 파라미터에 `HttpServletRequest request`를 추가한다. 

(2) request 객체의 `getAttribute` 메서드를 사용하여 특정 속성 값을 추출한다.

 

 

 

적용해보는 예시 : 요청온 정보를 컨트롤러에서 확인하는 법

@RequestBody, @RequestParam, @RequestHeader 등 여러 가지 방법이 있지만, HttpServeletRequest를 이용하여 확인하는 방법을 알아보자.

 

1) HttpServletRequest를 이용한 확인

 

- HttpServletRequest 객체를 매개변수로 받아서 요청 정보를 확인할 수 있음.

- 요청 헤더(Header), 요청 속성(Attribute), 요청 파라미터(Parameter) 등을 직접 가져올 수 있음.

(1) 예시

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

    @GetMapping("/api/test")
    public String testRequestInfo(HttpServletRequest request, @RequestParam String name, @RequestParam int age) {

        // 요청 정보 출력
        System.out.println("Request Method: " + request.getMethod());
        System.out.println("Request URL: " + request.getRequestURL());
        System.out.println("Request URI: " + request.getRequestURI());
        System.out.println("Query String: " + request.getQueryString());
        System.out.println("Remote Address: " + request.getRemoteAddr());

        return "Request received: name=" + name + ", age=" + age;
    }
}

(2) 실행 결과 : GET /api/test?name=John&age=30

Request Method: GET  
Request URL: http://localhost:8080/api/test  
Request URI: /api/test  
Query String: name=John&age=30  
Remote Address: 127.0.0.1

 

 

2) 다양한 방식들

방법 어노테이션/메소드 설명
요청 속성(Attribute) 확인  request.getAttribute("key") 인터셉터 등에서 설정한 속성 값 가져오기
요청 헤더(Header) 확인 request.getHeader("User-Agent") 
또는 @RequestHeader("User-Agent")
요청 헤더 값 가져오기
요청 파라미터(Parameter) 확인 request.getParameter("name") 
또는 @RequestParam("name")
URL 쿼리 파라미터 값 가져오기
요청 바디(JSON) 확인 @RequestBody Map<String, Object> JSON 데이터를 객체로 변환

 

 

 


Summary (스프링 인터셉터 요약)

1. 인터셉터 구현 단계

1) 인터셉터 클래스 생성

  • HandlerInterceptor 인터페이스 구현
  • 주요 메서드:
    • preHandle(): 컨트롤러 실행 전 호출, 요청 가로채기 (true 반환 시 컨트롤러로 요청 전달)
    • postHandle(): 컨트롤러 실행 후, 응답 전송 전 호출
    • afterCompletion(): 요청 처리 완료 후 호출

2) 인터셉터 등록

  • WebMvcConfigurer 인터페이스 구현 클래스 생성
  • @Configuration 어노테이션 추가
  • addInterceptors() 메서드 오버라이드:
    • registry.addInterceptor(): 인터셉터 등록
    • addPathPatterns(): 인터셉터 적용 URL 패턴 지정
    • excludePathPatterns(): 인터셉터 제외 URL 패턴 지정

3) 인터셉터 활용

  • 인터셉터에서 설정한 요청 속성은 컨트롤러에서 접근 가능
  • request.getAttribute("속성명") 으로 인터셉터에서 설정한 값 접근

2. 요청 정보 확인 방법

1) 요청 속성(Attribute) 확인

// 인터셉터에서 설정
request.setAttribute("key", "value");

// 컨트롤러에서 조회
String value = (String) request.getAttribute("key");

2) 요청 헤더(Header) 확인

// 모든 헤더 확인
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
    String name = headerNames.nextElement();
    String value = request.getHeader(name);
}

3) 컨트롤러에서 요청 정보 처리

@PostMapping("")
public ResponseEntity<?> method(HttpServletRequest request) {
    // 인터셉터에서 설정한 속성 값 확인
    String value = (String) request.getAttribute("key");

    // 이후 로직 처리
    return ResponseEntity.ok().build();
}

728x90