no image
[Spring] 어노테이션 정리
@Component클래스를 스프링 빈으로 등록하기 위한 일반적인 스테레오타입 어노테이션. 스프링이 관리하는 컴포넌트임을 나타낸다. @Component 어노테이션은 @Repository, @Service, @Controller와 같은 더 구체적인 스테레오타입 어노테이션의 기반으로 작용한다.이들은 각각 데이터 접근 계층, 비즈니스 로직 계층, 프레젠테이션 계층의 클래스를 나타내는 데 사용되며, @Component의 특수한 형태로 볼 수 있다. 이 구체적인 어노테이션들을 사용함으로써, 개발자는 애플리케이션의 다양한 부분을 더 명확하게 구분할 수 있고, 스프링은 특정 역할을 가진 컴포넌트를 적절히 처리할 수 있다. @ComponentScan스프링이 @Component 어노테이션이 붙은 클래스를 찾아 빈으로 등록..
2024.02.23
no image
Mac에서 Oracle(XE) + Mysql + DBeaver 설치
아래의 모든 명령어은 Homebrew가 설치되어있다고 가정한다. DBeaver 설치 터미널에서 아래의 명령어를 입력하면 특별한 오류가 나지 않는이상 설치는 완료된다. brew install --cask dbeaver-community 이후 앱을 실행하면 된다. Oracle(XE) Oracle XE Oracle Database Express Edition (Oracle XE)은 Oracle Corporation에서 제공하는 무료, 경량 버전의 Oracle 데이터베이스이다. 이 버전은 개발자, 교육용으로, 그리고 소규모 배포를 위해 설계되었다. Oracle XE는 기능적으로는 전체 Oracle 데이터베이스 시스템의 서브셋을 제공하지만, 많은 기본적인 기능과 툴을 포함하고 있어 데이터베이스 학습이나 개발, 테..
2024.02.22
no image
[DBeaver] Public Key Retrieval is not allowed Public Key Retrieval is not allowed
DBeaver 에서 MySQL을 연결하려고 하였으나 Public key retrieval is not allowed 오류가 발생하였다.이 오류는 데이터베이스 서버로부터 공개 키를 안전하게 검색하는 것이 기본적으로 허용되지 않을 때 발생하는데, 주로 MySQL 8.0 이상 버전에서 새로운 인증 방식을 사용할 때 나타난다고 한다. 해결 방법은 간단했다.이미 설정된 연결 목록에서 해당 연결을 우클릭하고 "Edit Connection"을 선택"Connection Settings" 창에서 "Driver Properties" 탭으로 이동여기서 allowPublicKeyRetrieval 속성을 찾아 값을 true로 설정모든 변경 사항을 적용한 후, "OK" 또는 "Apply" 버튼을 클릭하여 연결 설정을 저장 이제 ..
2024.02.22
no image
[Spring MVC] 기본 기능 - HTTP 응답
이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다. 스프링(서버)에서 응답 데이터를 만드는 방법은 크게 3가지이다. 정적 리소스 -예) 웹 브라우저에 정적인 HTML, css, js를 제공할 때는, 정적 리소스를 사용한다. 뷰 템플릿 사용 -예) 웹 브라우저에 동적인 HTML을 제공할 때는 뷰 템플릿을 사용한다. HTTP 메시지 사용 -HTTP API를 제공하는 경우에는 HTML이 아니라 데이터를 전달해야 하므로, HTTP 메시지 바디에 JSON 같은 형식으로 데이터를 실어 보낸다. 정적 리소스 스프링 부트는 클래스패스의 다음 디렉토리에 있는 정적 리소스를 제공한다. /static , /public , /resources , /META-INF/resources ..
2024.02.22
no image
[Spring MVC] 기본 기능 - HTTP 요청, 요청 파라미터
이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다. HTTP 요청 어노테이션 기반의 스프링 컨트롤러는 다양한 파라미터를 지원한다. @Slf4j @RestController public class RequestHeaderController { @RequestMapping("/headers") public String headers(HttpServletRequest request, HttpServletResponse response, HttpMethod httpMethod, Locale locale, @RequestHeader MultiValueMap headerMap, @RequestHeader("host") String host, @CookieValue(valu..
2024.02.21
no image
[Spring MVC] 기본 기능 - 요청 매핑
이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다. @RestController 특정 클래스를 RESTful 웹 서비스의 컨트롤러로 지정한다. 이 어노테이션을 사용한 클래스는 HTTP 요청을 처리하는 핸들러 메소드를 포함하며, 각 메소드는 특정 HTTP 요청(예: GET, POST, DELETE 등)에 매핑된다. @RestController는 @Controller 와 유사하지만, 차이점은 @RestController 로 지정된 컨트롤러의 메소드가 기본적으로 HTTP 응답 본문(Body)에 직접 데이터를 작성한다는 점이다.이는@ResponseBody 어노테이션을 모든 핸들러 메소드에 적용한 것과 같은 효과를 가진다. @Controller 는 반환 값이 String..
2024.02.20
no image
[Spring] Logging
이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다. 운영 시스템에서는 System.out.println() 같은 시스템 콘솔을 사용해서 필요한 정보를 출력하지 않고, 별도의 로깅 라이브러리를 사용해서 로그를 출력한다. 스프링 부트 라이브러리를 사용하면 스프링 부트 로깅 라이브러리(spring-boot-starter-logging)가 함께 포함된다. 스프링 부트 로깅 라이브러리는 기본으로 다음 로깅 라이브러리를 사용한다. SLF4J - http://www.slf4j.org Logback - http://logback.qos.ch 로그 라이브러리는 Logback, Log4J, Log4J2 등등 수 많은 라이브러리가 있는데, 그것을 통합해서 인터페이스로 제공하는 것이..
2024.02.19
no image
[Spring MVC] 스프링 MVC - 구조 이해
이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다. 2024.02.17 - [Java Category/JSP] - JSP와 서블릿을 이용한 MVC 프레임워크 만들기 JSP와 서블릿을 이용한 MVC 프레임워크 만들기 이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다. Model, View, Controller 컨트롤러: HTTP 요청을 받아서 파라미터를 검증하고, 비즈니스 로직을 실행한다. 그 rebugs.tistory.com 위 글과 이어진 내용입니다. JSP로 만든 MVC 프레임워크와 스프링 MVC 프레임워크의 구조가 매우 비슷하다는 것을 알 수 있다. 핵심은 FrontController와 DispatcherServl..
2024.02.18

@Component

클래스를 스프링 빈으로 등록하기 위한 일반적인 스테레오타입 어노테이션. 스프링이 관리하는 컴포넌트임을 나타낸다.

 

@Component 어노테이션은 @Repository, @Service, @Controller와 같은 더 구체적인 스테레오타입 어노테이션의 기반으로 작용한다.

이들은 각각 데이터 접근 계층, 비즈니스 로직 계층, 프레젠테이션 계층의 클래스를 나타내는 데 사용되며, @Component의 특수한 형태로 볼 수 있다.

 

이 구체적인 어노테이션들을 사용함으로써, 개발자는 애플리케이션의 다양한 부분을 더 명확하게 구분할 수 있고, 스프링은 특정 역할을 가진 컴포넌트를 적절히 처리할 수 있다.

 

@ComponentScan

스프링이 @Component 어노테이션이 붙은 클래스를 찾아 빈으로 등록할 때 검색할 패키지 범위를 지정한다.
이 어노테이션은 @Component 뿐만 아니라, @Repository, @Service, @Controller와 같은 어노테이션이 붙은 클래스를 대상으로 한다. 

@ComponentScan은 보통 스프링의 설정 정보를 담고 있는 클래스에 선언된다. 이 어노테이션은 basePackages 속성을 통해 스캔할 패키지의 범위를 지정할 수 있다. 지정하지 않을 경우, @ComponentScan이 선언된 클래스의 패키지가 기본 스캔 위치가 된다.

@Configuration
@ComponentScan(basePackages = "com.example.project")
public class AppConfig {
    // 추가적인 빈 설정이나 구성 정보...
}

 

@Bean

개발자가 직접 제어할 수 없는 외부 라이브러리들을 포함하여, 개발자가 직접 생성과 구성을 관리하고 싶은 객체를 스프링의 의존성 주입 컨테이너에 빈(bean)으로 등록하고자 할 때 사용된다.

 

이 어노테이션은 메소드 레벨에서 사용되며, 해당 메소드가 반환하는 객체를 스프링 애플리케이션 컨텍스트의 관리 대상인 빈으로 등록한다.

@Bean 어노테이션이 붙은 메소드는 보통 @Configuration 어노테이션이 선언된 클래스 내에 위치한다.

이 메소드는 스프링 컨테이너가 관리할 객체의 인스턴스를 생성하고 초기화하는 로직을 담는다.

스프링은 이 메소드를 호출하고, 반환된 객체를 애플리케이션 컨텍스트에 빈으로 등록한다.

 

@Qualifier

자동 의존성 주입(Autowired) 과정 중에, 동일한 타입의 빈이 여러 개 있을 때, 주입할 빈을 구체적으로 지정하는 데 사용된다.

즉, @Autowired와 함께 사용되어, 스프링이 자동으로 의존성을 주입할 때 어떤 빈을 선택해야 하는지를 명확하게 해준다.

 

스프링에서 동일한 인터페이스를 구현한 여러 빈이 있고, 특정 빈을 주입받고 싶은 경우, @Qualifier 어노테이션을 사용하여 빈의 이름을 지정할 수 있다.

@Component
public class MyService {
    private final MyRepository myRepository;

    @Autowired
    public MyService(@Qualifier("specificRepository") MyRepository myRepository) {
        this.myRepository = myRepository;
    }
}

위 예시에서, MyRepository 인터페이스를 구현한 여러 빈 중에서 specificRepository라는 이름의 빈을 MyService에 주입하고자 할 때, @Qualifier("specificRepository")를 사용한다.

주요 기능

  • 의존성 주입의 명확성: @Autowired만 사용할 때보다 더 명확하게 특정 빈을 지정할 수 있게 해줌으로써, 의도치 않은 빈의 주입을 방지한다.
  • 유연한 구성: 애플리케이션의 구성을 더 유연하게 만들어, 같은 타입의 빈이 여러 개 있어도 각 상황에 맞게 적절한 빈을 선택하여 사용할 수 있다.

 

주의사항

  • 빈 이름의 일치: @Qualifier 어노테이션에 지정된 이름은 스프링 컨테이너에서 관리되는 빈의 이름과 정확히 일치해야 한다.
  • 문서화와 유지보수: @Qualifier를 사용할 때는 왜 특정 빈을 선택했는지, 그리고 해당 빈이 어떤 역할을 하는지에 대한 문서화가 중요하다. 이는 코드의 가독성을 높이고, 유지보수를 용이하게 한다.

 

@Primary

@Primary 어노테이션은 스프링 프레임워크에서 자동 의존성 주입 과정 중 동일한 타입의 빈이 여러 개 있을 때, 기본으로 사용될 빈을 지정하는 데 사용된다.

이 어노테이션은 특정 상황이나 조건 없이 일반적으로 우선적으로 사용되어야 하는 빈을 스프링 컨테이너에 알린다.

@Primary는 @Autowired와 같은 자동 주입 설정과 함께 사용될 때 특히 유용하다.

@Component
public class PrimaryRepository implements MyRepository {
    // 구현체
}

@Component
@Primary
public class SecondaryRepository implements MyRepository {
    // 구현체
}

위 예시에서 MyRepository 인터페이스를 구현하는 두 개의 구현체가 있고, SecondaryRepository에 @Primary 어노테이션을 붙여 이를 기본 구현체로 지정했다.

이제 MyRepository 타입의 빈을 주입받아야 하는 경우, 스프링은 @Primary가 붙은 SecondaryRepository를 우선적으로 사용한다.

주요 기능

  • 기본 빈 지정: 동일 타입의 여러 빈 중에서 기본으로 사용될 빈을 명시적으로 지정한다.
  • 자동 주입 간소화: @Autowired 사용 시 @Qualifier 없이도 명확하게 주입할 빈을 결정할 수 있게 해, 의존성 주입 과정을 간소화한다.
  • 설정의 유연성 증가: 애플리케이션의 다양한 구성 요소 간의 결합도를 낮추고, 설정의 유연성을 증가시킨다.

 

주의사항

  • 명확한 사용 목적: @Primary 어노테이션은 여러 빈 중에서 기본 빈을 선택할 명확한 이유가 있을 때 사용해야 한다. 무분별한 사용은 의존성 주입 과정의 명확성을 해칠 수 있다.
  • @Qualifier와의 관계: @Primary로 지정된 빈이 있어도, @Qualifier 어노테이션을 사용하여 특정 빈을 명시적으로 지정할 수 있다. @Qualifier가 있을 경우, @Primary보다 우선시된다.

 

 

@PostConstruct

@PostConstruct 어노테이션은 스프링 프레임워크에서 빈의 초기화 작업을 수행하기 위해 사용된다.

이 어노테이션이 붙은 메서드는 빈의 생성자 호출 이후, 의존성 주입이 완료되고 나서 바로 실행된다.

@PostConstruct를 사용하면 개발자는 빈이 완전히 생성되었을 때 필요한 초기화 작업을 안전하게 수행할 수 있으며, 이는 리소스 생성, 데이터 사전 로딩, 설정 검증 등 다양한 목적으로 활용될 수 있다.

@Component
public class MyBean {
    
    @PostConstruct
    public void init() {
        // 초기화 로직 수행
    }
}

위 예시에서 MyBean 클래스는 스프링 관리 빈으로 선언되어 있고, init 메서드에는 @PostConstruct 어노테이션이 붙어 있다. 스프링 컨테이너는 MyBean 인스턴스를 생성하고 의존성 주입을 완료한 후 init 메서드를 자동으로 호출하여 초기화 작업을 수행한다.

주요 기능

  • 안전한 초기화: 의존성 주입이 완료된 후 초기화 로직을 실행함으로써, 모든 의존성이 제대로 설정된 상태에서 안전하게 리소스를 할당하거나 초기화 로직을 수행할 수 있다.
  • 코드 정리: 초기화 로직을 생성자나 설정 메서드에 분산하지 않고, @PostConstruct 어노테이션이 붙은 메서드에 집중함으로써 코드를 더 깔끔하고 관리하기 쉽게 만든다.
  • 자동 호출 보장: 스프링 컨테이너는 @PostConstruct 어노테이션이 붙은 메서드를 빈의 생명주기에 맞춰 자동으로 호출하므로, 개발자는 명시적으로 초기화 메서드를 호출할 필요가 없다.

 

주의사항

  • 실행 시점: @PostConstruct 어노테이션이 붙은 메서드는 빈이 생성되고 의존성 주입이 완료된 직후 단 한 번만 호출된다. 따라서 메서드 내에서는 빈의 상태를 변경하는 등의 초기화 작업만 수행해야 한다.
  • 메서드 제한: @PostConstruct 어노테이션은 하나의 클래스에 하나의 메서드에만 적용할 수 있다. 또한, 이 메서드는 파라미터를 가질 수 없으며, void를 반환 타입으로 가져야 한다.
  • 자바 표준: @PostConstruct 어노테이션은 자바 EE에서 정의된 표준이며, 스프링뿐만 아니라 다른 자바 EE 호환 컨테이너에서도 사용될 수 있다.

 

@PreDestroy

@PreDestroy 어노테이션은 스프링 프레임워크에서 빈의 생명주기가 끝나기 직전, 즉 빈이 소멸되기 전에 실행되어야 하는 메소드에 사용된다.

이 어노테이션을 사용함으로써, 개발자는 빈이 제거되기 전에 필요한 정리 작업을 안전하게 수행할 수 있으며, 이는 리소스 해제, 연결 닫기, 임시 파일 삭제 등 다양한 목적으로 활용될 수 있다.

@Component
public class MyBean {

    @PreDestroy
    public void destroy() {
        // 정리 로직 수행
    }
}

위 예시에서 MyBean 클래스는 스프링 관리 빈으로 선언되어 있고, destroy 메서드에는 @PreDestroy 어노테이션이 붙어 있다. 스프링 컨테이너는 MyBean 인스턴스를 소멸하기 직전에 destroy 메서드를 자동으로 호출하여 정리 작업을 수행한다.

주요 기능

  • 안전한 자원 해제: 빈이 소멸되기 전에 필요한 자원 해제 및 정리 작업을 수행할 수 있어, 메모리 누수나 리소스 낭비를 방지한다.
  • 코드 정리: 정리 작업을 생성자나 다른 메서드에 분산하지 않고, @PreDestroy 어노테이션이 붙은 메서드에 집중함으로써 코드를 더 깔끔하고 관리하기 쉽게 만든다.
  • 자동 호출 보장: 스프링 컨테이너는 @PreDestroy 어노테이션이 붙은 메서드를 빈의 생명주기에 맞춰 자동으로 호출하므로, 개발자는 명시적으로 정리 메서드를 호출할 필요가 없다.

 

주의사항

  • 실행 시점: @PreDestroy 어노테이션이 붙은 메서드는 빈이 소멸되기 직전에 단 한 번만 호출된다. 따라서 메서드 내에서는 리소스 해제나 정리 작업만 수행해야 한다.
  • 메서드 제한: @PreDestroy 어노테이션은 하나의 클래스에 하나의 메서드에만 적용할 수 있다. 또한, 이 메서드는 파라미터를 가질 수 없으며, void를 반환 타입으로 가져야 한다.
  • 자바 표준: @PreDestroy 어노테이션은 자바 EE에서 정의된 표준이며, 스프링뿐만 아니라 다른 자바 EE 호환 컨테이너에서도 사용될 수 있다.

 

 

@Scope

@Scope 어노테이션은 스프링 프레임워크에서 빈의 스코프(생명주기 범위)를 지정할 때 사용된다.

스프링 빈의 스코프는 해당 빈이 어떻게 생성되고, 어떤 범위 내에서 존재하는지를 결정한다.

기본적으로 스프링 빈은 싱글톤 스코프를 가지지만, @Scope 어노테이션을 사용하여 빈의 스코프를 변경할 수 있다.

스코프 종류

  • 싱글톤(Singleton): 기본 스코프. 스프링 컨테이너당 빈 인스턴스가 하나만 생성된다.
  • 프로토타입(Prototype): 빈을 요청할 때마다 새로운 인스턴스가 생성된다.
  • 요청(Request): HTTP 요청당 하나의 빈 인스턴스가 생성된다. 주로 웹 애플리케이션에서 사용된다.
  • 세션(Session): HTTP 세션당 하나의 빈 인스턴스가 생성된다. 주로 웹 애플리케이션에서 사용된다.

 

@Component
@Scope("prototype")
public class MyPrototypeBean {
    // 클래스 정의
}

위 예시에서 MyPrototypeBean 클래스는 @Scope("prototype") 어노테이션을 사용하여 프로토타입 스코프로 지정되었다. 이는 MyPrototypeBean 타입의 빈을 요청할 때마다 새로운 인스턴스가 생성됨을 의미한다.

주요 기능

  • 빈의 생명주기 관리: @Scope 어노테이션을 통해 개발자는 빈의 생명주기를 원하는 방식으로 관리할 수 있다.
  • 성능 최적화: 애플리케이션의 특정 부분에서만 필요한 빈의 경우, 적절한 스코프를 설정함으로써 리소스 사용을 최적화할 수 있다.
  • 웹 애플리케이션 컨텍스트: 요청 및 세션 스코프는 웹 애플리케이션에서 사용자의 상태를 관리하는 데 유용하게 사용된다.

 

주의사항

  • 의존성 주입: 프로토타입 스코프의 빈을 싱글톤 빈에 주입할 때는 프로토타입 빈의 새 인스턴스가 매번 생성되지 않는다는 점을 유의해야 한다. 이 경우 스프링의 ObjectFactory나 Provider를 사용하여 해결할 수 있다.
  • 리소스 관리: 요청, 세션 스코프 빈은 해당 HTTP 요청이나 세션이 끝날 때까지만 존재하므로, 이들 스코프의 빈을 사용할 때는 리소스 해제 관련 로직을 적절히 관리해야 한다.

 

@Configuration

해당 클래스가 스프링의 설정 정보를 담고 있음을 나타내며, @Bean 어노테이션을 통해 빈을 정의할 수 있다.
@Configuration 소스코드를 열어보면 @Component 애노테이션이 붙어있다.

 

이 어노테이션은 해당 클래스가 스프링의 애플리케이션 컨텍스트에 대한 빈 정의와 서비스 구성을 포함하고 있음을 나타낸다.

@Configuration으로 선언된 클래스 내부에서 @Bean 어노테이션을 사용한 메소드를 통해 스프링 컨테이너가 관리할 빈 객체들을 정의하고 등록한다.

 

@Configuration
public class AppConfig {
    @Bean
    public MyBean myBean() {
        return new MyBean();
    }
}

위의 예시에서 AppConfig 클래스는 @Configuration 어노테이션으로 마킹되어 있으며, 이는 스프링에게 이 클래스가 애플리케이션의 구성 정보를 담고 있음을 알린다.

@Bean 어노테이션을 사용한 myBean 메소드는 MyBean 타입의 객체를 스프링 컨테이너에 빈으로 등록하도록 한다.

주요 기능

  • 빈 정의: 애플리케이션에서 사용될 빈 객체들을 정의하고, 스프링 컨테이너에 등록한다.
  • 의존성 주입 구성: @Bean 어노테이션을 사용한 메소드를 통해 의존성 주입을 위한 빈 객체들을 생성하고, 이들 사이의 관계를 정의한다.
  • 애플리케이션 설정: 데이터베이스 연결, 프로퍼티 파일 설정, MVC 구성 등 애플리케이션 전반에 걸친 다양한 설정 정보를 제공한다.

 

주의사항

  • 싱글톤 보장: @Configuration으로 마킹된 클래스 내부에서 @Bean 어노테이션으로 정의된 메소드가 반환하는 객체는 기본적으로 싱글톤으로 관리된다. 스프링은 이 메소드들을 특별하게 처리하여, 같은 빈 요청에 대해 항상 같은 인스턴스를 반환한다.
  • CGLIB 프록시: @Configuration 클래스는 스프링에 의해 CGLIB를 사용하여 프록시되어, 빈 메소드 간의 호출이 싱글톤을 보장하도록 처리된다.
  • 구성 클래스의 위치: @ComponentScan 어노테이션과 함께 사용될 때, @Configuration 클래스의 위치가 기준이 되어 스캔 범위를 정의한다.

 

 

@Autowired

의존성 주입을 위해 사용되며, 스프링이 자동으로 의존 객체를 연결해준다.

이 어노테이션은 스프링 컨테이너에 의해 관리되는 빈(bean) 사이의 의존 관계를 자동으로 연결해 준다.

@Autowired는 생성자, 필드, 세터 메서드에 적용할 수 있으며, 스프링은 이 어노테이션이 붙은 대상에 해당하는 타입의 빈을 찾아서 자동으로 주입한다.

 

생성자 주입

@Component
public class MyComponent {
    private MyDependency myDependency;

    @Autowired
    public MyComponent(MyDependency myDependency) {
        this.myDependency = myDependency;
    }
}

생성자 주입은 생성자에 @Autowired를 사용하여 의존 객체를 주입받는 방식이다.

스프링 4.3 이후부터는 생성자가 하나만 있을 경우 @Autowired 어노테이션 생략이 가능하다. 변경 불가능한 의존성을 선호하거나 필수 의존성을 명시적으로 표현하고 싶을 때 권장된다.

 

@Controller

웹 요청을 처리하는 컨트롤러 클래스임을 나타내는 어노테이션. 일반적으로 MVC 패턴의 컨트롤러 역할을 한다.

이 어노테이션이 붙은 클래스는 웹 요청을 처리하는 핸들러로서 작동하며, 스프링 MVC의 디스패처 서블릿(DispatcherServlet)에 의해 관리된다.

@Controller는 사용자의 요청을 받아 처리한 후, 그 결과를 뷰에 전달하는 역할을 담당한다.

@Controller
public class MyController {
    
    @RequestMapping("/greeting")
    public String greeting(Model model) {
        model.addAttribute("message", "Hello, World!");
        return "greeting"; // 뷰 이름 반환
    }
}

 

위 예시에서 MyController 클래스는 @Controller 어노테이션으로 선언되어 있으며, /greeting 경로로 들어오는 요청을 처리하는 메서드를 포함하고 있다.

@RequestMapping 어노테이션은 요청을 해당 메서드에 매핑한다.

메서드는 모델 객체에 메시지를 추가한 후, 뷰 이름을 반환한다. 스프링은 이 뷰 이름을 사용하여 응답을 생성한다.

 

주요 기능

  • 요청 매핑: @RequestMapping 및 HTTP 메서드를 처리하는 @GetMapping, @PostMapping 등의 어노테이션과 함께 사용하여, 특정 URL 요청을 메서드에 매핑한다.
  • 데이터 모델과의 상호작용: 요청 처리 과정에서 생성된 데이터를 뷰에 전달하기 위해 Model 객체를 사용한다.
  • 뷰 선택: 처리 메서드는 뷰의 이름을 문자열 형태로 반환하며, 스프링 MVC는 이를 사용하여 최종 응답 페이지를 생성한다.

 

주의사항

  • 뷰 기술 선택: 스프링 MVC는 JSP, Thymeleaf, FreeMarker 등 다양한 뷰 기술을 지원한다. 컨트롤러가 반환하는 뷰 이름에 해당하는 뷰 템플릿 파일이 존재해야 한다.
  • 응답 바디 직접 반환: @ResponseBody 어노테이션을 메서드에 추가하면, 메서드가 반환하는 값은 뷰를 통해 렌더링되는 것이 아니라, HTTP 응답 본문으로 직접 전송된다. 이는 REST API를 구현할 때 유용하다.
  • REST 컨트롤러: RESTful 웹 서비스를 구현하는 경우, @RestController 어노테이션을 사용할 수 있다. 이는 @Controller와 @ResponseBody를 합친 효과를 가진다.

 

@RestController

컨트롤러 클래스에 붙는 어노테이션이다. 이 어노테이션은 @Controller와 @ResponseBody 어노테이션의 조합으로 볼 수 있으며, 컨트롤러에서 반환하는 데이터가 직접 응답 본문에 쓰여지도록 한다.

즉, 모든 핸들러 메서드에서 @ResponseBody를 붙일 필요 없이 HTTP 응답 본문에 객체를 직접 매핑할 수 있게 해준다.


이 어노테이션을 사용한 클래스는 HTTP 요청을 처리하는 핸들러 메소드를 포함하며, 각 메소드는 특정 HTTP 요청(예: GET, POST, DELETE 등)에 매핑된다.

 

@RestController는 @Controller 와 유사하지만, 차이점은 @RestController 로 지정된 컨트롤러의 메소드가 기본적으로 HTTP 응답 본문(Body)에 직접 데이터를 작성한다는 점이다.이는@ResponseBody 어노테이션을 모든 핸들러 메소드에 적용한 것과 같은 효과를 가진다.



@Controller 는 반환 값이 String 이면 뷰 이름으로 인식된다.
그래서 뷰를 찾고 뷰가 랜더링 된다. @RestController 는 반환 값으로 뷰를 찾는 것이 아니라, HTTP 메시지 바디에 바로 입력한다.

 

@RestController
public class MyRestController {

    @GetMapping("/users")
    public List<User> getAllUsers() {
        // 사용자 목록 반환
    }

    @PostMapping("/users")
    public User createUser(@RequestBody User user) {
        // 새로운 사용자 생성 및 반환
    }
}

이 예시에서, @RestController 어노테이션이 적용된 MyRestController 클래스는 두 개의 메서드를 포함하고 있다.

@GetMapping 어노테이션을 사용한 getAllUsers 메서드는 사용자 목록을 반환하고, @PostMapping 어노테이션을 사용한 createUser 메서드는 요청 본문에서 받은 User 객체를 생성하고 반환한다.

이때, @RequestBody 어노테이션은 요청 본문의 데이터를 User 객체로 변환하는 데 사용된다.

주요 기능

  • 간편한 RESTful API 개발: JSON이나 XML 등의 응답을 생성하는 RESTful 서비스를 쉽게 개발할 수 있다.
  • 응답 본문 직접 매핑: 컨트롤러 메서드가 반환하는 객체는 자동으로 응답 본문으로 변환되어 클라이언트에게 전송된다.
  • @ResponseBody 불필요: 컨트롤러 내의 모든 메서드에 대해 자동으로 @ResponseBody가 적용된다고 볼 수 있다.

 

주의사항

  • 뷰 템플릿 사용 불가: @RestController 어노테이션이 적용된 컨트롤러에서는 뷰 템플릿을 반환할 수 없다. 모든 응답은 데이터(예: JSON, XML) 형태로 직접 클라이언트에게 전송된다.
  • 적절한 HTTP 메시지 변환기 필요: 객체를 JSON이나 XML로 변환하기 위해서는 Jackson 2, JAXB 같은 HTTP 메시지 변환기가 필요하며, 이는 스프링 부트에서 자동으로 구성된다.
  • 예외 처리: REST 컨트롤러에서 발생하는 예외를 적절히 처리하기 위해 @ExceptionHandler 어노테이션을 사용하는 예외 처리 메서드를 정의할 수 있다.

 

 

@Service

비즈니스 로직을 처리하는 서비스 계층의 클래스임을 나타낸다. @Component와 유사하지만, 의미적인 구분을 위해 사용된다.

이 어노테이션은 해당 클래스가 비즈니스 로직을 담당하는 서비스 레이어의 컴포넌트임을 스프링 컨테이너에게 알린다. @Service로 선언된 클래스는 스프링이 자동으로 빈으로 등록하고 관리하게 되며, 이를 통해 다른 컴포넌트들과의 의존성 주입을 쉽게 할 수 있다.

 

@Service
public class MyService {
    public String provideService() {
        return "Service provided";
    }
}

위 예시에서 MyService 클래스는 @Service 어노테이션을 사용하여 서비스 계층의 컴포넌트로 선언되었다.

이 클래스 내에서 비즈니스 로직을 구현하며, 스프링 컨테이너는 이 클래스의 인스턴스를 자동으로 빈으로 등록하고 관리한다.

주요 기능

  • 비즈니스 로직 구현: 애플리케이션의 핵심 비즈니스 로직을 구현하고, 데이터 접근 계층과 프레젠테이션 계층 사이의 조정자 역할을 한다.
  • 트랜잭션 관리: 스프링의 선언적 트랜잭션 관리를 활용하여, 데이터베이스 트랜잭션을 처리한다.
  • 의존성 주입: @Autowired 어노테이션과 같은 메커니즘을 통해, 다른 빈들(예: DAO)과의 의존성을 자동으로 주입받는다.

 

@Repository

데이터 접근 계층(DAO)의 클래스임을 나타내며, 예외 변환 기능을 제공한다.

이 어노테이션은 해당 클래스가 데이터 저장소와의 통신을 담당하는 컴포넌트임을 스프링 컨테이너에게 알린다.

@Repository로 선언된 클래스는 스프링이 자동으로 빈으로 등록하고 관리하며, 데이터 액세스 예외를 스프링의 데이터 접근 예외로 변환하는 기능을 제공한다.

 

@Repository
public class UserRepository {
    // 데이터베이스와의 통신 로직 구현
}

위 예시에서 UserRepository 클래스는 데이터베이스와의 통신을 담당한다.

@Repository 어노테이션은 이 클래스가 데이터 접근 계층의 컴포넌트임을 나타내며, 스프링 컨테이너는 이를 자동으로 인식하여 빈으로 등록한다.

 

주요 기능

  • 데이터 저장소 통신: 데이터베이스, 파일 시스템, 외부 서비스 등 다양한 데이터 저장소와의 통신 로직을 구현한다.
  • 예외 변환: 데이터 접근 계층에서 발생할 수 있는 예외를 스프링의 DataAccessException으로 변환한다. 이는 데이터 접근 기술(예: JDBC, JPA, Hibernate)에 관계없이 일관된 예외 처리를 가능하게 한다.
  • 자동 빈 등록: 스프링의 클래스 패스 스캐닝을 통해 자동으로 빈으로 등록되어 관리된다.

 

@ResponseBody

컨트롤러의 핸들러 메소드가 HTTP 응답 본문(Body)에 직접 내용을 작성할 수 있도록 지정한다.

이 어노테이션은 @Controller 어노테이션이 적용된 클래스 내의 메소드에 사용될 때 유용하며, @RestController 어노테이션과는 달리, @Controller 어노테이션과 함께 사용되어야 한다.

@RestController 어노테이션은 기본적으로 모든 메소드에 @ResponseBody 를 적용한 것과 같은 효과를 제공한다.

 

이 어노테이션을 메서드에 적용하면, 스프링은 해당 메서드의 반환값을 HTTP 응답 데이터로 변환하고, 클라이언트에게 직접 전송한다.

@ResponseBody는 RESTful 웹 서비스를 구현할 때 자주 사용되며, JSON이나 XML과 같은 형식으로 데이터를 클라이언트에 전달하는 데 유용하다.

 

@Controller
public class MyController {

    @GetMapping("/message")
    @ResponseBody
    public String getMessage() {
        return "Hello, World!";
    }
}

위 예시에서 getMessage 메서드는 @ResponseBody 어노테이션이 적용되어 있으며, 이 메서드의 반환값인 "Hello, World!" 문자열이 HTTP 응답의 본문으로 직접 전송된다.

주요 기능

  • 데이터 변환: @ResponseBody 어노테이션을 사용하면, 스프링의 Message Converter 가 작동하여 메서드의 반환 타입을 적절한 형식(JSON, XML 등)으로 변환한다.
  • REST API 구현: RESTful 서비스에서 클라이언트에게 데이터를 JSON이나 XML 형식으로 직접 반환하는 데 필수적이다.
  • 응답 커스터마이징: HTTP 응답 본문을 직접 제어할 수 있어, 응답 데이터의 형식이나 구조를 자유롭게 정의할 수 있다.

 

 

@RequestBody

@RequestBody 어노테이션은 HTTP 요청의 본문(body)을 자바 객체로 매핑할 때 사용된다.

컨트롤러 메서드의 파라미터에 이 어노테이션을 적용함으로써, 클라이언트로부터 받은 JSON, XML 등의 데이터를 자바 객체로 변환하여 사용할 수 있다.

이 과정은 스프링의 HttpMessageConverter 인터페이스에 의해 자동으로 처리된다.

@ResponseBody
public String requestBodyStringV4(@RequestBody String messageBody) {
    log.info("messageBody={}", messageBody);
    return "ok";
}
@ResponseBody
public String requestBodyJsonV3(@RequestBody HelloData data) {
     log.info("username={}, age={}", data.getUsername(), data.getAge());
     return "ok";
}
@ResponseBody
public HelloData requestBodyJsonV5(@RequestBody HelloData data) {
    log.info("username={}, age={}", data.getUsername(), data.getAge());
    return data; //JSON
 }

 

주요 기능

  • 데이터 변환: HTTP 요청 본문에 포함된 데이터를 자바 객체로 변환한다. 이는 주로 JSON이나 XML 형식의 데이터 처리에 사용된다.
  • REST API 구현: RESTful 서비스를 구현할 때 클라이언트로부터 데이터를 받아 처리하는 경우에 필수적으로 사용된다.
  • 유효성 검사: 변환된 객체에 스프링의 유효성 검사(Validation)를 적용할 수 있다. 이를 위해 @Valid 또는 @Validated 어노테이션과 함께 사용될 수 있다.

 

@RequestBody 를 사용하면 HTTP 메시지 바디 정보를 편리하게 조회할 수 있다.
헤더 정보가 필요하다면 HttpEntity 를 사용하거나 @RequestHeader 를 사용하면 된다. 
이렇게 메시지 바디를 직접 조회하는 기능은 요청 파라미터를 조회하는 @RequestParam , @ModelAttribute 와 는 전혀 관계가 없다.

 

@ResponseBody와 @RequestBody

@ResponseBody 어노테이션은 컨트롤러 메소드에서 반환하는 값을 HTTP 응답 본문으로 직접 전달하는 데 사용된다.

이 어노테이션이 붙어 있으면, 메소드의 반환 데이터가 응답 본문에 바로 쓰이게 되며, 스프링의 MessageConverter를 통해 클라이언트가 요구하는 형식(JSON, XML 등)으로 자동으로 변환된다.

이는 주로 RESTful 웹 서비스에서 클라이언트에게 데이터를 직접 반환할 때 활용된다.

 

@RequestBody 어노테이션은 HTTP 요청 본문에서 데이터를 읽어와 메소드의 파라미터로 바인딩할 때 사용된다.

클라이언트가 보낸 요청 본문의 내용을 자바 객체로 변환하여 컨트롤러 메소드의 인자로 전달하게 된다.

이 과정에서도 스프링의 MessageConverter가 자동으로 해당 작업을 수행한다.

@RequestBody는 주로 클라이언트가 서버에 데이터를 전송할 때, 예를 들어 JSON이나 XML 형태의 데이터를 받아 처리할 때 사용된다.

 

간단히 말해, @ResponseBody는 서버에서 클라이언트로 데이터를 보낼 때 사용되며, @RequestBody는 클라이언트에서 서버로 데이터를 전송할 때 사용된다.

 

 

@GetMapping, @PostMapping, @PutMapping, @PatchMapping, @DeleteMapping

HTTP 요청 메서드에 따른 매핑을 지정할 때 사용한다. 각각 GET, POST, PUT, PATCH, DELETE 요청을 처리한다.

Content-Type, consume

HTTP 요청의 Content-Type 헤더를 기반으로 미디어 타입으로 매핑한다. 
만약 맞지 않으면 HTTP 415 상태코드(Unsupported Media Type)을 반환한다.

 

Content-Type은 HTTP 요청 헤더에 포함되어, 클라이언트가 서버로 전송하는 데이터의 미디어 타입을 지정한다. 예를 들어, 클라이언트가 JSON 형식의 데이터를 보낼 경우 Content-Type: application/json으로 설정한다.

consume 속성은 스프링 컨트롤러의 메서드가 처리할 수 있는 요청의 Content-Type을 지정한다. 이 속성을 사용함으로써 특정 메서드가 특정 타입의 데이터만을 처리하도록 제한할 수 있다.

@PostMapping(value = "/mapping-consume", consumes = "application/json")
 public String mappingConsumes() {
     log.info("mappingConsumes");
     return "ok";
}

이 속성을 사용하면 특정 타입의 데이터를 (서버의 입장에서)소비하는 요청만을 해당 컨트롤러 메소드에서 처리하도록 제한할 수 있다.
예를 들어, consumes = "application/json"이라고 지정하면, 해당 컨트롤러 메소드는 Content-Type 헤더가 application/json인 요청만을 처리한다.

 

Accept, produce

HTTP 요청의 Accept 헤더를 기반으로 미디어 타입으로 매핑한다.
만약 맞지 않으면 HTTP 406 상태코드(Not Acceptable)을 반환한다.

 

Accept 헤더는 클라이언트가 서버로부터 받을 수 있는 응답 데이터의 타입을 서버에 알린다. 클라이언트가 JSON 형식의 응답을 기대할 때는 Accept: application/json을 사용한다.

produce 속성은 컨트롤러 메서드가 클라이언트에게 반환할 데이터의 Content-Type을 지정한다. 이를 통해 해당 메서드가 생성하는 응답의 타입을 명시적으로 정의할 수 있다.

@PostMapping(value = "/mapping-produce", produces = "text/html")
public String mappingProduces() {
    log.info("mappingProduces");
    return "ok";
}

이 속성을 통해 클라이언트가 Accept 헤더를 통해 요청한 타입과 일치하는 응답 타입만을 반환하도록 제한할 수 있다.
예를 들어, produces = "text/html"이라고 지정하면, 해당 컨트롤러 메소드는 클라이언트에게 text/html 형식의 데이터를 반환한다.

 

@RequestParam

HTTP 요청 파라미터를 메서드 파라미터로 전달받을 때 사용한다.

int, String 등 기본 타입일 때 사용한다.

@RequestMapping("/request-param-v2")
public String requestParamV2(@RequestParam("username") String memberName, @RequestParam("age") int memberAge){
    log.info("username={}, age={}", memberName, memberAge);
    return "ok";
}

 

HTTP 파라미터 이름이 변수 이름과 같으면 아래와 같이 축약시킬 수 있다.

public String requestParamV3(
                            @RequestParam String username,
                            @RequestParam int age) {
    log.info("username={}, age={}", username, age);
    return "ok";
}

 

required 속성 값을 지정할 수 있는데, 기본값은 true이며, 해당 파라미터가 필수인지 여부를 설정해준다.

false로 바꾸면 해당 파라미터는 필수값이 아니다.

?username= 와 같이 파라미터 이름만 있고 값이 없는 경우 빈문자로 통과된다는 점을 주의해야 한다.

public String requestParamRequired(
        @RequestParam(required = true) String username,
        @RequestParam(required = false) Integer age) {
        
    log.info("username={}, age={}", username, age);
    return "ok";
}

 

파라미터에 값이 없는 경우 defaultValue 를 사용하면 기본 값을 적용할 수 있다.

이를 적용하면 이미 기본 값이 있기 때문에 required=true 는 의미가 없다.

defaultValue 파라미터 이름만 있고 값이 없는 경우에도 설정한 기본 값이 적용된다.

public String requestParamDefault(
        @RequestParam(required = true, defaultValue = "guest") String username,
        @RequestParam(required = false, defaultValue = "-1") int age) {
        
    log.info("username={}, age={}", username, age);
    return "ok";
}

 

@ModelAttribute

스프링 MVC에서 컨트롤러 메서드의 파라미터가 모델 객체에 자동으로 바인딩되도록 하거나, 모델 객체를 뷰에 전달하기 위해 사용된다. 

 

HTTP 요청 파라미터를 @ModelAttribute 어노테이션이 붙은 메서드 파라미터나 모델 객체의 필드와 자동으로 매칭하여 바인딩한다.

이때, 폼에서 전송된 데이터의 이름과 객체의 필드 이름이 일치해야 한다.

예를 들어, 사용자가 입력 폼에서 name, email 등의 필드를 채워서 전송하면, 스프링 MVC는 이 정보를 User 객체의 name, email 필드에 매핑하여 저장한다.

 

String, int , Integer 같은 단순 타입 = @RequestParam

나머지 = @ModelAttribute (argument resolver 로 지정해둔 타입 외)

 

이 어노테이션은 두 가지 주요 용도로 활용된다

  • 메서드 파라미터에 사용: 컨트롤러 메서드의 파라미터에 @ModelAttribute를 사용하면, HTTP 요청 파라미터가 해당 파라미터의 객체에 자동으로 바인딩된다. 이는 주로 폼 데이터를 받아 모델 객체를 생성하거나 업데이트할 때 사용된다.
  • 메서드 레벨에 사용: @ModelAttribute 어노테이션을 메서드 레벨에 사용하면, 해당 메서드가 반환하는 객체가 모델에 추가된다. 이 객체는 뷰에서 사용할 수 있으며, 모든 요청에 대해 모델 데이터를 사전에 설정하는 데 유용하다.

 

메서드 파라미터에 사용하는 예시

import lombok.Data;
@Data
public class HelloData {
    private String username;
    private int age;
}

 

@ModelAttribute 를 사용하면 HelloData 객체가 생성되고, 요청 파라미터의 값도 모두 들어가진다.

public String modelAttributeV1(@ModelAttribute HelloData helloData) {
    log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
    return "ok";
}

스프링MVC는 @ModelAttribute 가 있으면 다음을 실행한다.

-객체를 생성한다.
-요청 파라미터의 이름으로 객체의 프로퍼티를 찾는다.
-해당 프로퍼티의 setter를 호출해서 파라미터의 값을 입력(바인딩) 한다.

 

@PostMapping("/add")
public String addItemV2(@ModelAttribute("item") Item item, Model model) { // ->  모델(Model)에 @ModelAttribute 로 지정한 객체를 자동으로 넣어준다.
    model.addAttribute("item", item); //자동 추가, 생략 가능
    return "basic/item";
}
  • 데이터 바인딩: 사용자가 폼을 통해 전송한 데이터는 Item 객체의 필드와 자동으로 매칭되어 바인딩된다. 이때 폼 필드의 이름과 Item 클래스의 필드 이름이 일치해야 한다.

  • 모델에 객체 추가: @ModelAttribute("item") 어노테이션은 Item 객체를 "item"이라는 이름으로 모델에 자동으로 추가하도록 지시한다. 따라서, model.addAttribute("item", item); 코드는 스프링이 자동으로 처리하기 때문에 생략이 가능하다.

  • 뷰 선택 및 렌더링: 메서드가 반환하는 "basic/item" 문자열은 뷰의 이름이다. 스프링 MVC는 이 정보를 바탕으로 적절한 뷰 템플릿을 찾고, 모델에 추가된 데이터를 사용하여 HTML 페이지를 생성한다.

@PostMapping("/add")
public String addItemV2(@ModelAttribute Item item, Model model) {
    return "basic/item";
}
  • Model model 파라미터를 명시적으로 사용하지 않아도, @ModelAttribute로 지정된 Item 객체는 뷰로 전달될 모델에 자동으로 추가된다.
  • @ModelAttribute 의 이름을 생략하면 모델에 저장될 때 클래스명을 사용한다. 이때 클래스의 첫글자만 소문자로 변경해서 등록한다.

@ModelAttribute 클래스명 모델에 자동 추가되는 이름

  • Item -> item
  • HelloWorld -> helloWorld

 

@PostMapping("/add")
public String addItemV2(Item item) {
    return "basic/item";
}

최종적으로는 @ModelAttribute도 생략이 가능하다.

이때도 모델에 저장될 때 클래스명을 사용한다. 클래스의 첫글자만 소문자로 변경해서 등록한다.

 

메서드 레벨에 사용하는 예시

@ModelAttribute("regions")
 public Map<String, String> regions() {
    Map<String, String> regions = new LinkedHashMap<>(); regions.put("SEOUL", "서울");
    regions.put("BUSAN", "부산");
    regions.put("JEJU", "제주");
	return regions;
}
@ModelAttribute("allUsers")
public List<User> populateUsers() {
    return userService.findAllUsers();
}

@ModelAttribute 를 컨트롤러 클래스에서 별도의 메서드에 붙이면 해당 컨트롤러를 요청할 때 populateUsers() 에서 반환한 값이 자동으로 "allUsers"라는 이름으로 모델에 담기게 된다.

 

주요 기능

  • 자동 데이터 바인딩: 폼 데이터와 같은 HTTP 요청 파라미터를 자동으로 자바 객체에 매핑한다.
  • 모델 데이터 추가: 컨트롤러 메서드가 반환하는 객체를 모델에 추가하여 뷰에서 사용할 수 있게 한다.

 

주의사항

  • 바인딩 에러 처리: @ModelAttribute로 데이터 바인딩 시, 바인딩 에러가 발생할 수 있다. 이를 처리하기 위해 BindingResult 파라미터를 추가할 수 있다.
  • 명시적 모델 속성 이름: @ModelAttribute 어노테이션에 속성 이름을 명시적으로 지정하지 않으면, 파라미터 타입의 이름을 기반으로 한 기본 이름이 사용된다. 때로는 이 이름을 직접 지정하는 것이 더 명확할 수 있다.

 

@RequestParam과 @ModelAttribute

@RequestParam과 @ModelAttribute 어노테이션은 스프링 MVC에서 컨트롤러 메서드의 파라미터를 HTTP 요청과 매핑할 때 사용되며, 각각의 용도와 사용 방식이 다르다.

 

@RequestParam

@RequestParam은 HTTP 요청 파라미터를 컨트롤러 메서드의 파라미터에 바인딩할 때 사용된다. 주로 쿼리 파라미터나 폼 데이터에서 단일 값을 가져올 때 활용된다. 이 어노테이션은 필수 여부, 기본값 등을 설정할 수 있다.

@ModelAttribute

@ModelAttribute는 요청 파라미터를 객체에 바인딩하는 데 사용된다. 폼 데이터와 같이 여러 필드를 가진 복잡한 객체를 채울 때 주로 사용된다. 이 어노테이션은 요청 파라미터를 객체의 필드와 자동으로 매핑하여 객체를 생성하거나 업데이트한다.

주요 차이점

  • 용도: @RequestParam은 주로 단일 파라미터 값을 처리할 때 사용되며, @ModelAttribute는 여러 필드를 가진 객체를 매핑할 때 사용된다.
  • 복잡성: @RequestParam은 단순한 값이나 배열 등을 처리하는 데 적합하고, @ModelAttribute는 복잡한 객체를 다루는 데 적합하다.
  • 사용 시나리오: @RequestParam은 단일 파라미터의 처리, 선택적 파라미터, 기본값 지정 등에 유용하며, @ModelAttribute는 폼 제출과 같이 여러 데이터가 하나의 객체로 매핑되어야 할 때 유용하다.

 

@PathVariable

URL 경로에 있는 변수 값을 메서드 파라미터로 전달받을 때 사용한다.

@GetMapping("/mapping/{userId}")
 public String mappingPath(@PathVariable("userId") String data) {
     log.info("mappingPath userId={}", data);
     return "ok";
 }

/mapping/userA 이런 식으로 리소스 경로에 식별자를 넣는 스타일에서 사용하는 방식이다.

@RequestMapping 은 URL 경로를 템플릿화 할 수 있는데, @PathVariable 을 사용하면 매칭 되는 부분을 편리하게 조회할 수 있다.



@PathVariable 의 이름과 파라미터 이름이 같으면 아래와 같이 생략할 수 있다.

@GetMapping("/mapping/{userId}")
 public String mappingPath(@PathVariable String userId) {
     log.info("mappingPath userId={}", userId);
     return "ok";
 }

 

또한 PathVariable은 다중으로 사용이 가능하다.

@GetMapping("/mapping/users/{userId}/orders/{orderId}")
public String mappingPath(@PathVariable String userId, @PathVariable Long orderId) {
    log.info("mappingPath userId={}, orderId={}", userId, orderId);
    return "ok";
}

 

 

@CookieValues

@CookieValue(value = "myCookie", required = false) String cookie​
  • value: 추출하고자 하는 쿠키의 이름을 지정한다.
  • required: 이 속성은 쿠키의 존재 여부가 필수인지 아닌지를 지정한다. 기본값은 true이며, 이 경우 지정한 이름의 쿠키가 요청에 없으면 예외가 발생한다. false로 설정하면, 지정한 이름의 쿠키가 없어도 예외가 발생하지 않으며, 대신 파라미터 값은 null이 된다.

 

@RequestHeader

@RequestHeader 어노테이션은 스프링 MVC에서 컨트롤러 메서드의 파라미터가 HTTP 요청 헤더를 매핑할 때 사용된다.

이 어노테이션을 사용하면 클라이언트가 보낸 HTTP 요청의 헤더 정보를 메서드의 파라미터로 직접 받을 수 있다.

@RequestHeader는 주로 세션 정보, 토큰, 요청의 메타데이터 등을 처리할 때 유용하다.

 

@GetMapping("/headerInfo")
public String getHeaderInfo(@RequestHeader("host") String host) {
    return "Host : " + host;
}

 

위 예시는 클라이언트로부터 받은 요청 중 "host" 헤더의 값을 String 타입의 host 변수에 저장한다.

 

주요 기능

  • 헤더 값 추출: 특정 HTTP 요청 헤더의 값을 메서드 파라미터로 직접 받아 처리할 수 있다.
  • 필수 및 선택적 헤더 처리: required 속성을 통해 해당 헤더가 필수인지 선택적인지 지정할 수 있다. 기본값은 true로, 헤더가 반드시 존재해야 한다. false로 설정할 경우 헤더가 없어도 에러가 발생하지 않는다.
required: 이 속성은 해당 헤더가 반드시 필요한지를 지정한다. 기본값은 true로, 헤더가 요청에 없을 경우 예외가 발생한다. required 속성을 false로 설정하면, 헤더가 없어도 예외가 발생하지 않고, 파라미터 값은 null 또는 defaultValue로 설정된 값이 된다.

defaultValue: 헤더가 없을 때 사용할 기본값을 지정할 수 있다. required가 false로 설정되어 있고, 요청에 특정 헤더가 없을 경우 defaultValue로 지정된 값이 변수에 할당된다.

 

  • 기본값 설정: defaultValue 속성을 사용하여 헤더가 없을 경우 사용할 기본값을 지정할 수 있다.

 

주의사항

  • 다양한 헤더 타입 지원: @RequestHeader는 String, int, long 등 기본 자료형 뿐만 아니라 Date, Locale 등 특정 타입으로도 자동 변환을 지원한다.
  • 필수 헤더 누락 처리: 필수 헤더(required=true)가 요청에 포함되지 않은 경우, 스프링 MVC는 MissingRequestHeaderException을 발생시킨다. 이는 적절한 예외 처리 로직을 통해 관리해야 한다.
  • 리스트나 맵으로의 바인딩: 특정 헤더 값이 여러 개일 경우, List<String>이나 Map<String, String>과 같은 컬렉션 타입으로도 바인딩할 수 있다. 이를 통해 같은 이름을 가진 여러 헤더 값을 효과적으로 처리할 수 있다.

 

@Transactional

선언적 트랜잭션 관리를 위한 어노테이션이다.

이 어노테이션을 사용하면, 개발자는 메소드 레벨이나 클래스 레벨에서 트랜잭션의 경계를 선언적으로 지정할 수 있다.

스프링은 @Transactional이 적용된 메소드를 호출할 때 자동으로 트랜잭션을 시작하고, 메소드의 실행이 성공적으로 완료되면 트랜잭션을 커밋하며, 실행 중 예외가 발생하면 트랜잭션을 롤백한다.

@Transactional을 통해 스프링에서 제공하는 선언적 트랜잭션 관리를 효과적으로 활용할 수 있다. 이를 통해 애플리케이션의 데이터 일관성을 보장하고, 개발자가 복잡한 트랜잭션 관리 로직을 직접 작성하지 않아도 되는 편리함을 제공한다.

 

주요 기능 및 특징

  • 트랜잭션의 자동 시작과 종료: @Transactional 어노테이션을 사용하면 트랜잭션이 자동으로 시작되고, 메소드 실행이 완료될 때 자동으로 커밋 또는 롤백된다.
  • 트랜잭션 전파 설정: 트랜잭션의 전파 방식을 설정할 수 있다. 예를 들어, 이미 진행 중인 트랜잭션이 있을 때 새로운 트랜잭션을 시작할 것인지, 기존 트랜잭션에 참여할 것인지 등을 지정할 수 있다.
  • 롤백 규칙 설정: 특정 예외가 발생했을 때 롤백을 수행할지 여부를 설정할 수 있다. 기본적으로 런타임 예외(RuntimeException)와 에러(Error)가 발생하면 롤백된다.
  • 읽기 전용 설정: 트랜잭션을 읽기 전용으로 설정할 수 있다. 이는 트랜잭션 내에서 데이터를 변경하지 않고 오직 조회만 수행될 때 최적화를 위해 사용된다.
  • 시간 제한 설정: 트랜잭션의 최대 수행 시간을 설정할 수 있다. 지정된 시간을 초과하면 트랜잭션이 자동으로 롤백된다.

 

클래스 레벨에 @Transactional을 붙일 때

해당 클래스의 모든 public 메소드 호출에 트랜잭션 관리가 적용된다.
클래스 레벨에서 @Transactional을 선언하면, 해당 클래스 내의 각 메소드는 별도의 @Transactional 선언이 없어도 기본적으로 선언된 트랜잭션 정책을 상속받는다.
클래스 전체에 걸쳐 일관된 트랜잭션 정책을 적용하고 싶을 때 유용하다.
특정 메소드에만 다른 트랜잭션 설정을 적용하고 싶다면, 해당 메소드에 별도의 @Transactional 어노테이션을 추가하여 클래스 레벨에서의 설정을 오버라이드할 수 있다.

 

메소드 레벨에 @Transactional을 붙일 때

특정 메소드 호출 시에만 트랜잭션 관리가 적용된다.
메소드 레벨에서의 선언을 통해 더 세밀한 트랜잭션 관리 정책을 구현할 수 있다. 예를 들어, 다른 트랜잭션 전파 옵션을 사용하거나, 특정 메소드에만 롤백 규칙을 다르게 적용할 수 있다.
클래스 내의 특정 메소드에만 트랜잭션을 적용하고 싶을 때, 또는 클래스 내 메소드 간에 다른 트랜잭션 설정이 필요할 때 적합하다.

 

클래스 레벨에 @Transactional을 선언하고, 특정 메소드에도 @Transactional을 선언하면 메소드 레벨의 어노테이션이 우선 적용된다. 즉, 메소드 레벨에서 세부적으로 트랜잭션 정책을 설정할 수 있다.

 

주의 사항

  • @Transactional 어노테이션이 적용된 메소드는 프록시를 통해 호출될 때만 트랜잭션 관리가 적용된다. 즉, 같은 클래스 내의 메소드에서 @Transactional 메소드를 직접 호출할 경우 트랜잭션 관리가 적용되지 않을 수 있다.
  • @Transactional 어노테이션을 사용할 때는 트랜잭션 관리자(예: PlatformTransactionManager 구현체)가 스프링 빈으로 등록되어 있어야 한다.

 

@SpringBootTest

스프링 부트 기반의 애플리케이션 테스트를 위해 사용되며, 전체 스프링 애플리케이션 컨텍스트를 로드하여 통합 테스트(Integration Test)를 수행할 수 있게 해준다. 이 어노테이션은 스프링 부트의 테스트 지원 기능의 일부로, 테스트 시 애플리케이션을 구성하는 모든 빈(Bean)들과 설정을 포함한 실행 환경을 제공한다.

또한 @Autowired 등을 통해 스프링 컨테이너가 관리하는 빈들을 사용할 수 있다.

주요 특징

  • 전체 애플리케이션 컨텍스트 로드: @SpringBootTest 어노테이션을 사용한 테스트는 애플리케이션의 전체 스프링 컨텍스트를 로드한다. 이는 애플리케이션의 모든 부분이 실제와 같은 방식으로 작동하는 환경에서 테스트를 실행하고 싶을 때 유용하다.
  • 플렉서블한 테스트 환경 설정: @SpringBootTest는 다양한 속성을 제공하여 테스트 실행 환경을 세밀하게 조정할 수 있다. 예를 들어, 특정 프로파일을 활성화하거나 애플리케이션의 구성 속성을 변경할 수 있다.
  • 웹 환경 통합 테스트 지원: webEnvironment 속성을 통해 웹 애플리케이션의 테스트 환경을 설정할 수 있다. 예를 들어, 실제 서블릿 컨테이너를 실행하거나(Mock Servlet 환경), 완전히 모의된 웹 환경에서 테스트를 실행할 수 있다.

 

주의 사항

  • @SpringBootTest를 사용하는 테스트는 전체 스프링 애플리케이션 컨텍스트를 로드하므로 실행 시간이 길어질 수 있다. 따라서 모든 테스트에 @SpringBootTest를 사용하기보다는 필요한 경우에만 사용하는 것이 좋다.
  • 단위 테스트(Unit Test)와 통합 테스트(Integration Test)를 적절히 분리하여 테스트의 목적에 맞게 적용하는 것이 중요하다. 단위 테스트는 가능한 한 격리된 환경에서 빠르게 실행되어야 하며, 통합 테스트는 애플리케이션의 여러 부분이 함께 잘 작동하는지 검증하는 데 초점을 맞춰야 한다.

 

@TestConfiguration

스프링 부트에서 테스트 시 사용할 추가적인 설정을 정의할 때 사용된다.

이 어노테이션으로 표시된 클래스는 일반적인 @Configuration 클래스와 유사하지만, 주로 테스트 컨텍스트에서만 사용되는 특정 빈(bean) 설정을 제공하기 위해 사용된다.

@TestConfiguration을 사용하면 테스트 실행 시 필요한 컴포넌트나 설정을 별도로 정의하여, 테스트의 격리성과 명확성을 높일 수 있다.

주요 특징

  • 테스트 전용 설정 제공: @TestConfiguration은 테스트 실행 시에만 적용되는 특별한 구성을 제공한다. 이를 통해 테스트에서 필요한 빈의 정의나 오버라이딩을 할 수 있다.
  • 테스트 범위의 확장성: 일반적인 @Configuration과는 달리, @TestConfiguration으로 정의된 설정은 주로 테스트 범위 내에서만 적용된다. 이는 테스트 실행 시 테스트 환경을 보다 세밀하게 제어할 수 있게 해준다.
  • 테스트 의존성 주입 가능: @TestConfiguration에서 정의된 빈은 테스트 클래스에서 @Autowired를 사용하여 주입받을 수 있다. 이는 테스트에 필요한 모의 객체(mock)나 테스트 전용 서비스 구현체 등을 쉽게 주입할 수 있도록 해준다.

 

@TestConfiguration을 사용하는 방법은 크게 두 가지가 있다.

내부 클래스로 정의하기: 테스트 클래스 내부에 @TestConfiguration을 사용하여 내부 클래스로 정의할 수 있다. 이 방법은 해당 테스트 클래스에서만 사용될 설정을 정의할 때 유용하다.

@SpringBootTest
public class MyServiceTest {

    @TestConfiguration
    static class MyServiceTestConfiguration {
        @Bean
        public MyService myService() {
            return new MyServiceTestImpl();
        }
    }

    @Autowired
    private MyService myService;

    // 테스트 로직
}

 

별도의 클래스로 정의하기: 테스트 전용 설정을 별도의 클래스로 정의하고, @Import 어노테이션을 사용하여 테스트 클래스에 적용할 수 있다. 여러 테스트에서 공통적으로 사용할 설정이 있을 때 적합한 방법이다.

@TestConfiguration
public class MyServiceTestConfiguration {
    @Bean
    public MyService myService() {
        return new MyServiceTestImpl();
    }
}

@SpringBootTest
@Import(MyServiceTestConfiguration.class)
public class MyServiceTest {
    // 테스트 로직
}

 

주의 사항

  • @TestConfiguration으로 정의된 빈은 기본적으로 테스트 컨텍스트에만 추가된다. 따라서 테스트 중에만 사용되며, 애플리케이션의 주 실행 컨텍스트에는 포함되지 않는다.
  • 테스트가 다른 테스트에 영향을 주지 않도록, 테스트 전용 설정을 사용할 때는 격리성을 유지하는 것이 중요하다.

 

@BeforeEach

@BeforeEach 어노테이션은 JUnit 5에서 제공하는 테스트 라이프사이클 어노테이션 중 하나이다.

이 어노테이션이 붙은 메소드는 테스트 클래스 내의 각 테스트 메소드가 실행되기 전에 매번 호출된다.

@BeforeEach를 사용하면 테스트 실행 전에 필요한 사전 준비 작업을 자동으로 처리할 수 있어, 테스트 코드의 중복을 줄이고, 테스트의 독립성을 보장하는 데 도움이 된다.

 

사용 목적

  • 테스트 환경 초기화: 테스트 실행 전에 특정 상태로 환경을 초기화해야 하는 경우, 예를 들어 데이터베이스 연결을 초기화하거나 테스트 데이터를 준비하는 등의 작업을 수행한다.
  • 테스트 데이터 설정: 테스트 실행에 필요한 입력값이나 모의 객체(mock objects)를 설정한다.
    공통 자원 할당: 여러 테스트에서 공통으로 사용될 객체나 자원을 생성하고 할당한다.

 

주의사항

  • @BeforeEach 메소드는 테스트 메소드 실행 전에 매번 호출되므로, 테스트 간의 상태 간섭을 방지할 수 있다. 하지만, 테스트 각각에 대해 독립적으로 실행되어야 하는 초기화 코드만 포함시켜야 한다.
  • 테스트 메소드 실행 전에만 수행되어야 할 작업이 아니라면, @BeforeEach가 아닌 다른 어노테이션을 고려해야 한다. 예를 들어, 모든 테스트 메소드 실행 후에 공통적으로 수행되어야 하는 작업은 @AfterEach를 사용해야 한다.

 

@AfterEach

@AfterEach 어노테이션은 JUnit 5에서 제공하는 테스트 라이프사이클 어노테이션 중 하나로, 각 테스트 메소드가 실행된 후에 매번 호출되는 메소드를 정의할 때 사용된다.

이를 통해 테스트 실행 후에 필요한 정리 작업을 자동으로 수행할 수 있어, 테스트 간의 독립성을 보장하고, 다음 테스트 실행에 영향을 주지 않는 깨끗한 상태를 유지할 수 있다.

 

사용 목적

  • 리소스 해제: 테스트 실행 중에 할당된 리소스(예: 파일 핸들, 데이터베이스 연결 등)를 해제하거나 닫는 작업을 수행한다.
  • 테스트 환경 정리: 테스트 실행으로 인해 변경된 외부 상태나 데이터베이스를 원래 상태로 복구한다.
  • 테스트 후 검증: 테스트 실행 후 일부 후처리 검증 작업을 수행할 수도 있다.

 

주의사항

  • @AfterEach 메소드는 테스트 메소드 실행 후에 항상 실행되므로, 테스트 간의 상태 간섭을 방지하는 데 중요한 역할을 한다. 하지만, 모든 테스트 후에 반드시 수행해야 할 작업만 이곳에 포함시켜야 한다.
  • @AfterEach 메소드 내에서 발생하는 예외는 테스트의 실패 원인으로 간주될 수 있다. 따라서 리소스를 해제하는 등의 작업을 수행할 때는 예외 처리에 주의해야 한다.
  • 테스트 클래스에 여러 @AfterEach 메소드가 있다면, 실행 순서를 보장하지 않는다. 필요한 경우 하나의 @AfterEach 메소드에서 모든 정리 작업을 순차적으로 호출하는 방법을 고려할 수 있다.

 

@Nullable

@Nullable 어노테이션은 Java나 Spring Framework 같은 프로그래밍 환경에서 사용되며, 어떤 필드, 메소드의 반환값, 또는 메소드의 매개변수가 null이 될 수 있음을 명시적으로 표시하는 데 사용된다. 이는 개발자들 사이의 명확한 의사소통을 촉진하고, 가능한 NullPointerException을 방지하는 데 도움을 준다.

 

메소드 반환값에 사용

메소드가 null을 반환할 수 있음을 명시적으로 표시하고 싶을 때, 반환 타입 앞에 @Nullable을 붙일 수 있다. 이는 해당 메소드를 사용하는 개발자에게 반환값을 처리할 때 null 검사를 수행해야 할 수도 있음을 알려준다.

@Nullable
public String findUsernameById(Long id) {
    // 사용자 ID로 사용자 이름 조회
    // 해당 ID의 사용자가 없는 경우 null 반환
}

 

메소드 매개변수에 사용

메소드의 매개변수가 null이 될 수 있음을 명시하고 싶을 때, 매개변수 선언 앞에 @Nullable을 붙일 수 있다. 이는 메소드 내부에서 해당 매개변수를 사용할 때 null 상태를 고려해야 함을 의미한다.

public void printUserInfo(@Nullable User user) {
    if (user != null) {
        // user 객체 사용
    } else {
        // user 객체가 null일 경우의 처리
    }
}

 

필드에 사용

클래스의 필드가 null값을 가질 수 있음을 명시하고 싶을 때, 필드 선언 앞에 @Nullable을 사용할 수 있다. 이는 해당 필드를 사용하는 곳에서 null 검사를 수행할 필요가 있음을 알린다.

public class UserInfo {
    @Nullable
    private String nickname;
    // nickname 필드는 null일 수 있음
}

@Nullable 어노테이션의 사용은 코드의 가독성을 향상시키고, 개발자가 의도치 않은 null 처리로 인한 오류를 줄이는 데 도움을 준다. 하지만, @Nullable을 사용할 때는 해당 값이 null일 경우에 대한 적절한 처리 로직을 구현하는 것이 중요하다.

 

@CookieValue

HTTP 요청에 포함된 쿠키 값을 메소드 파라미터로 바인딩하는 데 사용된다.

이 어노테이션을 사용함으로써, 개발자는 쿠키에서 데이터를 쉽게 추출하고 해당 데이터를 컨트롤러 메소드 내에서 사용할 수 있다. @CookieValue는 주로 사용자 세션 관리, 광고 추적, 사용자 설정 저장 등의 목적으로 쿠키를 사용할 때 유용하다.

 

기본 사용법

@CookieValue 어노테이션은 컨트롤러 메소드의 파라미터 앞에 선언한다.

쿠키 이름을 어노테이션의 값으로 지정하여, 해당 쿠키의 값을 메소드 파라미터에 자동으로 바인딩할 수 있다.

@GetMapping("/someEndpoint")
public String handleRequest(@CookieValue("cookieName") String cookieValue) {
    // 여기서 cookieValue 파라미터는 "cookieName"이라는 이름의 쿠키 값으로 초기화된다.
    return "쿠키 값은: " + cookieValue;
}

 

선택적 속성

@CookieValue 어노테이션은 몇 가지 선택적 속성을 제공한다

 

  • value (또는 name): 바인딩할 쿠키의 이름을 지정한다.
  • required: 이 속성이 true로 설정되어 있고 지정된 이름의 쿠키가 존재하지 않을 경우, 예외를 발생시킨다. 기본값은 true이지만, false로 설정하면 쿠키가 없는 경우 메소드 파라미터를 null 또는 Optional.empty()로 처리할 수 있다.
  • defaultValue: 지정된 쿠키가 존재하지 않을 경우 사용할 기본값을 지정한다. 이 속성을 사용하면 required 속성이 자동으로 false가 된다.
@GetMapping("/example")
public String handleRequestWithDefault(@CookieValue(value = "cookieName", defaultValue = "defaultCookieValue") String cookieValue) {
    // "cookieName" 쿠키가 없는 경우, "defaultCookieValue"가 파라미터 값으로 사용된다.
    return "쿠키 값은: " + cookieValue;
}

 

주의 사항

@CookieValue를 사용할 때는 클라이언트 측에서 쿠키가 활성화되어 있고, 적절하게 설정되어 있어야 한다는 점을 고려해야 한다.

또한, 쿠키는 사용자의 브라우저에 저장되므로 민감한 정보를 쿠키에 저장하는 것은 피해야 한다.

필요한 경우 쿠키 값을 암호화하여 보안을 강화할 수 있다.

 

 

@SessionAttribute

스프링은 세션을 더 편리하게 사용할 수 있도록 @SessionAttribute 을 지원한다.
이미 로그인 된 사용자를 찾을 때는 다음과 같이 사용하면 된다.

@SessionAttribute(name = "loginMember", required = false) Member loginMember

이 기능은 세션을 생성하지 않는다.

 

이 어노테이션을 사용하면, 세션에서 직접 속성을 가져오거나 세션에 속성을 추가하는 복잡한 작업 없이, 세션에 저장된 데이터를 쉽게 사용할 수 있다.

 

@SessionAttribute는 주로 컨트롤러 클래스 레벨에 선언되며, 이 어노테이션으로 지정된 속성은 해당 컨트롤러의 모든 핸들러 메소드에서 접근할 수 있게 된다. 또한, 메소드 파라미터 레벨에서 사용될 수도 있어, 특정 핸들러 메소드에서만 세션 속성을 사용하고 싶을 때 유용하다.

 

클래스 레벨에서의 사용 예

@Controller
@SessionAttributes("user")
public class MyController {

    @ModelAttribute("user")
    public User createUserModel() {
        return new User();
    }

    @GetMapping("/user/profile")
    public String userProfile(Model model) {
        // "user" 세션 속성 사용 가능
        User user = (User) model.getAttribute("user");
        return "userProfile";
    }
}

 

메소드 파라미터 레벨에서의 사용 예

@GetMapping("/user/profile")
public String getUserProfile(@SessionAttribute("user") User user, Model model) {
    // 메소드 파라미터로 "user" 세션 속성 직접 접근
    return "userProfile";
}

 

@SessionAttribute로 지정된 세션 속성은 컨트롤러 내부에서 모델에 자동으로 추가되므로, 뷰 템플릿에서 해당 데이터를 사용할 수 있다.

 

이 어노테이션은 주로 읽기 전용 시나리오에서 사용될 것을 권장한다. 

 

세션 데이터를 변경하고 싶다면, 직접 HttpSession을 사용하는 것이 좋다.

 

@SessionAttributes와 혼동하지 말아야 한다. @SessionAttributes는 모델 속성을 세션에 저장하기 위해 사용되며, 주로 폼 제출 과정에서 사용자의 입력 데이터를 임시로 저장하는 데 쓰인다. 

 

반면, @SessionAttribute는 이미 세션에 저장된 속성에 접근하는 데 사용된다.

 

세션에서 직접 관리해야 하는 데이터가 아니라면, 가능한 한 세션 사용을 최소화하는 것이 좋다. 세션 데이터는 서버의 메모리를 사용하기 때문에, 과도한 사용은 애플리케이션의 성능에 부정적인 영향을 줄 수 있다.

 

TrackingModes

세션을 이용해 로그인을 하게되면 URL이 다음과 같이 jsessionid 를 포함하고 있는 것을 확인할 수 있다.

-http://localhost:8080/;jsessionid=F59911518B921DF62D09F0DF8F83F872

jsessionid가 노출되지 않도록 하기 위해서는 application.properties에 아래의 코드를 넣어주면 된다.

server.servlet.session.tracking-modes=cookie

 

@ControllerAdvice와 @RestControllerAdvice

Spring MVC에서 공통의 처리 로직(예외 처리, 데이터 바인딩, 모델 속성 추가 등)을 애플리케이션의 모든 컨트롤러 또는 REST 컨트롤러에 적용하기 위해 사용된다. 이 두 어노테이션은 기능적으로 유사하지만, 사용되는 컨텍스트에 따라 선택할 수 있다.

 

@ControllerAdvice는 예외 발생 시 에러 페이지로 이동하거나 특정 뷰를 반환하는 반면, @RestControllerAdvice는 예외 정보를 담은 JSON 객체를 HTTP 응답으로 반환한다. 따라서 개발하는 애플리케이션의 유형(웹 애플리케이션 vs RESTful API)에 맞추어 적절한 어노테이션을 선택하여 사용해야 한다.

 

 

@ControllerAdvice

@ControllerAdvice는 모든 @Controller에 대한 전역 설정을 제공한다. 이는 주로 웹 애플리케이션에서 HTML 뷰를 반환하는 컨트롤러에 적합하다.

 

예외 처리(@ExceptionHandler), 모델 속성 추가(@ModelAttribute), 바인딩 설정(@InitBinder)과 같은 공통 로직을 정의할 때 사용된다.
예외가 발생했을 때, 에러 페이지로 리다이렉트하거나, 특정 뷰를 반환하는 등의 처리를 할 때 적합하다.

 

@ControllerAdvice 에 대상을 지정하지 않으면 모든 컨트롤러에 적용된다. (글로벌 적용)

 

@ControllerAdvice 사용 예

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(value = Exception.class)
    public ModelAndView handleException(Exception ex) {
        ModelAndView modelAndView = new ModelAndView("errorPage");
        modelAndView.addObject("message", ex.getMessage());
        return modelAndView;
    }
}

 

 

@RestControllerAdvice

@RestControllerAdvice는 @ControllerAdvice에 추가적으로 @ResponseBody 어노테이션의 기능이 포함되어 있다. 즉, 이 어노테이션을 사용하면 반환되는 객체는 자동으로 JSON이나 XML과 같은 응답 본문으로 변환된다.

 

RESTful 웹 서비스를 개발할 때, JSON이나 XML로 데이터를 반환하는 @RestController에 대한 전역 설정을 제공하는 데 사용된다.
예외 처리를 통해 클라이언트에게 JSON 형식의 에러 응답을 보낼 때 유용하다.

 

 

@RestControllerAdvice 사용 예

@RestControllerAdvice
public class GlobalRestExceptionHandler {
    @ExceptionHandler(value = Exception.class)
    public ResponseEntity<Object> handleException(Exception ex) {
        Map<String, Object> body = new HashMap<>();
        body.put("message", "An error occurred");
        return new ResponseEntity<>(body, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

 

 

@Value

@Value는 스프링 프레임워크에서 사용되는 어노테이션으로, 필드, 메소드 매개변수, 또는 메소드(주로 설정 메소드)에 적용되어 값을 주입(inject)하는 데 사용된다. 이 어노테이션을 통해 리터럴 값, 표현식 결과, 또는 프로퍼티 파일과 같은 외부 설정 파일에 정의된 값을 주입받을 수 있다.

 

기본 사용법

리터럴 값 주입

@Value("Spring")
private String name;

이 경우, name 필드에 문자열 "Spring"이 주입된다.

 

프로퍼티 파일의 값 주입

application.properties 파일에 정의된 값을 사용하려면 ${property.name} 구문을 사용한다.

app.name=SpringApp
@Value("${app.name}")
private String appName;

이 경우, appName 필드에는 "SpringApp"이 주입된다.

 

주의사항

  • 프로퍼티 미존재 시의 처리: 프로퍼티 값이 설정 파일에 없을 경우 기본 값을 지정할 수 있다. 예: @Value("${app.version:1.0}")
  • 타입 변환: @Value는 자동으로 타입 변환을 수행한다. 예를 들어, 문자열을 정수나 불리언으로 변환할 수 있다.
  • 표현식 한계: @Value로 복잡한 타입이나 구성을 주입하는 것은 권장되지 않는다. 이런 경우에는 @ConfigurationProperties를 사용하는 것이 더 적합할 수 있다.

 

@EventListener

@EventListener는 스프링 프레임워크에서 이벤트를 처리하는 메소드에 적용하는 어노테이션이다. 이 어노테이션을 사용하면 특정 이벤트가 발생했을 때 스프링 애플리케이션 컨텍스트가 자동으로 해당 메소드를 호출하도록 설정할 수 있다. @EventListener를 사용함으로써, 애플리케이션 내에서의 비동기적인 이벤트 기반 프로그래밍을 간결하고 직관적으로 구현할 수 있다.

 

@EventListener 어노테이션은 이벤트를 처리할 메소드에 붙여 사용한다. 

@EventListener(ApplicationReadyEvent.class)
public void initData() {
    log.info("test data init");
    itemRepository.save(new Item("itemA", 10000, 10));
    itemRepository.save(new Item("itemB", 20000, 20));
}

@EventListener(ApplicationReadyEvent.class) : 스프링 컨테이너가 완전히 초기화를 다 끝내고, 실행 준비가 되었을 때 발생하는 이벤트이다. 스프링이 이 시점에 해당 애노테이션이 붙은 initData() 메서드 를 호출해준다.

 

이 기능 대신 @PostConstruct 를 사용할 경우 AOP 같은 부분이 아직 다 처리되지 않은 시점에 호출될 수 있기 때문에, 간혹 문제가 발생할 수 있다. 예를 들어서 @Transactional 과 관련된 AOP가 적 용되지 않은 상태로 호출될 수 있다.


@EventListener(ApplicationReadyEvent.class) 는 AOP를 포함한 스프링 컨테이너가 완전히 초기화 된 이후에 호출되기 때문에 이런 문제가 발생하지 않는다.

 

@Import

@Import는 스프링 프레임워크에서 사용되는 어노테이션으로, 다른 설정 클래스들을 현재의 설정 클래스에 추가할 때 사용된다. 이 어노테이션을 사용하면 스프링 애플리케이션 컨텍스트에 빈(Bean) 설정을 집중시킬 수 있어, 애플리케이션의 구성을 더 명확하게 관리할 수 있다.

사용 방법

@Import 어노테이션은 주로 @Configuration 어노테이션이 붙은 클래스에 적용된다. 이를 통해, 하나의 설정 클래스에서 다른 설정 클래스들을 불러와서 그 설정들을 현재의 애플리케이션 컨텍스트에 합치는 것이 가능하다.

@Configuration
@Import({DataSourceConfig.class, SecurityConfig.class})
public class MainConfig {
    // 기본 애플리케이션 설정
}

위의 예시에서 MainConfig 설정 클래스는 DataSourceConfig와 SecurityConfig 설정 클래스를 가져와서 사용한다. 이렇게 함으로써, MainConfig는 데이터 소스와 보안 설정을 포함한 전체 애플리케이션 설정의 중심점 역할을 한다.

주요 특징 및 이점

  • 모듈화: @Import를 사용하면 애플리케이션의 구성을 여러 설정 파일로 나누어 모듈화할 수 있다. 이는 각 설정 파일이 특정 기능이나 구성 요소에 집중할 수 있게 해, 애플리케이션의 구조를 더 명확하고 관리하기 쉽게 만든다.
  • 재사용성: 일반적인 구성 요소를 가진 설정 클래스를 만들고, 여러 애플리케이션 또는 애플리케이션의 다른 부분에서 재사용할 수 있다.
  • 조건부 구성: @Conditional 어노테이션과 함께 사용하여 특정 조건에 따라 설정 클래스를 포함시키거나 제외시킬 수 있다.

 

사용 시 고려사항

  • @Import는 주로 라이브러리의 설정 클래스나 애플리케이션 전반에 걸쳐 공통적으로 사용되는 설정 클래스를 추가할 때 유용하다. 그러나, 너무 많은 설정 클래스를 @Import로 가져오는 것은 관리하기 어렵게 만들 수 있으므로, 애플리케이션의 크기와 구조를 고려하여 적절히 사용해야 한다.
  • @Import 어노테이션은 클래스 수준에서만 사용할 수 있다는 점을 기억해야 한다. 또한, 가져오는 설정 클래스들도 스프링 빈으로 등록되어야 한다는 점을 유의해야 한다.

 

@Profile

@Profile 어노테이션은 스프링 프레임워크에서 제공하는 기능으로, 특정 환경에서만 빈(Bean)을 등록하거나 활성화하고자 할 때 사용된다. 이를 통해 개발, 테스트, 프로덕션 등 다양한 환경에 맞는 구성을 쉽게 관리할 수 있다. @Profile 어노테이션을 사용하면 애플리케이션의 실행 환경에 따라 다른 빈 설정을 적용할 수 있어, 환경별로 다른 데이터베이스 설정, 다른 외부 서비스 연동 방식 등을 구성하는 데 유용하다.

사용 방법

@Profile 어노테이션은 @Component, @Service, @Repository, @Controller 등의 스테레오타입 어노테이션과 함께 사용되거나, @Configuration 클래스에 적용할 수 있다.

클래스 레벨에서 사용하는 경우

@Configuration
@Profile("development")
public class DevelopmentConfig {
    // 개발 환경에서 사용할 빈 설정
}

 

메소드 레벨에서 사용하는 경우

@Configuration
public class ApplicationConfig {

    @Bean
    @Profile("production")
    public DataSource dataSource() {
        // 프로덕션 환경에서 사용할 데이터 소스 반환
        return new ProductionDataSource();
    }
}

 

활성화 방법

프로파일은 다양한 방식으로 활성화할 수 있다. 

대표적으로스프링 부트의 application.properties 통해 설정할 수 있다.

 

application.properties

spring.profiles.active=development

 

 

주요 특징 및 이점

  • 유연한 환경 구성: 다양한 환경에 맞는 구성을 한 곳에서 관리할 수 있어, 애플리케이션의 유연성이 증가한다.
  • 환경별 분리: 개발, 테스트, 프로덕션 환경 등에서 다른 설정이나 빈을 사용해야 할 때 유용하다.
  • 간결한 설정: 환경에 따라 다른 빈을 정의하고 선택할 수 있어, 조건별로 복잡한 구성 로직을 구현할 필요가 없다.

 

 

@Param

@Param 어노테이션은 Spring Data JPA에서 쿼리 메서드의 파라미터를 쿼리에 바인딩하는 데 사용된다. 이 어노테이션을 사용하면 메서드의 파라미터를 JPQL 혹은 SQL 쿼리 내에서 명시적으로 참조할 수 있다. @Param 어노테이션은 메서드 파라미터에 할당된 이름을 쿼리 내의 파라미터 이름과 매핑한다.

사용 예시

Spring Data JPA를 사용하여 사용자 이름과 이메일을 기준으로 사용자를 검색하는 기능을 구현한다고 가정해 보자. UserRepository 인터페이스에 새로운 쿼리 메서드를 추가할 때, @Param 어노테이션을 사용하여 쿼리의 파라미터를 명시적으로 정의할 수 있다.

public interface UserRepository extends JpaRepository<User, Long> {

    @Query("SELECT u FROM User u WHERE u.name = :name AND u.email = :email")
    List<User> findByNameAndEmail(@Param("name") String name, @Param("email") String email);
}

이 예시에서 @Query 어노테이션은 사용자 정의 쿼리를 나타내며, :name과 :email은 쿼리 내의 파라미터를 나타낸다. @Param 어노테이션은 메서드 파라미터(name과 email)가 쿼리의 파라미터(:name, :email)와 어떻게 매핑되는지 지정한다. 이렇게 함으로써, 메서드를 호출할 때 전달된 실제 파라미터 값이 쿼리에 바인딩된다.

 

@Query

어노테이션은 Spring Data JPA에서 사용자 정의 쿼리를 정의하는 데 사용된다. 이 어노테이션을 사용하면 개발자는 JPQL(Java Persistence Query Language) 또는 네이티브 SQL 쿼리를 직접 작성하여 Repository 메서드에 연결할 수 있다. @Query는 표준 CRUD 작업을 넘어서는 복잡한 조회나 연산을 필요로 할 때 유용하게 사용된다.

사용 예시

사용자 이름으로 사용자를 검색하는 쿼리를 정의하려고 한다. 이때, @Query 어노테이션을 사용하여 UserRepository 인터페이스에 메서드를 추가할 수 있다.

public interface UserRepository extends JpaRepository<User, Long> {

    @Query("SELECT u FROM User u WHERE u.name = :name")
    List<User> findByName(@Param("name") String name);
}

이 예시에서 @Query 어노테이션 내의 문자열은 JPQL 쿼리이며, :name은 쿼리의 파라미터를 나타낸다. @Param 어노테이션은 메서드의 name 파라미터가 쿼리의 :name 파라미터에 바인딩되도록 지정한다.

네이티브 쿼리 사용 예시

때때로, JPQL로 표현하기 어려운 데이터베이스 특정 기능을 사용해야 할 경우가 있다. 이런 경우에는 네이티브 SQL 쿼리를 사용할 수 있다.

public interface UserRepository extends JpaRepository<User, Long> {

    @Query(value = "SELECT * FROM users WHERE name = :name", nativeQuery = true)
    List<User> findByNameUsingNativeQuery(@Param("name") String name);
}

이 예시에서는 nativeQuery = true 속성을 통해 네이티브 SQL 쿼리를 사용하고 있음을 명시한다. 이렇게 하면, 직접 데이터베이스에 특화된 쿼리를 사용할 수 있다.

 

@PersistenceContext

EntityManager를 주입하기 위해 사용하는 어노테이션이다. 이 어노테이션을 사용하면 JPA의 영속성 컨텍스트와 연결된 EntityManager 인스턴스를 스프링이 자동으로 주입해준다.

EntityManager란?

EntityManager는 JPA에서 엔티티를 관리하는 주요 인터페이스로, 데이터베이스와의 CRUD(Create, Read, Update, Delete) 작업을 수행하는 데 사용된다.
EntityManager는 영속성 컨텍스트(Persistence Context)와 연결되어 있으며, 영속성 컨텍스트는 엔티티의 상태를 관리하고 데이터베이스와 동기화한다.


사용 방법

@PersistenceContext는 EntityManager를 주입하기 위한 어노테이션이다. 이 어노테이션을 사용하면 스프링 컨테이너가 EntityManager를 자동으로 주입해준다.

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.springframework.stereotype.Repository;

@Repository
public class MemberRepository {

    @PersistenceContext
    private EntityManager em;

    public Member find(Long id) {
        return em.find(Member.class, id);
    }

    public void save(Member member) {
        em.persist(member);
    }

    public void delete(Member member) {
        em.remove(member);
    }
}

위 예제에서 @PersistenceContext를 사용하여 EntityManager를 주입받고, 이를 이용해 Member 엔티티를 관리하는 메서드를 정의하고 있다.

@PersistenceContext와 트랜잭션

EntityManager는 일반적으로 트랜잭션 컨텍스트 내에서 사용된다. 스프링에서는 @Transactional 어노테이션을 사용하여 트랜잭션을 관리할 수 있다.
@Transactional이 적용된 메서드에서는 EntityManager를 통해 데이터베이스 작업이 수행되며, 작업이 끝나면 트랜잭션이 자동으로 커밋 또는 롤백된다.

import jakarta.persistence.EntityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository
public class MemberRepository {

    @Autowired
    private EntityManager em;

    public Member find(Long id) {
        return em.find(Member.class, id);
    }

    public void save(Member member) {
        em.persist(member);
    }
}

 

대체 어노테이션 @Autowired

스프링 부트에서는 @Autowired를 사용하여 EntityManager를 주입받을 수도 있다. 이 경우, 스프링은 내부적으로 @PersistenceContext와 동일한 방식으로 EntityManager를 처리한다.

하지만, @PersistenceContext는 JPA 표준 어노테이션이므로, JPA 관련 클래스에서는 @PersistenceContext를 사용하는 것이 권장된다.

import jakarta.persistence.EntityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository
public class MemberRepository {

    @Autowired
    private EntityManager em;

    public Member find(Long id) {
        return em.find(Member.class, id);
    }

    public void save(Member member) {
        em.persist(member);
    }
}

 

결론

  • @PersistenceContext는 JPA에서 EntityManager를 주입받기 위한 표준적인 방법이다.
  • 이 어노테이션을 통해 JPA의 영속성 컨텍스트와 연관된 EntityManager를 주입받아 데이터베이스 작업을 수행할 수 있다.
  • 트랜잭션을 관리할 때는 @Transactional과 함께 사용하는 것이 일반적이다.

아래의 모든 명령어은 Homebrew가 설치되어있다고 가정한다.

 

DBeaver 설치

터미널에서 아래의 명령어를 입력하면 특별한 오류가 나지 않는이상 설치는 완료된다.

brew install --cask dbeaver-community

이후 앱을 실행하면 된다.

 

Oracle(XE)

Oracle XE

Oracle Database Express Edition (Oracle XE)은 Oracle Corporation에서 제공하는 무료, 경량 버전의 Oracle 데이터베이스이다. 이 버전은 개발자, 교육용으로, 그리고 소규모 배포를 위해 설계되었다.
Oracle XE는 기능적으로는 전체 Oracle 데이터베이스 시스템의 서브셋을 제공하지만, 많은 기본적인 기능과 툴을 포함하고 있어 데이터베이스 학습이나 개발, 테스트 환경 구축에 이상적이다.

초기 세팅

https://www.youtube.com/watch?v=yCQD_rZEzZs&ab_channel=%EB%A6%AC%EC%96%BC%EB%8D%B0%EB%B8%8C%E2%B8%B0%EB%9F%AC%EB%8B%9D

위 영상을 참고하였음

 

터미널에서 아래의 명령어를 입력한다.

 

먼저 오라클은 맥 os를 지원하지 않기 때문에 도커를 통해서 실행할 수 있기 때문에 도커를 설치한다.

brew install docker

 

Colima MacOS 사용자를 위한 Docker Kubernetes 환경을 간편하게 설정하고 관리할 있는 오픈 소스 도구이다.

도커를 원활히 실행시킬 수 있도록 콜리마 또한 설치한다.

 

brew install colima

 

colima start --arch x86_64 --cpu 4 --memory 8 명령어는 Colima를 사용하여 x86_64 아키텍처를 지원하는 컨테이너 환경을 시작하는 데 사용된다.

여기서 --cpu 4는 컨테이너에 4개의 CPU 코어를 할당하고, --memory 8은 8GB의 메모리를 할당하라는 것을 의미한다.

 

colima start --arch x86_64 --cpu 4 --memory 8

 

아래의 명령어에서 가장 중요한 부분은 -e ORACLE_PASSWORD=비밀번호 이다.

데이터베이스의 SYS, SYSTEM, PDBADMIN 계정에 대한 비밀번호를 "비밀번호"로 설정한다.

당연히 이 부분은 사용자가 자신이 원하는 패스워드로 변경해서 명령어를 실행해야 한다.

docker run -d --name oracle-xe -p 8080:8080 -p 1521:1521 -e ORACLE_PASSWORD=비밀번호 -v ~/data/oracle:/opt/oracle/oradata gvenzl/oracle-xe

 

이후 sysdba 권한으로 접속한다.

sqlplus 명령어를 실행할 수 없다면
docker exec -it oracle-xe sqlplus 명령어를 사용하면 된다.
sqlplus / as sysdba

 

orcl이라는 사용자를 생성하고 비밀번호를 java로 설정한다.(원하는 아이디와 비밀번호로 바꿔도된다.)

CREATE USER orcl IDENTIFIED BY java;

 

권한 부여

GRANT CONNECT, RESOURCE TO orcl;

 

사용 방법

실행

콜리마를 실행한다.

이미 이 명령어를 실행했다면 이 부분은 생략해도 된다.

colima start --arch x86_64 --cpu 4 --memory 8

 

도커가 oracle-xe 컨테이너를 실행하는 명령어를 입력한다.

docker start oracle-xe
docker ps 명령어를 이용해 oracle-xe 컨테이너가 있는지 확인할 수 있다.

 

작업

이제 실행은 되고 있는 상태이다.

따라서 실행 - 작업 - 종료 순서에서 이 부분인 작업 부분은 사용자에 따라 다르기 때문에 무시해도 된다.

 

DBeaver에서 위에서 설정한 정보를 바탕으로 접속하려면 아래와 같다.

Database : XE

Username : orcl

Password : java

 

sqlplus 명령어를 사용하려면 아래의 명령어를 입력하면 된다.

docker exec -it oracle-xe sqlplus

 

sqlplus 명령어를 모두 끝냈다면 exit를 입력해서 벗어나면 된다.

exit

 

 

종료

실행 하는 순서를 거꾸로하면 종료하는 순서가 된다.

docker stop oracle-xe

 

colima stop

 

 

MySQL(8.X)

초기 설정

mysql 설치

brew install mysql

 

mysql 서버 시작

mysql.server start

 

보안 설정 스크립트

mysql_secure_installation

 

보안 설정 스크립트를 실행하면 아래와 같이 어떻게 보안을 설정할지 물어본다.

이 부분은 각자 환경에 맞게 설정하면 된다.

Would you like to setup VALIDATE PASSWORD plugin?
설치 과정에서 설정하지 않았다면, MySQL 루트 계정의 비밀번호를 설정하라는 요청을 받게 된다. 이미 설정된 비밀번호가 있다면, 더 강력한 비밀번호로 변경할 것인지 묻는다.

Remove anonymous users?
익명 사용자 제거: MySQL은 기본적으로 '익명 사용자'를 허용하는데, 이는 누구나 사용자 이름 없이 MySQL 서버에 접근할 수 있음을 의미한다. mysql_secure_installation 스크립트는 이러한 익명 사용자를 제거하도록 권장한다.

Disallow root login remotely?
루트 사용자 원격 접속 제한: 기본적으로, 루트 사용자는 어느 네트워크에서나 MySQL 서버에 접근할 수 있다. 보안을 위해, 이 스크립트는 루트 사용자의 원격 접속을 금지하고 로컬에서만 접근할 수 있도록 설정을 변경할 것을 권장한다.
->localhost 외에 다른 아이피로는 접속을 하지 못하게 하는 설정

Remove test database and access to it?
test 데이터베이스 삭제 여부: 보통 y를 선택

Reload privilege tables now?
권한 테이블 즉시 적용: 변경사항을 적용하고 권한 테이블을 새로고침하여, 모든 보안 변경사항이 바로 적용되도록 한다. 변경 사항을 즉시 적용하기 위해 보통 Y를 선택

 

사용 방법

MySQL 서버 시작 명령(이미 시작되어있다면 생략)

mysql.server start

 

루트 계정으로 로그인 명령

mysql -uroot -p

설정한 비밀번호를 입력해야 접속 가능

 

MySQL 서버 종료 명령

mysql.server stop

 

DBeaver 접속 설정

mysql_secure_installation에서 설정한 루트 비밀번호를 입력하여 접속

 

DBeaver 에서 MySQL을 연결하려고 하였으나 Public key retrieval is not allowed 오류가 발생하였다.

이 오류는 데이터베이스 서버로부터 공개 키를 안전하게 검색하는 것이 기본적으로 허용되지 않을 때 발생하는데, 주로 MySQL 8.0 이상 버전에서 새로운 인증 방식을 사용할 때 나타난다고 한다.

 

해결 방법은 간단했다.

  • 이미 설정된 연결 목록에서 해당 연결을 우클릭하고 "Edit Connection"을 선택

  1. "Connection Settings" 창에서 "Driver Properties" 탭으로 이동
  2. 여기서 allowPublicKeyRetrieval 속성을 찾아 값을 true로 설정
  3. 모든 변경 사항을 적용한 후, "OK" 또는 "Apply" 버튼을 클릭하여 연결 설정을 저장

 

이제 DBeaver에서 해당 데이터베이스 연결을 다시 시도하면, "Public key retrieval is not allowed" 오류 없이 연결이 성공적으로 이루어져야 한다.

 

 

이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다.


스프링(서버)에서 응답 데이터를 만드는 방법은 크게 3가지이다.

  • 정적 리소스
    -예) 웹 브라우저에 정적인 HTML, css, js를 제공할 때는, 정적 리소스를 사용한다.
  • 뷰 템플릿 사용
    -예) 웹 브라우저에 동적인 HTML을 제공할 때는 뷰 템플릿을 사용한다.
  • HTTP 메시지 사용
    -HTTP API를 제공하는 경우에는 HTML이 아니라 데이터를 전달해야 하므로, HTTP 메시지 바디에 JSON 같은 형식으로 데이터를 실어 보낸다.

 

정적 리소스

스프링 부트는 클래스패스의 다음 디렉토리에 있는 정적 리소스를 제공한다.

/static , /public , /resources , /META-INF/resources

 

src/main/resources 는 리소스를 보관하는 곳이고, 또 클래스패스의 시작 경로이다.

따라서 다음 디렉토리에 리소스를 넣어두면 스프링 부트가 정적 리소스로 서비스를 제공한다.

 

스프링 부트에는 내장 웹서버(Tomcat)가 있어서 정적 리소스를 그대로 서비스해준다.

 

정적 리소스 경로

src/main/resources/static

 

다음 경로에 파일이 들어있으면

src/main/resources/static/basic/hello-form.html

 

웹 브라우저에서 다음과 같이 실행하면 된다.

http://localhost:8080/basic/hello-form.html

 

정적 리소스는 해당 파일을 변경 없이 그대로 서비스하는 것이다.

 

뷰 템플릿

뷰 템플릿을 거쳐서 HTML이 생성되고, 뷰가 응답을 만들어서 전달한다.
일반적으로 HTML을 동적으로 생성하는 용도로 사용하지만, 다른 것들도 가능하다. 뷰 템플릿이 만들 수 있는 것이라 면 뭐든지 가능하다
.

 

스프링 부트는 기본 뷰 템플릿 경로를 제공한다.

뷰 템플릿 경로

src/main/resources/templates

 

뷰 템플릿 생성(타임리프)

<!DOCTYPE html>
 <html xmlns:th="http://www.thymeleaf.org">
 <head>
     <meta charset="UTF-8">
     <title>Title</title>
 </head>
<body>
 <p th:text="${data}">empty</p>
</body>
</html>

 

뷰 템플릿을 호출하는 컨트롤러

String을 반환하는 경우 - View or HTTP 메시지

@RequestMapping("/response-view-v2")
public String responseViewV2(Model model) {
	model.addAttribute("data", "hello!!");
	return "response/hello";
}

@ResponseBody 가 없으면 response/hello 로 뷰 리졸버가 실행되어서 뷰를 찾고, 렌더링 한다.

@ResponseBody 가 있으면 뷰 리졸버를 실행하지 않고, HTTP 메시지 바디에 직접 response/hello 라는 문자가 입력된다.

여기서는 뷰의 논리 이름인 response/hello 를 반환하면 다음 경로의 뷰 템플릿이 렌더링 되는 것을 확인할 수 있다.

 

Void를 반환하는 경우

@RequestMapping("/response/hello")
public void responseViewV3(Model model) {
	model.addAttribute("data", "hello!!");
}

@Controller 를 사용하고, HttpServletResponse , OutputStream(Writer) 같은 HTTP 메시지 바디를 처리하는 파라미터가 없으면 요청 URL을 참고해서 논리 뷰 이름으로 사용 요청

이 방식은 권장하지 않음

 

HTTP API, 메시지 바디에 직접 입력

정적 리소스나 뷰 템플릿을 거치지 않고, 직접 HTTP 응답 메시지를 전달하는 경우

 

ResponseEntity(단순 텍스트, 상태메시지)

ResponseEntity 엔티티는 HttpEntity를 상속 받았는데, HttpEntity는 HTTP 메시지의 헤더, 바디 정보를 가지고 있다. ResponseEntity는 여기에 더해서 HTTP 응답 코드를 설정할 수 있다.

HttpStatus.CREATED로 변경하면 201 응답이 나가는 것을 확인할 수 있다.

/**
* HttpEntity, ResponseEntity(Http Status 추가) * @return
*/
@GetMapping("/response-body-string-v2")
public ResponseEntity<String> responseBodyV2() {
    return new ResponseEntity<>("ok", HttpStatus.OK);
}

 

@ResponseBody

@ResponseBody를 사용하면 view를 사용하지 않고, HTTP 메시지 컨버터를 통해서 HTTP 메시지를 직접 입력할 수 있다. ResponseEntity도 동일한 방식으로 동작한다.

@ResponseBody
@GetMapping("/response-body-string-v3")
public String responseBodyV3() {
    return "ok";
}

 

ResponseEntity(JSON)

ResponseEntity를 반환한다. HTTP 메시지 컨버터를 통해서 JSON 형식으로 변환되어서 반환된다.

@GetMapping("/response-body-json-v1")
public ResponseEntity<HelloData> responseBodyJsonV1() {
    HelloData helloData = new HelloData();
    helloData.setUsername("userA");
    helloData.setAge(20);
    return new ResponseEntity<>(helloData, HttpStatus.OK);
}

 

@ResponseBody + @ResponseStatus

ResponseEntity는 HTTP 응답 코드를 설정할 수 있는데, @ResponseBody를 사용하면 이런 것을 설정하기 까다롭다. @ResponseStatus(HttpStatus.OK) 어노테이션을 사용하면 응답 코드도 설정할 수 있다.

물론 어노테이션이기 때문에 응답 코드를 동적으로 변경할 수는 없다.

프로그램 조건에 따라서 동적으로 변경하려면 ResponseEntity를 사용하면 된다.

@ResponseStatus(HttpStatus.OK)
@ResponseBody
@GetMapping("/response-body-json-v2")
public HelloData responseBodyJsonV2() {
    HelloData helloData = new HelloData();
    helloData.setUsername("userA");
    helloData.setAge(20);
    return helloData;
}

 

@RestController

@Controller 대신에 @RestController 어노테이션을 사용하면, 해당 컨트롤러에 모두 @ResponseBody 적용되는 효과가 있다.

따라서 뷰 템플릿을 사용하는 것이 아니라, HTTP 메시지 바디에 직접 데이터를 입력한다. 이름 그대로 Rest API(HTTP API)를 만들 때 사용하는 컨트롤러이다.

@ResponseBody 는 클래스 레벨에 두면 전체 메서드에 적용되는데, @RestController 어노테이션 안에 @ResponseBody 가 적용되어 있다.

이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다.


HTTP 요청

어노테이션 기반의 스프링 컨트롤러는 다양한 파라미터를 지원한다.

@Slf4j
@RestController
public class RequestHeaderController {
    @RequestMapping("/headers")
    public String headers(HttpServletRequest request,
                          HttpServletResponse response,
                          HttpMethod httpMethod,
                          Locale locale,
                          @RequestHeader MultiValueMap<String, String> headerMap,
                          @RequestHeader("host") String host,
                          @CookieValue(value = "myCookie", required = false) String cookie) {
        
        log.info("request={}", request);
        log.info("response={}", response);
        log.info("httpMethod={}", httpMethod);
        log.info("locale={}", locale);
        log.info("headerMap={}", headerMap);
        log.info("header host={}", host);
        log.info("myCookie={}", cookie);
        return "ok";
    }
}
@CookieValues
@CookieValue(value = "myCookie", required = false) String cookie​

value: 추출하고자 하는 쿠키의 이름을 지정한다.

required: 이 속성은 쿠키의 존재 여부가 필수인지 아닌지를 지정한다. 기본값은 true이며, 이 경우 지정한 이름의 쿠키가 요청에 없으면 예외가 발생한다. false로 설정하면, 지정한 이름의 쿠키가 없어도 예외가 발생하지 않으며, 대신 파라미터 값은 null이 된다.

@RequestHeader("host") String host
클라이언트로부터 받은 요청 중 "host" 헤더의 값을 String 타입의 host 변수에 저장한다.

required: 이 속성은 해당 헤더가 반드시 필요한지를 지정한다. 기본값은 true로, 헤더가 요청에 없을 경우 예외가 발생한다. required 속성을 false로 설정하면, 헤더가 없어도 예외가 발생하지 않고, 파라미터 값은 null 또는 defaultValue로 설정된 값이 된다.

defaultValue: 헤더가 없을 때 사용할 기본값을 지정할 수 있다. required가 false로 설정되어 있고, 요청에 특정 헤더가 없을 경우 defaultValue로 지정된 값이 변수에 할당된다.

HttpMethod: HTTP 메서드를 조회한다. org.springframework.http.HttpMethod

  • Locale: Locale 정보를 조회한다.
  • @RequestHeader MultiValueMap<String, String> headerMap 모든 HTTP 헤더를 MultiValueMap 형식으로 조회한다.
  • @RequestHeader("host") String host 특정 HTTP 헤더를 조회한다.
    속성 필수 값 여부: required -> 기본 값 속성: true
  • @CookieValue(value = "myCookie", required = false) String cookie 특정 쿠키를 조회한다. 
    속성 필수 값 여부: required -> 기본 값: true

 

MultiValueMap

MAP과 유사한데, 하나의 키에 여러 값을 받을 수 있다.
HTTP header, HTTP 쿼리 파라미터와 같이 하나의 키에 여러 값을 받을 때 사용한다.
 -> keyA=value1&keyA=value2

MultiValueMap<String, String> map = new LinkedMultiValueMap();
 map.add("keyA", "value1");
 map.add("keyA", "value2");
 //[value1,value2]
List<String> values = map.get("keyA");​
하나의 키를 입력하면 값이 여러개 이므로 리스트 타입으로 반환된다.

 

참고

@Controller 의 사용 가능한 파라미터 목록은 다음 공식 메뉴얼에서 확인할 수 있다.
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann- arguments


@Controller 의 사용 가능한 응답 값 목록은 다음 공식 메뉴얼에서 확인할 수 있다.
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann- return-types

 

HTTP 요청 파라미터

클라이언트에서 서버로 요청 데이터를 전달할 때는 주로 다음 3가지 방법을 사용한다.

  • GET - 쿼리 파라미터
    -/url?username=hello&age=20
    -메시지 바디 없이, URL의 쿼리 파라미터에 데이터를 포함해서 전달
    -예) 검색, 필터, 페이징 등에서 많이 사용하는 방식
  • POST - HTML Form
    -content-type: application/x-www-form-urlencoded
    -메시지 바디에 쿼리 파라미터 형식으로 전달 username=hello&age=20
    -예) 회원 가입, 상품 주문, HTML Form 사용
  • HTTP message body에 데이터를 직접 담아서 요청
    -HTTP API에서 주로 사용, JSON, XML, TEXT
    -데이터 형식은 주로 JSON 사용
    -POST, PUT, PATCH

 

GET, 쿼리 파라미터 전송

  • http://localhost:8080/request-param?username=hello&age=20

 

POST, HTML Form 전송

  • POST /request-param ...
    content-type: application/x-www-form-urlencoded

    username=hello&age=20

 

GET 쿼리 파리미터 전송 방식이든, POST HTML Form 전송 방식이든 둘다 형식이 같으므로 구분없이 조회할 수 있다.

이것을 간단히 요청 파라미터(request parameter) 조회라 한다.

 

 

@RequestParam(int, String 등 기본 타입)

기본

스프링이 제공하는 @RequestParam 을 사용하면 요청 파라미터를 매우 편리하게 사용할 수 있다.

/**
* @RequestParam 사용
* - 파라미터 이름으로 바인딩
* @ResponseBody 추가
* - View 조회를 무시하고, HTTP message body에 직접 해당 내용 입력
*/
@ResponseBody //View 조회를 무시하고, HTTP message body에 직접 해당 내용 입력
@RequestMapping("/request-param-v2")
public String requestParamV2(@RequestParam("username") String memberName, @RequestParam("age") int memberAge){
    log.info("username={}, age={}", memberName, memberAge);
    return "ok";
}
  • @RequestParam : 파라미터 이름으로 바인딩
  • @ResponseBody : View 조회를 무시하고, HTTP message body에 직접 해당 내용 입력

 

@RequestParam의 name(value) 속성이 파라미터 이름으로 사용

  • @RequestParam("username") String memberName
    -> request.getParameter("username")

 

파라미터 이름 생략

HTTP 파라미터 이름이 변수 이름과 같으면 @RequestParam(name="xx") 생략이 가능하다.

/**
* @RequestParam 사용
* HTTP 파라미터 이름이 변수 이름과 같으면 @RequestParam(name="xx") 생략 가능 */
 @ResponseBody
 @RequestMapping("/request-param-v3")
 public String requestParamV3(
                            @RequestParam String username,
                            @RequestParam int age) {
    log.info("username={}, age={}", username, age);
    return "ok";
}

 

@RequestParam 생략

파라미터의 타입이 기본 타입, HTTP 파라미터 이름이 변수 이름과 같으면 @RequestParam 이 생략 가능하다.

/**
* @RequestParam 사용
* String, int 등의 단순 타입이면 @RequestParam 도 생략 가능
*/
@ResponseBody
@RequestMapping("/request-param-v4")
public String requestParamV4(String username, int age) {
    log.info("username={}, age={}", username, age);
    return "ok";
}

 

@RequestParam 애노테이션을 생략하면 스프링 MVC는 내부에서 required=false 를 적용한다.

 

파라미터 필수 여부 - requestParamRequired

/**
  * @RequestParam.required
* /request-param-required -> username이 없으므로 예외 *
* 주의!
* /request-param-required?username= -> 빈문자로 통과 *
* 주의!
* /request-param-required
* int age -> null을 int에 입력하는 것은 불가능, 따라서 Integer 변경해야 함(또는 defaultValue 사용)
*/
 @ResponseBody
 @RequestMapping("/request-param-required")
 public String requestParamRequired(
        @RequestParam(required = true) String username,
        @RequestParam(required = false) Integer age) {
        
    log.info("username={}, age={}", username, age);
    return "ok";
}

@RequestParam.required -> 파라미터 필수 여부 -> 기본값이 파라미터 필수(true)이다.

/request-param-required 요청
-> username 이 없으므로 400 예외가 발생한다.

 

주의! 
- 파라미터 이름만 사용
/request-param-required?username=
파라미터 이름만 있고 값이 없는 경우 빈문자로 통과

- 기본형(primitive)에 null 입력
/request-param 요청 @RequestParam(required = false) int age
null 을 int 에 입력하는 것은 불가능(500 예외 발생)따라서 null 을 받을 수 있는 Integer 로 변경하거나, 또는 defaultValue 사용

 

기본 값 적용 - requestParamDefault

  • 파라미터에 해당하는 값이 들어오지 않은 경우(null, "")에 requestParamDefault를 쓰면 된다.
/**
 * @RequestParam
 * - defaultValue 사용 *
 * 참고: defaultValue는 빈 문자의 경우에도 적용 * /request-param-default?username=
 */
@ResponseBody
@RequestMapping("/request-param-default")
public String requestParamDefault(
        @RequestParam(required = true, defaultValue = "guest") String username,
        @RequestParam(required = false, defaultValue = "-1") int age) {
        
    log.info("username={}, age={}", username, age);
    return "ok";
}

파라미터에 값이 없는 경우 defaultValue 를 사용하면 기본 값을 적용할 수 있다.

이를 적용하면 이미 기본 값이 있기 때문에 required=true 는 의미가 없다.

defaultValue 는 빈 문자의 경우에도 설정한 기본 값이 적용된다.

/request-param-default?username=

즉, 파라미터 값이 null이거나 "" 인 경우에도 기본 값이 적용된다.

 

파라미터를 Map으로 조회하기 - requestParamMap

/**
 * @RequestParam Map, MultiValueMap
 * Map(key=value)
 * 파라미터의 값이 1개가 확실하다면 `Map` 을 사용해도 되지만, 그렇지 않다면 `MultiValueMap` 을 사용하자.
 * MultiValueMap(key=[value1, value2, ...]) ex) (key=userIds, value=[id1, id2])
 */
@ResponseBody
@RequestMapping("/request-param-map")
public String requestParamMap(@RequestParam Map<String, Object> paramMap) {
    log.info("username={}, age={}", paramMap.get("username"), paramMap.get("age"));
    return "ok";
}

 

파라미터를 Map, MultiValueMap으로 조회할 수 있다.

  • @RequestParam Map
    -Map(key=value)
  • @RequestParam MultiValueMap
    -MultiValueMap(key=[value1, value2, ...] ex) (key=userIds, value=[id1, id2])'

 

@ModelAttribute(객체)

HelloData 라는 객체가 아래와 같이 정의되어 있다고 가정하자

import lombok.Data;
@Data
public class HelloData {
    private String username;
    private int age;
}

롬복 @Data : @Getter , @Setter , @ToString , @EqualsAndHashCode , @RequiredArgsConstructor 자동으로 적용해준다.

 

/**
* @ModelAttribute 사용
* 참고: model.addAttribute(helloData) 코드도 함께 자동 적용됨, 뒤에 model을 설명할 때 자세히
설명
*/
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData) {
    log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
    return "ok";
}

이렇게 @ModelAttribute 를 사용하면 HelloData 객체가 생성되고, 요청 파라미터의 값도 모두 들어가진다.

스프링MVC는 @ModelAttribute 가 있으면 다음을 실행한다.

-HelloData 객체를 생성한다.
-요청 파라미터의 이름으로 HelloData 객체의 프로퍼티를 찾는다.
-해당 프로퍼티의 setter를 호출해서 파라미터의 값을 입력(바인딩) 한다.

 

바인딩 오류

age=abc 처럼 숫자가 들어가야 할 곳에 문자를 넣으면 BindException 이 발생한다.

 

@ModelAttribute를 생략할 수도 있다.

/**
 * @ModelAttribute 생략 가능
 * String, int 같은 단순 타입 = @RequestParam
 * argument resolver 로 지정해둔 타입 외 = @ModelAttribute */
@ResponseBody
@RequestMapping("/model-attribute-v2")
public String modelAttributeV2(HelloData helloData) {
    log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
    return "ok";
}
스프링은 해당 생략시 다음과 같은 규칙을 적용한다.

String, int , Integer 같은 단순 타입 = @RequestParam
나머지 = @ModelAttribute (argument resolver 로 지정해둔 타입 외)

 

HTTP message body에 데이터를 직접 담아서 요청

  • HTTP API에서 주로 사용, JSON, XML, TEXT
  • 데이터 형식은 주로 JSON 사용
  • POST, PUT, PATCH

 

요청 파라미터와 다르게, HTTP 메시지 바디를 통해 데이터가 직접 넘어오는 경우는 @RequestParam , @ModelAttribute 를 사용할 수 없다. (물론 HTML Form 형식으로 전달되는 경우는 요청 파라미터로 인정된다.)

 

단순 텍스트

HttpEntity

/**
* HttpEntity: HTTP header, body 정보를 편리하게 조회
* - 메시지 바디 정보를 직접 조회(@RequestParam X, @ModelAttribute X)
* - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용 *
* 응답에서도 HttpEntity 사용 가능
* - 메시지 바디 정보 직접 반환(view 조회X)
* - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용 */
 @PostMapping("/request-body-string-v3")
 public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) {
     String messageBody = httpEntity.getBody();
     log.info("messageBody={}", messageBody);
     return new HttpEntity<>("ok");
 }
  • HttpEntity<String> httpEntity: HttpEntity 클래스는 HTTP 요청이나 응답을 표현하며, 여기서는 요청 본문에 담긴 String 타입의 데이터를 받는다.
  • HttpEntity는 요청의 헤더와 본문 정보를 모두 포함하고 있어, 메소드 내에서 요청 본문뿐만 아니라 헤더 정보에도 접근할 수 있다는 장점이 있다.
  • String messageBody = httpEntity.getBody(): getBody() 메소드를 사용해 HTTP 요청 본문의 내용을 String 형태로 추출한다.
  • return new HttpEntity<>("ok"): 처리 결과를 나타내는 "ok" 문자열을 담은 새로운 HttpEntity 객체를 반환한다. 이 객체는 클라이언트에게 응답 본문으로 전송되어, 처리가 성공적으로 이루어졌음을 알린다.

    이 방법은 특히 HTTP API를 구현할 때 클라이언트로부터 전달받은 데이터를 그대로 처리해야 할 경우 유용하다.

 

HttpEntity는 응답에도 사용 가능

  • 메시지 바디 정보 직접 반환
  • 헤더 정보 포함 가능
  • view 조회X

 

HttpEntity 를 상속받은 다음 객체들도 같은 기능을 제공한다.

  • RequestEntity
    -HttpMethod, url 정보가 추가, 요청에서 사용
  • ResponseEntity
    -HTTP 상태 코드 설정 가능, 응답에서 사용
    -return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED)
스프링MVC 내부에서 HTTP 메시지 바디를 읽어서 문자나 객체로 변환해서 전달해주는데, 이때 HTTP 메시지 컨버터(HttpMessageConverter )라는 기능을 사용한다.

 

@RequestBody

/**
 * @RequestBody
 * - 메시지 바디 정보를 직접 조회(@RequestParam X, @ModelAttribute X)
 * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용 *
 * @ResponseBody
 * - 메시지 바디 정보 직접 반환(view 조회X)
 * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
 */
@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) {
    log.info("messageBody={}", messageBody);
    return "ok";
}

@RequestBody 를 사용하면 HTTP 메시지 바디 정보를 편리하게 조회할 수 있다.

헤더 정보가 필요하다면 HttpEntity 를 사용하거나 @RequestHeader 를 사용하면 된다.

이렇게 메시지 바디를 직접 조회하는 기능은 요청 파라미터를 조회하는 @RequestParam , @ModelAttribute 와 는 전혀 관계가 없다.

@ResponseBody 를 사용하면 응답 결과를 HTTP 메시지 바디에 직접 담아서 전달할 수 있다.

물론 이 경우에도 view를 사용하지 않는다.

 

@ResponseBody와 @RequestBody

@ResponseBody 어노테이션은 컨트롤러 메소드에서 반환하는 값을 HTTP 응답 본문으로 직접 전달하는 데 사용된다. 이 어노테이션이 붙어 있으면, 메소드의 반환 데이터가 응답 본문에 바로 쓰이게 되며, 스프링의 MessageConverter를 통해 클라이언트가 요구하는 형식(JSON, XML 등)으로 자동으로 변환된다. 이는 주로 RESTful 웹 서비스에서 클라이언트에게 데이터를 직접 반환할 때 활용된다.

@RequestBody 어노테이션은 HTTP 요청 본문에서 데이터를 읽어와 메소드의 파라미터로 바인딩할 때 사용된다. 클라이언트가 보낸 요청 본문의 내용을 자바 객체로 변환하여 컨트롤러 메소드의 인자로 전달하게 된다. 이 과정에서도 스프링의 MessageConverter가 자동으로 해당 작업을 수행한다. @RequestBody는 주로 클라이언트가 서버에 데이터를 전송할 때, 예를 들어 JSON이나 XML 형태의 데이터를 받아 처리할 때 사용된다.

간단히 말해, @ResponseBody는 서버에서 클라이언트로 데이터를 보낼 때 사용되며, @RequestBody는 클라이언트에서 서버로 데이터를 전송할 때 사용된다.

 

요청 파라미터 vs HTTP 메시지 바디

요청 파라미터를 조회하는 기능: @RequestParam , @ModelAttribute
HTTP 메시지 바디를 직접 조회하는 기능: @RequestBody

 

 

JSON

아래의 내용들은 content-typeapplication/json 일때 해당하는 내용들이다.

@RequestBody 문자 변환

/**
* @RequestBody
* HttpMessageConverter 사용 -> StringHttpMessageConverter 적용 *
* @ResponseBody
* - 모든 메서드에 @ResponseBody 적용
* - 메시지 바디 정보 직접 반환(view 조회X)
* - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용 */
 @ResponseBody
 @PostMapping("/request-body-json-v2")
 public String requestBodyJsonV2(@RequestBody String messageBody) throws IOException {
     HelloData data = objectMapper.readValue(messageBody, HelloData.class);
     log.info("username={}, age={}", data.getUsername(), data.getAge());
     return "ok";
}
ObjectMapper

Jackson 라이브러리의 핵심 클래스로, 자바 객체와 JSON 간의 변환을 담당한다.
JSON 문자열을 자바 객체로 변환하거나(역직렬화), 자바 객체를 JSON 문자열로 변환하는(직렬화) 작업을 수행한다.
ObjectMapper 는 높은 유연성과 다양한 설정 옵션을 제공하여 복잡한 JSON 구조와 자바 객체 간의 매핑을 가능하게 한다.

 

@RequestBody 객체 변환

/**
* @RequestBody 생략 불가능(@ModelAttribute 가 적용되어 버림)
* HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter (content-type:application/json)
*/
 @ResponseBody
 @PostMapping("/request-body-json-v3")
 public String requestBodyJsonV3(@RequestBody HelloData data) {
     log.info("username={}, age={}", data.getUsername(), data.getAge());
     return "ok";
}

HttpEntity , @RequestBody 를 사용하면 HTTP 메시지 컨버터가 HTTP 메시지 바디의 내용을 우리가 원하는 문자나 객체 등으로 변환해준다.
HTTP
메시지 컨버터는 문자 뿐만 아니라 JSON도 객체로 변환해준다.

 

HttpEntity

@ResponseBody
@PostMapping("/request-body-json-v4")
public String requestBodyJsonV4(HttpEntity<HelloData> httpEntity) { //HttpEntity를 사용해도 됨
    HelloData data = httpEntity.getBody();
    log.info("username={}, age={}", data.getUsername(), data.getAge());
    return "ok";
}

 

리턴을 객체로

/**
* @RequestBody 생략 불가능(@ModelAttribute 가 적용되어 버림)
* HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter (content-type:application/json)
*
* @ResponseBody 적용
* - 메시지 바디 정보 직접 반환(view 조회X)
* - HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter 적용(Accept:application/json)
*/
@ResponseBody
@PostMapping("/request-body-json-v5")
public HelloData requestBodyJsonV5(@RequestBody HelloData data) {
    log.info("username={}, age={}", data.getUsername(), data.getAge());
    return data;
 }

 

응답의 경우에도 @ResponseBody 를 사용하면 해당 객체를 HTTP 메시지 바디에 직접 넣어줄 수 있다.

물론 이 경우에도 HttpEntity 를 사용해도 된다.

 

@RequestBody 요청

  • JSON 요청 -> HTTP 메시지 컨버터 -> 객체

 

@ResponseBody 응답

  • 객체 -> HTTP 메시지 컨버터 -> JSON 응답

'Java Category > Spring' 카테고리의 다른 글

[Spring] 어노테이션 정리  (0) 2024.02.23
[Spring MVC] 기본 기능 - HTTP 응답  (0) 2024.02.22
[Spring MVC] 기본 기능 - 요청 매핑  (0) 2024.02.20
[Spring] Logging  (0) 2024.02.19
[Spring MVC] 스프링 MVC - 구조 이해  (1) 2024.02.18

이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다.


@RestController
특정 클래스를 RESTful 웹 서비스의 컨트롤러로 지정한다.
이 어노테이션을 사용한 클래스는 HTTP 요청을 처리하는 핸들러 메소드를 포함하며, 각 메소드는 특정 HTTP 요청(예: GET, POST, DELETE 등)에 매핑된다.
@RestController는 @Controller 와 유사하지만, 차이점은 @RestController 로 지정된 컨트롤러의 메소드가 기본적으로 HTTP 응답 본문(Body)에 직접 데이터를 작성한다는 점이다.이는@ResponseBody 어노테이션을 모든 핸들러 메소드에 적용한 것과 같은 효과를 가진다.

@Controller 는 반환 값이 String 이면 뷰 이름으로 인식된다.
그래서 뷰를 찾고 뷰가 랜더링 된다. @RestController 는 반환 값으로 뷰를 찾는 것이 아니라, HTTP 메시지 바디에 바로 입력한다.
@ResponseBody
웹 어플리케이션에서 컨트롤러의 핸들러 메소드가 HTTP 응답 본문(Body)에 직접 내용을 작성할 수 있도록 지정한다.
이 어노테이션은 @Controller 어노테이션이 적용된 클래스 내의 메소드에 사용될 때 유용하며, @RestController 어노테이션과는 달리, @Controller 어노테이션과 함께 사용되어야 한다.
@RestController 어노테이션은 기본적으로 모든 메소드에 @ResponseBody 를 적용한 것과 같은 효과를 제공한다.
스프링 부트 3.0 부터는 /hello-basic 과 /hello-basic/ 는 서로 다른 URL 요청을 사용해야 한다.
따라서 다음과 같이 다르게 매핑해서 사용해야 한다.
매핑: /hello-basic -> URL요청: /hello-basic
매핑: /hello-basic/ -> URL요청: /hello-basic/

 

단순 @RequestMapping("/hello-basic")

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

@Slf4j
@RestController
public class MappingController {

    /**
     * 기본 요청
     * 둘다 허용 /hello-basic, /hello-basic/
     * HTTP 메서드 모두 허용 GET, HEAD, POST, PUT, PATCH, DELETE
     */
    @RequestMapping("/hello-basic")
    public String helloBasic(){
        log.info("helloBasic");
        return "ok";
    }
}

단순히 @RequestMapping("/경로") 를 사용하면 어떤 HTTP 메서드로 들어오든 상관 없이 해당 컨트롤러가 실행된다.

즉, @RequestMapping method 속성으로 HTTP 메서드를 지정하지 않으면 HTTP 메서드와 무관하게 호출된다.

 

HTTP 메서드 매핑

메서드 지정

/*
* method 특정 HTTP 메서드 요청만 허용
* GET, HEAD, POST, PUT, PATCH, DELETE
*/
 @RequestMapping(value = "/mapping-get-v1", method = RequestMethod.GET)
 public String mappingGetV1() {
     log.info("mappingGetV1");
     return "ok";
 }

get 요청만 허락하도록 @RequestMapping에 메서드를 지정할 수 있다.

만약 여기에 POST 요청을 하면 스프링 MVCHTTP 405 상태코드(Method Not Allowed)를 반환한다.

 

HTTP 메서드 매핑 축약

/**
* 편리한 축약 애노테이션
* @GetMapping
* @PostMapping
* @PutMapping
* @DeleteMapping
* @PatchMapping
*/
@GetMapping(value = "/mapping-get-v2")
public String mappingGetV2() {
     log.info("mapping-get-v2");
	return "ok";
}

HTTP 메서드를 축약한 애노테이션을 사용하는 것이 더 직관적이다.

이 어노테이션 내부에서 @RequestMapping method 를 지정해서 사용한다.

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping
  • @PatchMapping

지정된 메스드 이외의 메서드로 접근하면 오류를 내뿜는다.

 

PathVariable(경로 변수) 사용

/**
* PathVariable 사용
* 변수명이 같으면 생략 가능
* @PathVariable("userId") String userId -> @PathVariable String userId
*/
 @GetMapping("/mapping/{userId}")
 public String mappingPath(@PathVariable("userId") String data) {
     log.info("mappingPath userId={}", data);
     return "ok";
 }

/mapping/userA 이런 식으로 리소스 경로에 식별자를 넣는 스타일에서 사용하는 방식이다.

@RequestMapping URL 경로를 템플릿화 할 수 있는데, @PathVariable 을 사용하면 매칭 되는 부분을 편리하게 조회할 수 있다.

 

@PathVariable 의 이름과 파라미터 이름이 같으면 아래와 같이 생략할 수 있다.

 @GetMapping("/mapping/{userId}")
 public String mappingPath(@PathVariable String userId) {
     log.info("mappingPath userId={}", userId);
     return "ok";
 }

 

 

또한 PathVariable은 다중으로 사용이 가능하다.

/**
* PathVariable 사용 다중
*/
@GetMapping("/mapping/users/{userId}/orders/{orderId}")
public String mappingPath(@PathVariable String userId, @PathVariable Long orderId) {
    log.info("mappingPath userId={}, orderId={}", userId, orderId);
    return "ok";
}

 

 

특정 파라미터 조건 매핑

특정 파라미터가 있거나 없는 조건을 추가할 수 있다.

잘 사용하지는 않는다.

/mapping-param?mode=debug

/**
* 파라미터로 추가 매핑
* params="mode",
* params="!mode"
* params="mode=debug"
* params="mode!=debug" (! = )
* params = {"mode=debug","data=good"}
*/
 @GetMapping(value = "/mapping-param", params = "mode=debug")
 public String mappingParam() {
     log.info("mappingParam");
     return "ok";
 }

 

특정 헤더 조건 매핑

파라미터 매핑과 비슷하지만, HTTP 헤더를 사용한다.

즉, 헤더에 정보가 포함되어 있어야 사용할 수 있다.

/**
* 특정 헤더로 추가 매핑
* headers="mode",
* headers="!mode"
* headers="mode=debug"
* headers="mode!=debug" (! = )
*/
@GetMapping(value = "/mapping-header", headers = "mode=debug")
public String mappingHeader() {
    log.info("mappingHeader");
    return "ok";
}

 

미디어 타입 조건 매핑

Consumes과 Produces의 차이점
Consumes: 컨트롤러가 처리할 수 있는 요청의 미디어 타입을 지정한다. 요청이 이 타입을 만족해야 컨트롤러 메소드가 해당 요청을 처리한다.
Produces: 컨트롤러 메소드가 생성하여 반환할 수 있는 응답의 미디어 타입을 지정한다. 클라이언트는 이 타입의 데이터를 응답으로 받게 된다.

 

HTTP 요청 Content-Type, consume

HTTP 요청의 Content-Type 헤더를 기반으로 미디어 타입으로 매핑한다.

만약 맞지 않으면 HTTP 415 상태코드(Unsupported Media Type)을 반환한다.

 

consumes 속성은 컨트롤러 메소드가 처리할 수 있는 요청의 MIME 타입을 지정한다.  

consumes = "text/plain"
consumes = {"text/plain", "application/*"}
consumes = MediaType.TEXT_PLAIN_VALUE

이 속성을 사용하면 특정 타입의 데이터를 (서버의 입장에서)소비하는 요청만을 해당 컨트롤러 메소드에서 처리하도록 제한할 수 있다.

예를 들어, consumes = "application/json"이라고 지정하면, 해당 컨트롤러 메소드는 Content-Type 헤더가 application/json인 요청만을 처리한다.

/**
* Content-Type 헤더 기반 추가 매핑 Media Type * consumes="application/json"
* consumes="!application/json"
* consumes="application/*"
* consumes="*\/*"
* MediaType.APPLICATION_JSON_VALUE
*/
 @PostMapping(value = "/mapping-consume", consumes = "application/json")
 public String mappingConsumes() {
     log.info("mappingConsumes");
     return "ok";
}

 

HTTP 요청 Accept, produce

HTTP 요청의 Accept 헤더를 기반으로 미디어 타입으로 매핑한다.

만약 맞지 않으면 HTTP 406 상태코드(Not Acceptable)을 반환한다.

 

produces 속성은 컨트롤러 메소드에서 반환하는 응답의 MIME 타입을 지정한다.

produces = "text/plain"
produces = {"text/plain", "application/*"}
produces = MediaType.TEXT_PLAIN_VALUE
produces = "text/plain;charset=UTF-8"

이 속성을 통해 클라이언트가 Accept 헤더를 통해 요청한 타입과 일치하는 응답 타입만을 반환하도록 제한할 수 있다.

예를 들어, produces = "text/html"이라고 지정하면, 해당 컨트롤러 메소드는 클라이언트에게 text/html 형식의 데이터를 반환한다.

/**
* Accept 헤더 기반 Media Type * produces = "text/html"
* produces = "!text/html"
* produces = "text/*"
* produces = "*\/*"
*/
@PostMapping(value = "/mapping-produce", produces = "text/html")
public String mappingProduces() {
    log.info("mappingProduces");
    return "ok";
}

 

API 예시

회원 관리를 HTTP API로 만든다 생각하고 매핑을 어떻게 하는지 알아보자.

회원 관리 API
회원 목록 조회 GET
/users
회원 등록 POST /users
회원 조회 GET /users/{userId}
회원 수정 PATCH /users/{userId}
회원 삭제 DELETE /users/{userId}

 

@RequestMapping("/mapping/users")

클래스 레벨에 매핑 정보를 두면 메서드 레벨에서 해당 정보를 조합해서 사용한다.

package hello.springmvc.basic.requestmapping;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/users")
public class MappingClassController {
    /**
    * GET /mapping/users
    */
    @GetMapping
    public String users() {
        return "get users";
    }
    
    /**
     * POST /mapping/users
     */
    @PostMapping
    public String addUser() {
        return "post user";
    }
    
    /**
     * GET /mapping/users/{userId}
     */
    @GetMapping("/{userId}")
    public String findUser(@PathVariable String userId) {
        return "get userId=" + userId;
    }
    
    /**
     * PATCH /mapping/users/{userId}
     */
    @PatchMapping("/{userId}")
    public String updateUser(@PathVariable String userId) {
        return "update userId=" + userId;
    }
    
    /**
     * DELETE /mapping/users/{userId}
     */
    @DeleteMapping("/{userId}")
    public String deleteUser(@PathVariable String userId) {
        return "delete userId=" + userId;
    }
}

이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다.


운영 시스템에서는 System.out.println() 같은 시스템 콘솔을 사용해서 필요한 정보를 출력하지 않고, 별도의 로깅 라이브러리를 사용해서 로그를 출력한다.

 

스프링 부트 라이브러리를 사용하면 스프링 부트 로깅 라이브러리(spring-boot-starter-logging)가 함께 포함된다.

스프링 부트 로깅 라이브러리는 기본으로 다음 로깅 라이브러리를 사용한다.

 

로그 라이브러리는 Logback, Log4J, Log4J2 등등 수 많은 라이브러리가 있는데, 그것을 통합해서 인터페이스로 제공하는 것이 바로 SLF4J 라이브러리다.

SLF4J는 인터페이스이고, 그 구현체로 Logback 같은 로그 라이브러리를 선택하면 된다.

실무에서는 스프링 부트가 기본으로 제공하는 Logback을 대부분 사용한다.

 

로그 선언

  • private Logger log = LoggerFactory.getLogger(getClass());
  • private static final Logger log = LoggerFactory.getLogger(Xxx.class)
  • @Slf4j : 롬복에서 제공하는 로그 어노테이션

 

로그 호출

  • log.info("hello")`

 

로그가 출력되는 포멧

  • 시간, 로그 레벨, 프로세스 ID, 쓰레드 명, 클래스명, 로그 메시지

 

올바른 로그 사용법

  • log.debug("data="+data) : 로그 출력 레벨을 info로 설정해도 해당 코드에 있는 "data="+data가 실제 실행이 되어 버린다. 결과적으로 문자 더하기 연산이 발생한다. -> 이렇게 사용하면 안된다.
  • log.debug("data={}", data) : 로그 출력 레벨을 info로 설정하면 아무일도 발생하지 않는다. 따라서 위와 같은 의미없는 연산이 발생하지 않는다.

 

실무에서는 항상 로그를 사용해야 한.

 

로그를 남기는 방법

방법 1

package hello.springmvc.basic;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController //반환 값 string 그대로 html 바디에 전송
public class LogTestController {

    private final Logger log = LoggerFactory.getLogger(getClass());

    @RequestMapping("/log-test")
    public String logTest(){
        String name = "Spring";

        //TRACE > DEBUG > INFO > WARN > ERROR
        log.trace("trace log = {}", name);
        log.debug("debug log = {}", name);
        log.info("info log = {}", name);
        log.warn("warn log = {}", name);
        log.error("error log = {}", name);

        log.debug("String concat log = " + name); //로그를 사용하지 않아도 a+b 계산 로직이 먼저 실행됨, 이런 방식으로 사용하면 X
        return "ok";
    }
}

 

방법2(@Slf4j 사용)

package hello.springmvc.basic;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController //반환 값 string 그대로 html 바디에 전송
public class LogTestController {
    @RequestMapping("/log-test")
    public String logTest(){
        String name = "Spring";

        //TRACE > DEBUG > INFO > WARN > ERROR
        log.trace("trace log = {}", name);
        log.debug("debug log = {}", name);
        log.info("info log = {}", name);
        log.warn("warn log = {}", name);
        log.error("error log = {}", name);

        //로그를 사용하지 않아도 a+b 계산 로직이 먼저 실행됨, 이런 방식으로 사용하면 X
        log.debug("String concat log = " + name);
        return "ok";
    }
}

 

@RestController
특정 클래스를 RESTful 웹 서비스의 컨트롤러로 지정한다.
이 어노테이션을 사용한 클래스는 HTTP 요청을 처리하는 핸들러 메소드를 포함하며, 각 메소드는 특정 HTTP 요청(예: GET, POST, DELETE 등)에 매핑된다.
@RestController는 @Controller 와 유사하지만, 차이점은 @RestController 로 지정된 컨트롤러의 메소드가 기본적으로 HTTP 응답 본문(Body)에 직접 데이터를 작성한다는 점이다.
이는@ResponseBody 어노테이션을 모든 핸들러 메소드에 적용한 것과 같은 효과를 가진다.


@Controller 는 반환 값이 String 이면 뷰 이름으로 인식된다.
그래서 뷰를 찾고 뷰가 랜더링 된다. @RestController 는 반환 값으로 뷰를 찾는 것이 아니라, HTTP 메시지 바디에 바로 입력한다.
@ResponseBody
웹 어플리케이션에서 컨트롤러의 핸들러 메소드가 HTTP 응답 본문(Body)에 직접 내용을 작성할 수 있도록 지정한다.
이 어노테이션은 @Controller 어노테이션이 적용된 클래스 내의 메소드에 사용될 때 유용하며, @RestController 어노테이션과는 달리, @Controller 어노테이션과 함께 사용되어야 한다.
@RestController 어노테이션은 기본적으로 모든 메소드에 @ResponseBody 를 적용한 것과 같은 효과를 제공한다.

 

로그 레벨

로그 레벨 : TRACE < DEBUG < INFO < WARN < ERROR

  • TRACE: 가장 낮은 레벨로, 시스템의 작동 상태를 매우 상세하게 추적하고자 할 때 사용된다. 애플리케이션의 흐름이나 변수의 값 변화 등을 추적하는 데 유용하다. TRACE 레벨은 주로 개발 과정에서 문제를 진단하기 위해 사용된다.
  • DEBUG: TRACE보다는 덜 상세한 정보를 제공하지만, 개발자가 코드의 실행 상태를 이해하고 문제를 진단하는 데 도움이 되는 정보를 로깅한다. DEBUG 레벨은 애플리케이션의 내부 작동 상태를 확인할 때 사용된다.
  • INFO: 애플리케이션의 일반적인 작업 진행 상황을 나타내는 데 사용된다. 예를 들어, 애플리케이션의 시작과 종료, 중요한 작업의 완료 등이 INFO 레벨로 로깅된다.
  • WARN: 잠재적인 문제를 경고하는 데 사용된다. 시스템의 작동에는 현재 영향을 주지 않지만, 향후 문제가 될 수 있는 상황이나 예상치 못한 상황을 나타낸다.
  • ERROR: 심각한 문제가 발생했음을 나타내며, 애플리케이션의 정상적인 작동을 방해하는 오류나 예외 상황을 로깅한다. ERROR 레벨의 로그는 즉각적인 주의와 대응이 필요한 상황을 나타낸다.

 

이러한 로그 레벨 구조를 통해 개발자는 로깅을 통해 애플리케이션의 다양한 상태와 이벤트를 효과적으로 모니터링하고 문제를 진단할 수 있다. 또한, 로그 레벨을 적절히 설정함으로써 필요한 정보만을 기록하여 로그 파일의 크기를 관리하고 성능 저하를 최소화할 수 있다.

 

로그 레벨 설정을 변경한 후 애플리케이션의 로깅 동작에 발생하는 변화는 다음과 같다.

  • 선택된 로그 레벨 이상의 메시지만 기록됨: 예를 들어, 로그 레벨을 INFO로 설정하면, INFO, WARN, ERROR 레벨의 로그 메시지만 기록된다. DEBUG와 TRACE 레벨의 메시지는 이보다 낮은 레벨이므로 기록되지 않는다. 이는 로그 데이터의 양을 줄이고, 로그 파일을 통해 중요한 정보에 더 쉽게 접근할 수 있게 한다.
  • 로깅 성능의 개선: 낮은 레벨의 로그 메시지(예: DEBUG와 TRACE)가 많이 생성되지 않게 되므로, 로그 기록으로 인한 성능 저하가 감소한다. 이는 특히 로그 메시지의 양이 많고 로깅이 성능에 미치는 영향이 큰 애플리케이션에서 유용하다.
  • 로그 파일 관리 용이성 증가: 필요한 정보만 로깅되므로 로그 파일의 크기가 줄어들고, 로그 관리가 용이해진다. 이는 로그 파일의 저장 공간을 절약하고, 로그 분석 과정을 간소화한다.
  • 중요 정보의 가시성 향상: 중요한 정보만 기록되기 때문에, 개발자나 시스템 관리자가 로그 파일을 통해 문제를 진단하거나 시스템의 상태를 모니터링하는 데 있어 중요한 정보에 더 쉽게 주목할 수 있다.

 

-> 개발 서버는 debug 출력 운영 서버는 info 출력

 

로그 레벨 변경 

application.properties에서 로그 레벨을 변경할 수 있다.

#전체 로그 레벨 설정(기본 info)
logging.level.root=info

#hello.springmvc 패키지와 그 하위 로그 레벨 설정
logging.level.hello.springmvc=debug

 

이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다.

2024.02.17 - [Java Category/JSP] - JSP와 서블릿을 이용한 MVC 프레임워크 만들기

 

JSP와 서블릿을 이용한 MVC 프레임워크 만들기

이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다. Model, View, Controller 컨트롤러: HTTP 요청을 받아서 파라미터를 검증하고, 비즈니스 로직을 실행한다. 그

rebugs.tistory.com

위 글과 이어진 내용입니다.


왼쪽 : JSP로 만든 MVC프레임워크, 오른쪽 : 스프링 MVC프레임워크

 

JSP로 만든 MVC 프레임워크와 스프링 MVC 프레임워크의 구조가 매우 비슷하다는 것을 알 수 있다.

 

핵심은 FrontController와 DispatcherServlet이다.

JSP로 만든 프레임워크와 스프링 MVC 비교
FrontController
DispatcherServlet
handlerMappingMap
HandlerMapping
MyHandlerAdapter
HandlerAdapter
ModelView
ModelAndView
viewResolver
ViewResolver
MyView
View

 

DispatcherServlet

org.springframework.web.servlet.DispatcherServlet

스프링 MVC도 프론트 컨트롤러 패턴으로 구현되어 있다.
스프링 MVC의 프론트 컨트롤러가 바로 디스패처 서블릿(DispatcherServlet)이다. 그리고 이 디스패처 서블릿이 바로 스프링 MVC의 핵심이다.

DispatcherServlet 서블릿 등록

DispatcherServlet 도 부모 클래스에서 HttpServlet 을 상속 받아서 사용하고, 서블릿으로 동작한다.

DispatcherServlet -> FrameworkServlet -> HttpServletBean -> HttpServlet

스프링 부트는 DispatcherServlet 을 서블릿으로 자동으로 등록하면서 모든 경로( urlPatterns="/")에 대해서 매핑한다.

모든 경로 ( urlPatterns="/" )에 대해서 매핑
더 자세한 경로가 우선순위가 높다. 그래서 기존에 등록한 서블릿도 함께 동작한다.

 

요청 흐름

서블릿이 호출되면 HttpServlet 이 제공하는 serivce() 가 호출된다.

스프링 MVCDispatcherServlet 의 부모인 FrameworkServlet 에서 service() 를 오버라이드 해 두었다.

FrameworkServlet.service() 를 시작으로 여러 메서드가 호출되면서 DispatcherServlet.doDispatch() 가 호출된다.

 

DispatcherServlet 의 핵심인 doDispatch() 코드를 분석해보자. 최대한 간단히 설명하기 위해 예외 처리, 인터셉터 기능은 제외했다.

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        ModelAndView mv = null;
        // 1. 핸들러 조회
        mappedHandler = getHandler(processedRequest);
        if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
        }

        // 2. 핸들러 어댑터 조회 - 핸들러를 처리할 수 있는 어댑터
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

        // 3. 핸들러 어댑터 실행 -> 4. 핸들러 어댑터를 통해 핸들러 실행 -> 5. ModelAndView 반환
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
     private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
         // 뷰 렌더링 호출
        render(mv, request, response);
    }
    
     protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
     
     View view;
     String viewName = mv.getViewName();
     
     // 6. 뷰 리졸버를 통해서 뷰 찾기, 7. View 반환
     view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
     // 8. 뷰 렌더링
     view.render(mv.getModelInternal(), request, response);
 }

 

요청의 전처리

DispatcherServlet이 요청을 받으면, 등록된 여러 HandlerMapping들 중에서 해당 요청을 처리할 수 있는 HandlerMapping을 찾는다.

HandlerMapping은 URL, HTTP 메소드 등 요청의 정보를 기반으로 적절한 핸들러(컨트롤러) 객체를 결정한다.

핸들러 선택

요청에 매핑된 핸들러 객체가 결정되면, 해당 핸들러를 실행할 수 있는 HandlerAdapter를 찾는다.

HandlerAdapter는 다양한 타입의 컨트롤러를 지원할 수 있도록 해준다.

예를 들어, RequestMappingHandlerAdapter는 @RequestMapping 어노테이션이 붙은 메소드를 처리할 수 있다.

 

핸들러 실행

적절한 HandlerAdapter를 통해 핸들러(컨트롤러 메소드)가 실행된다. 이 과정에서 핸들러 메소드의 입력 파라미터는 요청에서 추출하거나 변환된 값이 바인딩되고, 핸들러 메소드의 실행 결과로 ModelAndView 객체가 반환된다.

ModelAndView는 뷰 이름과 뷰를 렌더링할 때 사용할 모델 데이터를 포함한다.

 

뷰 해석

컨트롤러가 반환한 뷰 이름을 바탕으로 ViewResolver를 사용해 실제 뷰 객체를 찾는다.

예를 들어, InternalResourceViewResolver는 JSP 파일 경로를 해석하여 View 객체를 생성한다.

 

뷰 렌더링

찾아진 View 객체는 모델 데이터를 렌더링하는 과정을 거쳐 최종적인 응답을 생성한다. 이 응답은 클라이언트에게 전송된다.

 

 

동작 순서

  1. 핸들러 조회 : 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러(컨트롤러)를 조회한다.
  2. 핸들러 어댑터 조회 : 핸들러를 실행할 수 있는 핸들러 어댑터를 조회한다.
  3. 핸들러 어댑터 실행 : 핸들러 어댑터를 실행한다.
  4. 핸들러 실행 : 핸들러 어댑터가 실제 핸들러를 실행한다.
  5. ModelAndView 반환 : 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView로 변환해서 반환한다.
  6. viewResolver 호출 : 뷰 리졸버를 찾고 실행한다.
  7. View 반환 : 뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 바꾸고, 렌더링 역할을 담당하는 뷰 객체를 반환한다. 
  8. 뷰 렌더링 : 뷰를 통해서 뷰를 렌더링 한다.

 

핸들러 매핑과 핸들러 어댑터

 

과거 버전 스프링 컨트롤러 - Controller 인터페이스

org.springframework.web.servlet.mvc.Controller

public interface Controller {
     ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;

}

스프링도 처음에는 이러한 형식의 컨트롤러를 제공했다.

Controller 인터페이스는 @Controller 애노테이션과는 전혀 다르다.

 

OldController 클래스

package hello.servlet.web.springmvc;

//과거 버전 스프링 컨트롤러

//springmvc/old-controller 라는 이름의 스프링 빈으로 등록
//빈의 이름으로 URL을 매핑
@Component("/springmvc/old-controller")
public class OldController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println("OldController.handleRequest");
        return new ModelAndView("new-form");
    }
}
@Component 는 스프링 프레임워크에서 제공하는 어노테이션 중 하나로, 클래스를 스프링의 빈(bean)으로 등록하기 위해 사용된다.

@Component : 이 컨트롤러는 /springmvc/old-controller 라는 이름의 스프링 빈으로 등록되었다.

빈의 이름으로 URL을 매핑한다.

즉, http://localhost:8080/springmvc/old-controller 에 접속하면 해당 컨트롤러가 실행된다.

 

이 컨트롤러가 호출되려면 다음 2가지가 필요하다.

  • HandlerMapping(핸들러 매핑)
    -핸들러 매핑에서 이 컨트롤러를 찾을 수 있어야 한다.
    -예) 스프링 빈의 이름으로 핸들러를 찾을 수 있는 핸들러 매핑이 필요하다.
  • HandlerAdapter(핸들러 어댑터)
    -핸들러 매핑을 통해서 찾은 핸들러를 실행할 수 있는 핸들러 어댑터가 필요하다.
    -예) Controller 인터페이스를 실행할 수 있는 핸들러 어댑터를 찾고 실행해야 한다.

스프링은 이미 필요한 핸들러 매핑과 핸들러 어댑터를 대부분 구현해두었다. 개발자가 직접 핸들러 매핑과 핸들러 어댑터를 만드는 일은 거의 없다.

 

스프링 부트가 자동 등록하는 핸들러 매핑과 핸들러 어댑터(실제로는 더 많음)

핸들러 매핑도, 핸들러 어댑터도 모두 아래의 순서대로 찾고 만약 없으면 다음 순서로 넘어간다.

HandlerMapping

순위 핸들러 매핑 설명
0 RequestMappingHandlerMapping 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 BeanNameUrlHandlerMapping 스프링 빈의 이름으로 핸들러를 찾는다.


HandlerAdapter

순위 핸들러 어댑터 설명
0 RequestMappingHandlerAdapter 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 HttpRequestHandlerAdapter HttpRequestHandler 처리
2 SimpleControllerHandlerAdapter Controller 인터페이스(애노테이션X, 과거에 사용) 처리
  1. 핸들러 매핑으로 핸들러 조회
    1. HandlerMapping 을 순서대로 실행해서, 핸들러를 찾는다.
    2. 이경우 빈 이름으로 핸들러를 찾아야하기 때문에 이름 그대로 빈 이름으로 핸들러를 찾아주는 BeanNameUrlHandlerMapping 가 실행에 성공하고 핸들러인 OldController 를 반환한다.
  2.  핸들러 어댑터 조회
    1. HandlerAdapter 의 supports() 를 순서대로 호출한다.
    2. SimpleControllerHandlerAdapter 가 Controller 인터페이스를 지원하므로 대상이 된다.
  3. 핸들러 어댑터 실행
    1. 디스패처 서블릿이 조회한 SimpleControllerHandlerAdapter 를 실행하면서 핸들러 정보도 함께 넘겨준다.
    2. SimpleControllerHandlerAdapter 는 핸들러인 OldController 를 내부에서 실행하고, 그 결과를 반환한다.

OldController 를 실행하면서 사용된 객체는 다음과 같다.

- HandlerMapping = BeanNameUrlHandlerMapping

- HandlerAdapter = SimpleControllerHandlerAdapter

 

HttpRequestHandler 컨트롤러

HttpRequestHandler 핸들러(컨트롤러)서블릿과 가장 유사한 형태의 핸들러이다.

 

HttpRequestHandler

public interface HttpRequestHandler {
     void handleRequest(HttpServletRequest request, HttpServletResponse response)
}

 

 

MyHttpRequestHandler

@Component("/springmvc/request-handler")
public class MyHttpRequestHandler implements HttpRequestHandler {
    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("MyHttpRequestHandler.handleRequest");
    }
}

http://localhost:8080/springmvc/request-handler 이 경로로 접속하면 해당 컨트롤러가 실행된 것이다.

 

  1. 핸들러 매핑으로 핸들러 조회
    1. HandlerMapping 을 순서대로 실행해서, 핸들러를 찾는다.
    2. 이 경우 빈이름으로 핸들러를 찾아야하기 때문에 이름 그대로 빈 이름으로 핸들러를 찾아주는 BeanNameUrlHandlerMapping 가 실행에 성공하고 핸들러인 MyHttpRequestHandler 를 반환한다.
  2. 핸들러 어댑터 조회
    1. HandlerAdapter 의 supports() 를 순서대로 호출한다.
    2. HttpRequestHandlerAdapter 가 HttpRequestHandler 인터페이스를 지원하므로 대상이 된다.
  3. 핸들러 어댑터 실행
    1. 디스패처 서블릿이 조회한 HttpRequestHandlerAdapter 를 실행하면서 핸들러 정보도 함께 넘겨준다.
    2. HttpRequestHandlerAdapter 는 핸들러인 MyHttpRequestHandler 를 내부에서 실행하고, 그 결과를 반환한다.

 

MyHttpRequestHandler 를 실행하면서 사용된 객체는 다음과 같다.

-HandlerMapping = BeanNameUrlHandlerMapping

-HandlerAdapter = HttpRequestHandlerAdapter

 

 

뷰 리졸버

@Component("/springmvc/old-controller")
public class OldController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println("OldController.handleRequest");
        return new ModelAndView("new-form");
    }
}

이 코드를 실행하면 논리적 경로인 "new-form"가 뷰 리졸버로 전달되고, 뷰 리졸버(View Resolver)의 주된 역할은 컨트롤러가 반환한 뷰 이름을 바탕으로 실제 뷰의 경로를 찾아내고, 이를 뷰 객체에 전달하여 최종적으로 렌더링할 수 있도록 하도록 한다.

이 상황에서는 뷰 리졸버가 실제 뷰의 경로를 찾을 수 없기 때문에 발생하는 오류이다.

 

application.properties에 아래의 코드를 추가시키면 뷰 리졸버가 실제 뷰의 경로를 찾을 수 있게 해준다.

spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

 

InternalResourceViewResolver - JSP 처리 뷰

스프링 부트는 InternalResourceViewResolver 라는 뷰 리졸버를 자동으로 등록하는데, 이때 application.properties 에 등록한 spring.mvc.view.prefix , spring.mvc.view.suffix 설정 정보를 사용해서 등록한다.

참고로 권장하지는 않지만 설정 없이 다음과 같이 전체 경로를 주어도 동작하기는 한다.
return new ModelAndView("/WEB-INF/views/new-form.jsp");

 

스프링 부트가 자동 등록하는 뷰 리졸버(실제로는 더 많음)

순위
리졸버
설명
1
BeanNameViewResolver
빈 이름으로 뷰를 찾아서 반환한다. (: 엑셀 파일 생성 기능 에 사용)
2
InternalResourceViewResolver
JSP를 처리할 수 있는 뷰를 반환한다.

 

  1. 핸들러 어댑터 호출
    -핸들러 어댑터를 통해 new-form 이라는 논리 뷰 이름을 획득한다.
  2. ViewResolver 호출
    -new-form이라는 뷰 이름으로 viewResolver를 순서대로 호출한다.
    -BeanNameViewResolver 는 new-form 이라는 이름의 스프링 빈으로 등록된 뷰를 찾아야 하는데 없다.
    -그래서 InternalResourceViewResolver 가 호출된다.
  3. InternalResourceViewResolver
    -이 뷰 리졸버는 InternalResourceView 를 반환한다.
  4. 뷰 - InternalResourceView
    -InternalResourceView 는 JSP처럼 포워드 forward() 를 호출해서 처리할 수 있는 경우에 사용한다.
  5. view.render()
    -view.render()가 호출되고 InternalResourceView 는 forward() 를 사용해서 JSP를 실행한다.

 

참고
다른 뷰는 실제 뷰를 렌더링하지만, JSP의 경우 forward() 통해서 해당 JSP로 이동(실행)해야 렌더링이 된다. JSP를 제외한 나머지 뷰 템플릿들은 forward() 과정 없이 바로 렌더링 된다.


Thymeleaf 뷰 템플릿을 사용하면 ThymeleafViewResolver 를 등록해야 한다. 최근에는 라이브러리만 추 가하면 스프링 부트가 이런 작업도 모두 자동화해준다.

 

 

스프링 MVC로 리팩토링

@Controller, @RequestMapping

@RequestMapping

  • RequestMappingHandlerMapping -> 핸들러 맵핑
  • RequestMappingHandlerAdapter -> 핸들러 어댑터

 

@Controller

클래스 레벨에서 사용되며, 해당 클래스가 웹 요청을 처리하는 컨트롤러임을 스프링에게 알린다.

-> RequestMappingHandlerMapping
@Controller가 붙은 클래스는 스프링 MVC에서 HTTP 요청을 처리하는 데 필요한 여러 핸들러 메소드를 포함할 수 있으며, 스프링의 컴포넌트 스캔 기능에 의해 자동으로 탐지되어(내부에 @Component 있음) 스프링 애플리케이션 컨텍스트에 빈으로 등록된다.

 

@RequestMapping

어노테이션은 메소드 레벨에서 사용되며, 특정 HTTP 요청(URL 경로 및 HTTP 메소드)을 컨트롤러 내의 메소드에 매핑한다.
이를 통해 해당 메소드가 어떤 요청을 처리할 책임이 있는지를 정의한다.
@RequestMapping은 path또는 value와 method 속성을 통해 요청을 매핑하며, 다양한 속성을 제공하여 요청을 세밀하게 처리할 수 있다.
@RequestMapping은 클래스 레벨에도 사용될 수 있으며, 이 경우 해당 클래스 내의 모든 메소드에 공통적으로 적용되는 기본 URL 경로를 정의한다.

애노테이션을 기반으로 동작하기 때문에, 메서드의 이름은 임의로 지으면 된다.

 

정리

@Controller는 클래스가 웹 요청을 처리하는 컨트롤러임을 선언하는 데 사용되며, @RequestMapping은 해당 컨트롤러 내의 특정 메소드가 처리할 요청을 매핑하는 데 사용된다.
@Controller는 컨트롤러의 역할을 정의하는 반면, @RequestMapping은 컨트롤러 내에서 특정 요청을 어떻게 처리할지를 세부적으로 지정한다.
둘은 함께 사용되어 스프링 MVC에서 효율적으로 요청을 처리하고 응답을 생성하는 구조를 만든다.

 

SpringMemberFormControllerV1 - 회원 등록 폼

package hello.servlet.web.springmvc.old.v1;

import hello.servlet.domain.member.MemberRepository;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller //스프링이 자동으로 스프링 빈으로 등록한다. (내부에 @Component 애노테이션이 있어서 컴포넌트 스캔의 대상이 됨)
public class SpringMemberFormControllerV1 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @RequestMapping("/springmvc/v1/members/new-form") //해당 URL이 호출되면 이 메서드가 호출된다. 애노테이션을 기반으로 동작하기 때문에, 메서드의 이름은 임의로 지으면 된다.
    public ModelAndView process(){
        return new ModelAndView("new-form"); //모델과 뷰 정보를 담아서 반환
    }
}

 

http://localhost:8080/springmvc/v1/members/new-form 에 들어가면 정상적으로 작동하는 것을 확인할 수 있다.

 

SpringMemberSaveControllerV1 - 회원 저장

package hello.servlet.web.springmvc.old.v1;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class SpringMemberSaveControllerV1 {
    private MemberRepository memberRepository = MemberRepository.getInstance();
    @RequestMapping("/springmvc/v1/members/save")
    public ModelAndView process(HttpServletRequest request, HttpServletResponse response) {
        String username = request.getParameter("username");
        int age = Integer.parseInt(request.getParameter("age"));
        Member member = new Member(username, age);

        System.out.println("member = " + member);
        memberRepository.save(member);

        ModelAndView mv = new ModelAndView("save-result");
        mv.addObject("member", member); //스프링이 제공하는 ModelAndView 를 통해 Model 데이터를 추가할 때는 addObject() 를 사용하면 된다. 이 데이터는 이후 뷰를 렌더링 할 때 사용된다.
        return mv;
    }
}

mv.addObject("member", member)
스프링이 제공하는 ModelAndView 를 통해 Model 데이터를 추가할 때는 addObject() 를 사용하면 된다. 이 데이터는 이후 뷰를 렌더링 할 때 사용된다.

 

SpringMemberListControllerV1 - 회원 목록

package hello.servlet.web.springmvc.old.v1;
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import java.util.List;
@Controller
public class SpringMemberListControllerV1 {
    private MemberRepository memberRepository = MemberRepository.getInstance();

    @RequestMapping("/springmvc/v1/members")
    public ModelAndView process() {
        List<Member> members = memberRepository.findAll();
        ModelAndView mv = new ModelAndView("members");
        mv.addObject("members", members);
        return mv;
    }
}

 

컨트롤러 통합

@RequestMapping 을 잘 보면 클래스 단위가 아니라 메서드 단위에 적용된 것을 확인할 수 있다. 따라서 컨트롤러 클래스를 유연하게 하나로 통합할 수 있다.

package hello.servlet.web.springmvc.v2;
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import java.util.List;
/**
 * 클래스 단위 -> 메서드 단위
 * @RequestMapping 클래스 레벨과 메서드 레벨 조합
 */
@Controller
@RequestMapping("/springmvc/v2/members") //클래스 단위 -> 기본 경로
public class SpringMemberControllerV2 {
    private MemberRepository memberRepository = MemberRepository.getInstance();
    @RequestMapping("/new-form")
    public ModelAndView newForm() {
        return new ModelAndView("new-form");
    }
    
    @RequestMapping("/save")
    public ModelAndView save(HttpServletRequest request, HttpServletResponse response) {
        String username = request.getParameter("username");
        int age = Integer.parseInt(request.getParameter("age"));
        Member member = new Member(username, age);
        memberRepository.save(member);
        ModelAndView mav = new ModelAndView("save-result");
        mav.addObject("member", member);
        return mav;
    }
    
    @RequestMapping //어노테이션에 추가적인 값을 할당하지 않으면 클래스 단위 기본 경로값으로 자동 지정
    public ModelAndView members() {
        List<Member> members = memberRepository.findAll();
        ModelAndView mav = new ModelAndView("members");
        mav.addObject("members", members);
        return mav;
    }
}

 

클래스 레벨에 다음과 같이 @RequestMapping 을 두면 메서드 레벨과 조합이 된다.

@Controller
@RequestMapping("/springmvc/v2/members")
 public class SpringMemberControllerV2 {}

클래스 레벨 @RequestMapping("/springmvc/v2/members")

  • 메서드 레벨 @RequestMapping("/new-form") -> /springmvc/v2/members/new-form
  • 메서드 레벨 @RequestMapping("/save") -> /springmvc/v2/members/save
  • 메서드 레벨 @RequestMapping -> /springmvc/v2/members

 

실용적인 방식

package hello.servlet.web.springmvc.v3;
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/** * v3
 * Model 도입
 * ViewName 직접 반환
 * @RequestParam 사용
 * @RequestMapping -> @GetMapping, @PostMapping
 */
@Controller
@RequestMapping("/springmvc/v3/members")
public class SpringMemberControllerV3 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @GetMapping("/new-form")
    public String newForm() {
        return "new-form";
    }

    @PostMapping("/save")
    public String save(
            @RequestParam("username") String username,
            @RequestParam("age") int age,
            Model model) {
        Member member = new Member(username, age);
        memberRepository.save(member);
        model.addAttribute("member", member);
        return "save-result";
    }

    @GetMapping
    public String members(Model model) {
        List<Member> members = memberRepository.findAll();
        model.addAttribute("members", members);
        return "members";
    }
}

 

Model 파라미터

  • save() , members() 를 보면 Model을 파라미터로 받는 것을 확인할 수 있다. 스프링 MVC도 이런 편의 기능을 제공한다.

 

ViewName 직접 반환

  • 뷰의 논리 이름을 String 타입으로 반환할 수 있다.

 

@RequestParam 사용

  • 스프링은 HTTP 요청 파라미터를 @RequestParam 으로 받을 수 있다.
  • @RequestParam("username") 은 request.getParameter("username") 와 거의 같은 코드라 생각하면 된다.
    물론 GET 쿼리 파라미터, POST Form 방식을 모두 지원한다.  

 

@RequestMapping -> @GetMapping, @PostMapping

  • @RequestMapping 은 URL만 매칭하는 것이 아니라, HTTP Method도 함께 구분할 수 있다.
  • 예를 들어서 URL이 /new-form 이고, HTTP Method가 GET인 경우를 모두 만족하는 매핑을 하려면 다음과 같이 처리하면 된다.
@RequestMapping(value = "/new-form", method = RequestMethod.GET)
  • 이것을 @GetMapping , @PostMapping 으로 더 편리하게 사용할 수 있다.
  • 물론 Get, Post, Put, Delete, Patch 모두 어노테이션이 있다.