본문 바로가기

(11)

[Spring] 싱글톤 레지스트지와 Bean Scope

스프링의 애플리케이션 컨텍스트가 관리하는 객체(=Bean)는 일반적인 POJO 객체와는 중요한 차이점이 있다. 예를들어서 아래의 두 코드는 언뜻보면 비슷해보이지만, 큰 차이가 있다. public class AppConfig { public MemberService memberService() { return new MemberServiceImpl(new MemoryMemberRepository()); return new MemberServiceImpl(memberRepository()); } public OrderService orderService() { return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());..

Spring - 의존성 주입(DI)을 '생성자 주입 방식'으로 권장하는 이유

필드 주입 방식으로 의존성을 주입하는 방식에 익숙한(?) 개발자도 있을 것이다. 하지만 스프링에서 필드 주입 방식은 선호하지 않는다. 필드 주입 방식을 선호하지 않는다고 한다. 그렇다면 스프링은 어떤 방식을 선호할까? 퀵 액션(맥 기준 ⌥ + ↩︎) 버튼을 누르면 아래와 같이 나타난다. 그 결과는 생성자 주입방식으로 변경해준다. 그 내용을 더 상세히보면 처음 언급했던 것 처럼, Spring Team은 의존성 주입은 항상 생성자를 사용하라고 하고있다. 의존성 주입 방식은 아래와 같이 3가지가 있다. 생성자 주입 (권장) 설정자 주입 (Setter Injection) 필드 주입 (@Autowired 선언) 그렇다면 이 3가지의 의존성 주입 방식 중에서 생성자 주입 방식을 사용함으로써 얻을 수 있는 이점은 무..

Spring Rest Docs 소개 및 사용법

Spring Rest Docs 소개 Spring Rest Docs 공식 레퍼런스 Spring MVC test를 사용해서 문서의 일부분을 생성해낼 때 유용한 기능을 제공해주는 라이브러리이다. 우리가 만든 테스트를 실행할 때 사용하는 요청과 응답 및 응답에 실려나오는 header 이런 정보를 사용해서, 문서조각을 만들 수 있다. 이런 문서조각을 모아서 REST API Documentation을 완성할 수 있다(html로 만들어진다). Spring Rest Docs는 Asciidoctor 툴을 사용한다. plain text로 작성한 문서를 특별한 문법에 맞춰(mark down과 유사) page 문서로 만들어준다. Rest Docs는 Test와 통합할 수 있는 방법 MockMvc Spring Data REST ..

스프링의 특징과 의존성 주입

해당 내용은 '코드로 배우는 스프링 웹 프로젝트' 책을 공부하면서 정리한 내용입니다. 프레임 워크는 '뼈대나 근간을 이루는 코드들의 묶음' 이라고 할 수 있다. 스프링은 인기 있는 프레임워크이다. 많은 프레임워크 중에서도 스프링 프레임워크가 인기있는 이유는 아래와 같은 스프링의 차별성 때문이다. 복잡함에 반기를 들어서 만들어진 프레임워크 엔터프라이즈급 프레임워크들의 가장 큰 문제는 복잡하다는 것이었다. 많은 케이스를 처리할 수 있고 다양한 기능을 가지다보니 하나의 기능을 위해 너무 많은 구조가 필요한 상태였다. 이러한 복잡성을 해결하기 위해서 나온 경량화된 프레임워크가 스프링이다. 일반적인 Java의 클래스와 인터페이스를 이용하는 구조여서 진입 장벽이 높지 않고, 복잡한 프레임워크에 비해 가벼워 빠르게 ..

@Get, @Post, Delete, @Put, @Patch Mapping

Spring Boot를 이용하면서 이전에는 (주로 4.3.18 버전의 스프링 MVC를 사용했다) 보지 못했던 맵핑들이 자주 보였는데, 그 중에서도 Mapping 부분에 Get, Post 등이 직접 언급되는 어노테이션이 나오는 걸 보고 구글링하게 되었다. 간략한 내용이지만 정리를 해보려고 한다. (혹시나 헷갈리면 확인하기 위함이 제일 클 것 같다!) Spring 4.3 버전에 추가된 내용으로는 기존의 @RequestMapping(value="/", method="...")의 어노테이션의 사용을 조금 더 명시적이고 효율적인 방법으로 사용하기 위해 @GetMapping, @PostMapping, @DeleteMapping, @PutMapping, @PatchMapping의 메소드가 생겼다. 기본적인 작동원리 ..

Spring Framework Module

[Spring] 싱글톤 레지스트지와 Bean Scope

728x90

스프링의 애플리케이션 컨텍스트가 관리하는 객체(=Bean)는 일반적인 POJO 객체와는 중요한 차이점이 있다.

예를들어서 아래의 두 코드는 언뜻보면 비슷해보이지만, 큰 차이가 있다.

public class AppConfig {

    public MemberService memberService() {
        return new MemberServiceImpl(new MemoryMemberRepository());
        return new MemberServiceImpl(memberRepository());
    }

    public OrderService orderService() {
        return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

    private MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

    public DiscountPolicy discountPolicy() {
        return new FixDiscountPolicy();
    }

}
@Configuration
public class AppConfig {

    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }

    @Bean
    public OrderService orderService() {
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

    private MemberRepository memberRepository() {
    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

    @Bean
    public DiscountPolicy discountPolicy() {
        return new FixDiscountPolicy();
    }

동일성은 두 객체의 주소 값이 같은지 확인하는 것이고 (==), 동등성은 두 객체의 내부 값이 일치하는지 확인하는 것이다!

전자(= 오브젝트 팩토리)의 AppConfig에서 memberRepository()를 여러번 호출하면 어떤 결과가 나올까?
결과적으로 매번 새로운 오브젝트가 생성된다. 따라서 동일성 비교를 하게되면, false라는 결과가 나오게 된다.

하지만 후자(= 스프링 애플리케이션 컨텍스트)의 AppCofig는 어떻게 될까? 놀랍게도 객체의 주소 값이 동일하다.
스프링은 여러 번에 걸쳐 빈을 요청하더라도 매번 동일한 오브젝트를 돌려준다는 것이다. 이는, 스프링 애플리케이션 컨텍스트가 싱글톤 레지스트리이기 때문이다.

스프링 애플리케이션 컨텍스트 동작방식

애플리케이션 컨텍스트는 전자와 같이 오브젝트 팩토리와 비슷한 방식으로 동작하는 IoC(Inversion Of Controller) 컨테이너이다.
그러면서 동시에 이 애플리케이션 컨텍스트는 싱글톤을 저장하고 관리하는 싱글톤 레지스트리(singleton registry)이기도 하다.

스프링은 기본적으로 내부에서 생성하는 빈 오브젝트를 모두 싱글톤으로 만든다.
여기서 말하는 싱글톤은 디자인 패턴 중 하나인 싱글톤 패턴과 유사하지만 구현 방법이 다르다.

스프링이 빈을 싱글톤으로 만드는 이유는?

그 이유는, 자바 엔터프라이즈 기술을 사용하는 서버환경이기 때문이다.
태생적으로 스프링은 엔터프라이즈 시스템을 위해 고안된 기술이기 때문에 서버환경에서 사용될 때 그 진가가 드러난다.

스프링이 처음 설계됐던 대규모의 엔터프라이즈 서버환경은 서버 하나당 최대로 초당 수십에서 수백번씩 브라우저나 여타 시스템으로부터의 요청을 받아 처리할 수 있는 높은 성능이 요구되는 환경이었다. 또 하나의 요청을 처리하기 위해 데이터 액세스 로직, 서비스 로직, 비즈니스 로직, 프레젠테이션 로직 등의 다양한 기능을 담당하는 오브젝트들이 참여하는 계층형 구조로 이뤄진 경우가 대부분이다.

만약 매번 클라이언트에서 요청이 올 때마다 각 로직을 담당하는 객체를 새로 생성해서 사용한다고 생각해보자.
매 요청마다 각 계층의 객체들이 생성될 것이고, 그 요청의 양도 적지 않다. 아무리 자바의 오브젝트 생성과 가비지 컬렉션(GC)의 성능이 좋아졌다고 한들 이렇게 부하가 걸리면 서버가 감당하기 힘들다.

그래서 엔터프라이즈 분야에서는 서비스 오브젝트라는 개념을 일찍부터 사용해왔다. 서블릿은 자바 엔터프라이즈 기술의 가장 기본이 되는 서비스 오브젝트라고 할 수 있다. 대부분 멀티스레드 환경에서 싱글톤으로 동작한다. 서블릿 클래스당 하나의 오브젝트만 만들어주고, 사용자의 요청을 담당하는 여러 스레드에서 하나씩 오브젝트를 공유해 동시에 사용한다.

이렇게 애플리케이션 안에 제한된 수, 대게 한 개의 오브젝트만 만들어서 사용하는 것이 싱글톤 패턴의 원리다. 따라서 서버환경에서는 서비스 싱글톤의 사용이 권장된다. 하지만 일반적으로 싱글톤 패턴은 사용하기도 어렵고 문제점도 많아서, 안티패턴이라는 인식이 대다수다.

싱글톤 패턴의 한계

일반적으로 자바에서 싱글톤을 구현하는 방법은 아래와 같다.

  • 클래스 외부에서 오브젝트를 생성하지 못하도록 생성자를 private로 만든다.
  • 생성된 싱글톤 오브젝트를 저장할 수 있는 자신과 같은 타입의 스태틱 필드를 정의한다.
  • 스태틱 팩토리 메서드인 getInstance()를 만들고 이 메서드가 최초로 호출되는 시점에서 한번만 오브젝트가 만들어지게 한다.
    • 생성된 오브젝트는 스태틱 필드에 저장
    • 또는 스태틱 필드의 초기값으로 오브젝트를 미리 만들어둘 수도 있다.
  • 한번 오브젝트(= 싱글톤)가 만들어지고 난 후에는 getInstance() 메서드를 통해 이미 만들어져 스태틱 필드에 저장해둔 오브젝트를 넘겨준다.

일반적으로 싱글톤 패턴 구현 방식에는 다음과 같은 문제가 있다.

priavet 생성자를 갖고 있기 때문에 상속할 수 없다.

싱글톤 패턴은 생성자를 priavet으로 제한한다. 오직 싱글톤 클래스 자신만이 자기 오브젝트를 만들도록 제한하는 것이다.
문제는 private 생성자를 가진 클래스는 다른 생성자가 없다면 상속이 불가능하다는 점이다.
객체지향의 장점인 상속과 이를 이용한 다형성을 적용할 수가 없다.
기술적인 서비스만 제공하는 경우라면 상관없겠지만, 애플리케이션의 로직을 담고 있는 일반 오브젝트의 경우 싱글톤으로 만들었을 때, 객체지향적인 설계의 장점을 적용하기가 어렵다는 것이다. 또한 상속과 다형성 같은 객체지향의 특징이 적용되지 않는 static 필드와 메서드를 사용하는 것도 역시 동일한 문제를 발생시킨다.

싱글톤은 테스트하기가 힘들다.

싱글톤은 테스트하기 어렵거나 테스트 방법에 따라 아예 테스트가 불가능하다.
싱글톤은 만들어지는 방식이 제한적이기 때문에 테스트에서 사용될 때 목 오브젝트 등으로 대체하기가 힘들다.
싱글톤은 초기화 과정에서 생성자 등을 통해 사용할 오브젝트를 다이나믹하게 주입하기도 힘들기 때문에 필요한 오브젝트는 직접 오브젝트를 만들어 사용할 수밖에 없다. 이런 경우 테스트용 오브젝트로 대체하기가 힘들다.

서버환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못한다.

서버에서 클래스 로더를 어떻게 구성하고 있느냐에 따라서 싱글톤 클래스임에도 하나 이상의 오브젝트가 만들어질 수도 있다.
따라서 자바 언어를 이용한 싱글톤 패턴 기법은 서버환경에서는 싱글톤이 꼭 보장된다고 볼 수 없다.
여러 개의 JVM에 분산돼서 설치가 되는 경우에도 각각 독립적으로 오브젝트가 생기기 때문에 싱글톤으로서의 가치가 떨어진다.

싱글톤의 사용은 전역 상태를 만들 수 있기 때문에 바람직하지 못하다.

싱글톤은 사용하는 클라이언트가 정해져 있지 않다. 싱글톤의 static 메서드를 이용해 언제든지 싱글톤에 쉽게 접근할 수 있기 때문에 애플리케이션 어디서든지 사용될 수 있고, 그러다 보면 자연스럽게 전역 상태(global state)로 사용되기 쉽다. 아무 객체나 자유롭게 접근하고 수정하고 공유할 수 있는 전역 상태를 갖는 것은 객체지향 프로그래밍에서는 권장되지 않는 프로그래밍 모델이다. 그럼에도 싱글톤을 상요하면 그런 유혹에 빠지기 쉽다. 그럴 바에는 아예 스태틱 필드와 메서드로만 구성된 클래스를 사용하는 것이 낫다.

왜 싱글톤 패턴이 안티 패턴이라 불리는지 조금은 감이 올 것 같다.

싱글톤 레지스트리

스프링은 서버환경에서 싱글톤이 만들어져서 서비스 오브젝트 방식으로 사용되는 것을 적극 지지한다.
하지만 자바의 기본적인 싱글톤 패턴의 구현 방식은 여러 가지 단점이 있기 때문에, 스프링은 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공한다.
그것이 바로 싱글톤 레지스트리(singleton registry)다. 스프링 컨테이너는 싱글톤을 생성하고, 관리하고, 공급하는 싱글톤 관리 컨테이너이기도 하다.
싱글톤 레지스트리의 장점은 스태틱 메서드와 private 생성자를 사용해야하는 비정상적인 클래스가 아니라 평범한 자바 클래스를 싱글톤으로 활용하게 해준다는 점이다. 평범한 자바 클래스라도 IoC 방식의 컨테이너를 사용해서 생성과 관계설정, 사용 등에 대한 제어권을 컨테이너에게 넘기면 손쉽게 싱글톤 방식으로 만들어져 관리되게 할 수 있다. 오브젝트 생성에 관한 모든 권한은 IoC 기능을 제공하는 애플리케이션 컨텍스트에게 있기 때문이다.

스프링의 싱글톤 레지스트리 덕분에 싱글톤 방식으로 사용될 애플리케이션 클래스라도 public 생성자를 가질 수 있다.
싱글톤으로 사용돼야하는 환경이 아니라면 간단히 오브젝트를 생성해서 사용할 수 있다.
따라서 테스트 환경에서 자유롭게 오브젝트를 만들 수 있고, 테스트를 위한 목 오브젝트로 대체하는 것도 간단하다.

가장 중요한 것은 싱글톤 패턴과 달리 스프링이 지지하는 객체지향적인 설계 방식과 원칙, 디자인 패턴(싱글톤 패턴 제외) 등을 적용하는데 아무런 제약이 없다는 점이다. 스프링은 IoC 컨테이너일 뿐만 아니라, 고전적인 싱글톤 패턴을 대신해서 싱글톤을 만들고 관리해주는 싱글톤 레지스트리라는 점을 기억해두자.
스프링이 빈을 싱글톤으로 만드는 것은 결국 오브젝트의 생성 방법을 제어하는 IoC 컨테이너로서의 역할이다.

다음은 싱글톤으로 만들어지기 때문에 주의해야 할 점에 대해 알아보자!

싱글톤과 오브젝트의 상태

싱글톤은 멀티스레드 환경이라면 여러 스레드가 동시에 접근해서 사용할 수 있다.
따라서 상태 관리에 주의를 기울여야 한다. 기본적으로 싱글톤이 멀티스레드 환경에서 서비스 형태의 오브젝트로 사용되는 경우에는 상태정보를 내부에 갖고있지 않은 무상태(stateless) 방식으로 만들어야 한다. 다중 사용자의 요청을 한꺼번에 처리하는 스레드들이 동시에 싱글톤 오브젝트의 인스턴스 변수를 수정하는 것은 매우 위험하다. 저장할 공간이 하나뿐이니 서로 값을 덮어쓰고 자신이 저장하지 않은 값을 읽어올 수 있기 때문이다.
따라서 싱글톤을 기본적으로 인스턴스 필드의 값을 변경하고 유지하는 상태유지(stateful)방식으로 만들지 않는다.
이를 지키지 않으면 개발자 혼자서 개발하고 테스트할 때는 아무런 문제가 없겠지만, 서버에 배포되고 여러 사용자가 동시에 접속하면 데이터가 엉망이 돼버리는 등의 심각한 문제가 발생할 것이다. 물론 읽기전용의 값이라면 초기화 시점에서 인스턴스 변수에 저장해두고 공유하는 것은 아무 문제가 없다.

상태가 없는 방식으로 클래스를 만드는 경우에 각 요청에 대한 정보나, DB나 서버의 리소스로부터 생성한 정보는 어떻게 다뤄야 할까?
이때는 파라미터와 로컬 변수, 리턴 값 등을 이용하면 된다. 메서드 파라미터나, 메서드 안에서 생성되는 로컬 변수는 매번 새로운 값을 저장할 독립적인 공간이 만들어지기 때문에 싱글톤이라고 해도 여러 스레드가 변수의 값을 덮어쓸 일이 없다.

인스턴스 변수로 사용할 수 있는 것은 스프링 컨테이너가 관리하는 또 다른 Bean 혹은 읽기 전용 값 등이 허용된다.
스프링이 한 번 초기화해주고 나면 이후에는 수정되지 않기 때문에 멀티스레드 환경에서 사용해도 아무런 문제가 없다.
또한 단순한 읽기전용 값이라면 static final이나 final로 선언하는 편이 나을 것이다.

스프링 빈의 스코프

스프링에서는 스프링이 관리하는 오브젝트, 즉 빈이 생성되고 존재하고, 적용되는 범위빈의 스코프(scope)라고 한다.
우리는 앞에서 스프링 컨텍스트가 빈을 싱글톤으로 관리한다고 했었다.

그렇다면 스프링의 모든 빈은 싱글톤으로만 존재할까?

아니다! 싱글톤 외에도 다른 빈 스코프가 존재한다.
먼저 스프링 빈의 기본 스코프는 싱글톤이다. 싱글톤 스코프는 컨테이너 내에 한 개의 오브젝트만 만들어져서, 강제로 제거하지 않는 한 스프링 컨테이너가 존재하는 동안 계속 유지된다. 스프링에서 만들어지는 대부분의 빈은 싱글톤 스코프를 갖는다.

경우에 따라서는 싱글톤 외의 스코프를 가질 수 있다.
대표적으로 프로토타입(prototype) 스코프가 있다. 프로토타입은 싱글톤과 달리 컨테이너에 빈을 요청할 때마다 매번 새로운 오브젝트를 만들어준다.
그 외에도 웹을 통해 새로운 HTTP 요청이 생길 때마다 생성되는 요청(request) 스코프가 있고, 웹의 세션과 스코프가 유사한 세션(session) 스코프가 있다.

사실 이 외에도 더 있다. 해당 내용에 대해서는 다음 블로깅에서 정리해보려고한다.

728x90
Spring Framework Module

Spring - 의존성 주입(DI)을 '생성자 주입 방식'으로 권장하는 이유

728x90

필드 주입 방식으로 의존성을 주입하는 방식에 익숙한(?) 개발자도 있을 것이다. 하지만 스프링에서 필드 주입 방식은 선호하지 않는다.

필드 주입 방식을 선호하지 않는다고 한다.

그렇다면 스프링은 어떤 방식을 선호할까? 퀵 액션(맥 기준 ⌥ + ↩︎) 버튼을 누르면 아래와 같이 나타난다.

그 결과는 생성자 주입방식으로 변경해준다. 그 내용을 더 상세히보면

처음 언급했던 것 처럼, Spring Team은 의존성 주입은 항상 생성자를 사용하라고 하고있다.

의존성 주입 방식은 아래와 같이 3가지가 있다.

  • 생성자 주입 (권장)
  • 설정자 주입 (Setter Injection)
  • 필드 주입 (@Autowired 선언)

그렇다면 이 3가지의 의존성 주입 방식 중에서 생성자 주입 방식을 사용함으로써 얻을 수 있는 이점은 무엇일까?

    1. 단일 책임의 원칙
      생성자의 인자가 많을 경우 코드량도 많아지고, 의존관계도 많아져 단일 책임의 원칙에 위배된다. 그래서 생성자 주입 방식을 사용함으로써 의존관계, 복잡성을 쉽게 알수 있어 리팩토링의 실마리를 제공한다
    1. 테스트 용이성
      DI컨테이너에서 관리되는 클래스는 특정 DI 컨테이너에 의존하지않고 POJO여야 한다. DI 컨테이너를 사용하지 않고도 인스턴스화 할 수 있고, 단위 테스트도 가능하며, 다른 DI 프레임 워크로 전환할 수도 있게 된다.
    1. Immutability
      생성자 주입에서는 필드는 final로 선언할 수 있다(위에서도 자동으로 final 변수로 선언했다). 불변 객체가 가능한데 비해 '필드 주입' 방식은 final을 선언할 수 없기 때문에 객체가 변경 가능한 상태가 된다.
    1. 순환 의존성
      생성자 주입에서는 멤버 객체가 순환 의존성을 가질 경우 BeanCurrentlyIncreationException() 이 발생해서 순환 의존성을 알 수 있게 된다.
    1. 의존성 명시
      의존 객체 중 필수는 생성자 주입을 옵션인 경우는 Setter Injection을 활용할 수 있다.

글을 끝마치며...

필자 같은 경우 스프링을 사용할 때, 생성자 주입방식을 적극적으로 사용하고 있는데 이러한 장점들을 정확히 알고 사용했다기보다는 '스프링이 권장하는 방식'이라는 것 때문에 맹목적으로 사용했는데 이러한 특징을 알고 사용하는 것이 중요하다 생각하여 왜? 사용하지라는 의문이 들었다. 누군가 왜 의존성 주입을 생성자 주입 방식을 사용하나요? 라고 했을 때, 근사하게 설명할 수 있지 않을까?


참고자료

스프링부트를 이용한 웹 서비스 개발 T아카데미
왜 Constructor Injection을 사용해야 하는가?

728x90
Spring Framework Module/SpringBoot

Spring Rest Docs 소개 및 사용법

728x90

Spring Rest Docs 소개

Spring Rest Docs 공식 레퍼런스

Spring MVC test를 사용해서 문서의 일부분을 생성해낼 때 유용한 기능을 제공해주는 라이브러리이다.

우리가 만든 테스트를 실행할 때 사용하는 요청과 응답 및 응답에 실려나오는 header 이런 정보를 사용해서, 문서조각을 만들 수 있다. 이런 문서조각을 모아서 REST API Documentation을 완성할 수 있다(html로 만들어진다). Spring Rest Docs는 Asciidoctor 툴을 사용한다. plain text로 작성한 문서를 특별한 문법에 맞춰(mark down과 유사) page 문서로 만들어준다.

Rest Docs는 Test와 통합할 수 있는 방법

  1. MockMvc
    • Spring Data REST
    • Spring HATEOAS
  2. WebTestClient (Spring 5부터 지원)
    • WebTestClient
  3. REST Assured
    • Grails
    • REST Assured
  4. Advanced
    • Slate
    • TestNG
    • JUnit5

출처: Spring REST Docs

연동 방법 - MockMvc (Spring legacy 사용시)

private MockMvc mockMvc; 

@Autowired 
private WebApplicationContext context; 

@Before
public void setUp() {
    this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
            .apply(DocumentationConfiguration(this.restDocumentation))
            .build();
}

위의 코드는 JUnit 4를 기준으로 작성된 것 JUnit 5를 사용하는 경우, 아래를 참조하여 작성

JUnit 4 JUnit 5
@BeforeClass @BeforeAll
@Before @BeforeEach
@After @AfterEach
@AfterClass @AfterAll

연동 방법 - MockMvc (SpringBoot 사용시)

애노테이션 하나만 추가하면 간편하게 설정할 수 있다.

테스트 위에 @AutoConfigureRestDocs를 추가하면 된다.

사용 방법 (연동 후)

this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
    .andExpect(status().isOk())
    .andDo(document("index", links(halLinks(), // (1)
        linkWithRel("alpha").description("Link to the alpha resource"), // (2)
        links(linkWithRel("bravo").description("Link to the bravo resource")))); // (3) 

설명

  • (1): document라는 메서드를 사용해서 현재 이 테스트를 실행한 그 결과를 어떤 디렉토리, 어떤 이름으로 만들지 정해주는 것, "index" 문서 아래에 여러가지 snippets들이 만들어지게 된다.
  • (2):

Swagger라는 자동화된 문서화 도구도 있지만 Rest Docs를 선호하는 이유 ?

우리가 API 코드를 변경했을 때, 테스트 코드 또한 변경할 것이다. 그때, 문서도 자동으로 같이 바뀐다. 테스트를 하지않은 내용 또한 추가로 생기면 그것 또한 문서화하도록 강제할 수 있다.

Swagger를 사용하는 것도 무관하지만 코드를 변경했는데(=API가 변경됐는데) 문서가 안바뀔 수도 있는 문제를 미연에 방지할 수 있기 떄문에 Spring Rest Docs를 선호한다.

snippets

  • links()
  • requestParameters() + parameterWithName()
  • pathParameters() + parametersWithName()
  • requestParts() + partWIthName()
  • requestPartBody()
  • requestPartFields()
  • requestHeaders() + headerWithName()
  • requestFields() + fieldWithPath()
  • responseHeaders() + headerWithName()
  • responseFields() + fieldWithPath()
  • 이 외에도 다양한 정보들을 문서화할 수 있다.

Spring REST Docs 사용법

STEP 1. 의존성 추가하기 (프로젝트를 생성할 때, 이미 추가했다면 생략해도 무방)

image

STEP 2. Spring Rest Docs를 적용하고 싶다면, 만든 테스트 위에 @AutoConfigureRestDocs 추가한다
image

그러면 이제부터 REST Docs를 사용할 수 있는데..

STEP 3. REST Docs를 처음 사용하는 방법

.andDo(document(문서의 이름))

위와 같이 시작하면 된다.

ex)
image

위의 예제 테스트를 실행하면 아래와 같이 generated-snippets 이라는 폴더가 생겼고 그 안에 create-event가 생성된다. (원래는 target 폴더에 생성되는 것으로 예상했는데, 왜 난 build에 생기지...?)

image

그 결과 중 http-request를 봤을 때, 아래와 같이 포맷팅이 안되어 있어서 보기가 불편하다.

image

포맷팅을 하기위해서는 RestDocMockMvc 커스텀마이징이 필요하다.

커스텀마이징 방법은 test 패키지에....

  1. RestDocsMockMvcConfigurationCustomizer를 구현한 빈 등록
  2. @TestConfiguration // Test에서만 사용하는 Configuration이라고 알려주는 것

FunctionalInterface인 RestDocsMockMvcConfigurationCustomizer의 configurer만 재정의하면 된다. (예제에서는 request와 response를 이쁘게(prettyPrint()) 출력하도록 재정의한다.

image

RestDocsMockMvcConfigurationCustomizer 구현 예제
image

구현한 빈을 import 해준다!
image

그리고 실행한다

request value가 포맷팅되었다!
image

(prettyPrint는 Preprocessors 중 하나이다 공식 레퍼런스를 보면 이 외에도 다양한 프로세서가 있다)

참고자료: REST API 개발 - 백기선님 강의

참고하면 좋을 것 같은 자료: Spring REST Docs - Yun 블로그

728x90

'Spring Framework Module > SpringBoot' 카테고리의 다른 글

[Spring] 옵션 처리  (0) 2023.08.02
Spring Framework Module

스프링의 특징과 의존성 주입

728x90

해당 내용은 '코드로 배우는 스프링 웹 프로젝트' 책을 공부하면서 정리한 내용입니다. 

 프레임 워크는 '뼈대나 근간을 이루는 코드들의 묶음' 이라고 할 수 있다. 스프링은 인기 있는 프레임워크이다. 많은 프레임워크 중에서도 스프링 프레임워크가 인기있는 이유는 아래와 같은 스프링의 차별성 때문이다.

  • 복잡함에 반기를 들어서 만들어진 프레임워크
    엔터프라이즈급 프레임워크들의 가장 큰 문제는 복잡하다는 것이었다. 많은 케이스를 처리할 수 있고 다양한 기능을 가지다보니 하나의 기능을 위해 너무 많은 구조가 필요한 상태였다. 이러한 복잡성을 해결하기 위해서 나온 경량화된 프레임워크가 스프링이다. 일반적인 Java의 클래스와 인터페이스를 이용하는 구조여서 진입 장벽이 높지 않고, 복잡한 프레임워크에 비해 가벼워 빠르게 엔터프라이즈급의 시스템을 작성할 수 있다. 

  • 프로젝트의 전체 구조를 설계할 때 유용한 프레임워크
    다른 프레임워크들은 웹이나 데이터베이스 영역 등의 전문적인 영역에 대해서만 지원하는 경우가 많았고, 비즈니스 로직을 처리하는 부분에 대한 설계는 개발자의 역량에 맡기는 경우가 많았다. 반면에 스프링은 어느 한 분야에 집중하지 않고, 전체를 설계하는 용도로 사용될 수 있었다. 스프링이 Web이라는 제한된 영역에 많이 사용되기는 하지만, 근본적인 사상 자체는 OOP 구조를 뒷받침하고 구조를 설계하는 사상이다. 스프링의 큰 특징중 하나인 의존성 주입(DI)가 이에 대한 설명이다.

  • 다른 프레임워크들의 포옹
    스프링은 전체 구조에 집중했기 때문에 특정한 영역의 프레임워크와 공존하는 방식으로 사용할 수 있었다. 다른 프레임워크들은 특정 프레임워크를 채택하면 해당 영역 전체를 수정해야 하는 고질적인 문제를 가지고 있는 반면에 스프링은 다른 프레임워크들과의 통합을 지원했기 때문에 최소한의 수정이 가능했다. 스프링의 최대 장점은 기본 뼈대를 흔들지 않고, 여러 종류의 프레임워크를 혼용해서 사용할 수 있다는 점이다.

  • 개발 생산성과 개발도구의 지원
    스프링의 경우 이론적으로는 개발자가 제대로 이해해야 하는 부분이 많았지만, 결과적으로 코드의 양은 확실히 줄어들 수 있었고, 유지 보수에 있어서도 XML의 설정 등을 이용했기 때문에 환영받을 수 있었다. STS나 Eclipse, Intellij 등의 플러그인의 지원 역시 다른 프레임워크들에 비해서 빠른 업데이트가 되었기 때문에 별도의 새로운 개발도구에 대한 적응이 없이도 개발이 가능했다. 

스프링의 주요한 변화는 다음과 같다.

  • Spring 2.5버전 : 어노테이션을 활용하는 설정을 도입하면서 편리한 설정과 개발이 가능하도록 지원
  • Spring 3.0버전 : 별도의 설정 없이도 Java 클래스만으로 설정 파일을 대신할 수 있게 지원 
  • Spring 4.0버전 : 모바일 환경과 웹 환경에서 많이 사용되는 REST 방식의 컨트롤러 지원
  • Spring 5.0버전 : Reactor를 이용한 Reactive 스타일의 개발 환경 지원'

스프링의 주요 특징

  • POJO 기반의 구성 
  • 의존성 주입(Dependency Injection)을 통한 객체 간의 관계 구성 
  • AOP(Aspect-Oriented-Programming) 지원
  • 편리한 MVC 구조
  • WAS의 종속적이지 않은 개발 환경

POJO 기반의 구성 

스프링의 성격 자체가 가벼운 프레임워크지만, 그 내부에는 객체 간의 관계를 구성할 수 있는 특징을 가지고 있다. 스프링은 다른 프레임워크들과 달리 이 관계를 구성할 때 별도의 API 등을 사용하지 않는 POJO(Plain Old Java Object)의 구성만으로 가능하도록 제작되어 있다. 쉽게 말해 일반적인 Java 코드를 이용해서 객체를 구성하는 방식을 그대로 스프링에서 사용할 수 있다는 얘기다. 

이것이 중요한 이유는 코드를 개발할 때 개발자가 특정한 라이브러리나 컨테이너의 기술에 종속적이지 않다는 것을 의미하기 때문이다. 개발자는 가장 일반적인 형태로 코드를 작성하고 실행할 수 있기 때문에 생산성에도 유리하고, 코드에 대한 테스트 작업 역시 좀 더 유연하게 할 수 있다는 장점이 생긴다. 

의존성 주입(DI)과 스프링

스프링에 대한 얘기를 하면서 빠지지 않는 개념이 이 '의존성 주입'이라는 개념이다. 의존성(Dependency)이라는 것은 하나의 객체가 다른 객체 없이 제대로 된 역할을 할 수 없다는 것을 의미한다. (흔히 A 객체가 B 객체 없이 동작이 불가능한 상황을 'A가 B에 의존적이다'라고 표현한다) 

'주입(Injection)'은 말 그대로 외부에서 '밀어 넣는 것'을 의미한다. 의존성과 주입을 결합해서 생각해 보면 '어떤 객체가 필요한 객체를 외부에서 밀어 넣는다'는 의미가 된다. 그렇다면 왜 외부에서 객체를 주입하는 방식을 사용하는지에 대한 의문이 들것이다. 그 이유는 '주입을 받는 입장에서는 어떤 객체인지 신경 쓸 필요가 없다'는 것이다. 즉 '어떤 객체에 의존하든 자신이 역할은 변하지 않는다'와 같은 의미이다. 

'의존성 주입' 방식을 사용하려면 추가적인 하나의 존재가 필요하게 된다. 이 존재는 의존성이 필요한 객체를 찾아서 '주입'하는 역할을 하게된다. 

스프링은 이러한 구조를 만드는데 적합한 구조로 설계되어 있다. 스프링에서는 'ApplicationContext'라는 존재가 필요한 객체들을 생성하고, 필요한 객체들을 주입하는 역할을 해주는구조이다. 따라서 스프링을 이용하면 개발자들은 기존의 프로그래밍과달리 객체와 객체를 분리해서 생성하고, 이러한 객체들을 엮는(wiring) 작업을 하는 형태의 개발을 하게 된다. 스프링에서는 ApplicationContext가 관리하는 객체들을 '빈(Bean)'이라는 용어로 부르고, 빈과 빈 사이의 의존관계를 처리하는 방식으로 XML 설정, 어노테이션 설정, Java 설정 방식을 이용할 수 있다

AOP의 지원

좋은 개발환경의 중요 원칙은 '개발자가 비즈니스 로직에만 집중할 수 있게 한다.' 이다. 이 목표를 이루기 위해서는 몇 가지 중요한 원칙이 있지만, 가장 쉽게 생각할 수 있는 것이 '반복적인 코드의 제거'라고 할 수 있다. 스프링은 프레임워크를 이용한 개발은 이러한 반복적인 코드를 줄이고, 핵심 비즈니스 로직에만 집중할 수 있다.

대부분의 시스템이 공통으로 가지고 있는 보안, 로그, 트랜잭션 같이 비즈니스 로직은 아니지만, 반드시 처리가 필요한 부분을 스프링에서는 '횡단 관심사(cross-concern)'라고 한다. 스프링은 이러한 횡단 관심사를 분리해서 제작하는 것이 가능하다. AOP(Aspect Orinted Programming)는 이러한 횡단 관심사를 모듈로 분리하는 프로그래밍의 패러다임이다. 

스프링은 AOP를 AspectJ의 문법을 통해서 작성할 수 있는데, 이를 통해서 개발자는 1) 핵심 비즈니스 로직에만 집중해서 코드를 개발할 수 있게 되었고, 2) 각 프로젝트마다 다른 관심사를 적용할 때 코드의 수정을 최소화시킬 수 있었으며, 3) 원하는 관심사의 유지보수가 수월한 코드를 구성할 수 있다. 

트랜잭션의 지원

데이터베이스를 이용할 때 반드시 신경 써야 하는 부분은 하나의 업무가 여러 작업으로 이루어지는 경우의 트랜잭션 처리이다. 이 트랜잭션 처리는 상황에 따라서 복잡하게 구성될 수도 있고, 아닐 수도 있는데, 그때마다 코드를 이용해서 처리하는 작업은 개발자에게는 상당히 피곤한 일이다. 스프링은 이런 트랜잭션의 관리를 어노테이션이나 XML로 설정할 수 있기 때문에 개발자가 매번 상황에 맞는 코드를 작성할 필요가 없도록 설계되었다. 

 

스프링이 동작하면 생기는 일 

  • 스프링 프레임워크가 시작되면 먼저 스프링이 사용하는 메모리 영역을 만들게 되는데 이를 컨텍스트(Context)라고 한다. 스프링에서는 ApplicationContext라는 이름의 객체가 만들어진다. 
  • 스프링은 자신이 객체를 생성하고 관리해야 하는 객체들에 대한 설정이 필요하다. 이에 대한 설정이 root-context.xml 파일이다.
  • root-context.xml에 설정되어 있는 <context:component-scan> 태그의 내용을 통해서 'org.zerock.sample' 패키지를 스캔(scan)하기 시작한다. 
  • 해당 패키지에 있는 클래스들 중에서 스프링이 사용하는 @Component라는 어노테이션이 존재하는 클래스의 인스턴스를 생성한다. 
  • Restaurant 객체는 Chef 객체가 필요하다는 어노테이션(@Autowired) 설정이 있으므로, 스프링은  Chef 객체의 레퍼런스를 Restaurant 객체에 주입한다. 

(Restaurant 객체의 필드에 Chef가 있고 이는 @Autowird 속성이 있다) 

 

728x90
Spring Framework Module

@Get, @Post, Delete, @Put, @Patch Mapping

728x90

Spring Boot를 이용하면서 이전에는 (주로 4.3.18 버전의 스프링 MVC를 사용했다) 보지 못했던 맵핑들이 자주 보였는데, 그 중에서도 Mapping 부분에 Get, Post 등이 직접 언급되는 어노테이션이 나오는 걸 보고 구글링하게 되었다. 간략한 내용이지만 정리를 해보려고 한다. (혹시나 헷갈리면 확인하기 위함이 제일 클 것 같다!)

 

Spring 4.3 버전에 추가된 내용으로는 기존의 @RequestMapping(value="/", method="...")의 어노테이션의 사용을 조금 더 명시적이고 효율적인 방법으로 사용하기 위해 @GetMapping, @PostMapping, @DeleteMapping, @PutMapping, @PatchMapping의 메소드가 생겼다.

기본적인 작동원리

요청( HTTP(S) ) @맵핑 어노테이션
GET @GetMapping
POST @PostMapping
DELETE @DeleteMapping
PATCH, PUT @PatchMapping, @PutMapping

 

기존의 요청 방식

@RequestMapping(value = "/test", method = RequestMethod.GET
public void TestCase(HttpServletRequest request, HttpServletResponse response) {
   // do something
} 

 

- @GetMapping 사용 방법(다른 것도 동일)

@GetMapping(value = "/test2")
public void TestCase2(HttpServletRequest request, HttpServletResponse response) {
  // do something
}

 

위에서 GetMapping 을 상황에 따라서 @PostMapping, @DeleteMapping, @PatchMapping, @PutMapping 등으로 바꾸기만 하면 된다.

더 명시적이다는 면에서 코드 가독성이 올라간다.(주관적인 의견이지만 대부분이 그렇다고 생각할 것이다. 코드를 보는데 노력하지 않아도 한눈에 보이는 장점이 있다)

728x90