본문 바로가기

(4)

[Spring] NoUniqueBeanDefinitionException 예외 해결방법

이 해결법을 모르는 것은 아니지만 이 현상을 자주 맞이하는게 아니다보니, 늘 쓰는방식만쓰고 지나갔던 기억이 있다. 이번 기회에 정리해두고 똑같은 상황을 맞이했을 때 조금 더 현명(?)하게 대처할 수 있도록 기록을 남긴다. 총 3가지 예외 해결방법이 있다. @Autowired 필드명 매칭 @Qualifier -> @Qualifier끼리 매칭 (Bean 이름 매칭) @Primary 1. @Autowired 필드명 매칭 Autowired는 우선적으로 타입 매칭을 시도하고 타입이 매칭되는 빈이 여러개라면 이름(파라미터 이름)으로 빈 이름을 매핑한다. (필드명 매칭은 타입 매칭 결과가 1개뿐이라면 발생하지 않는다) @Autowired private FooComponent foo; // FooComponent 타입..

[Spring Batch] 구조

Batch란…? 사전 정의 큰 단위의 작업을 일괄 처리 대부분 처리량이 많고 비 실시간성 처리에 사용 대용량 데이터 계산, 정산, 통계, 데이터베이스, 변환 etc… 컴퓨터 자원을 최대로 활용 컴퓨터 자원 사용이 낮은 시간대에 배치를 처리하거나 배치만 처리하기 위해 사용자가 사용하지 않는 또 다른 컴퓨터 자원을 사용 사용자 상호작용으로 실행되기 보단, 스케줄러 같은 시스템에 의해 실행되는 대상 ex) 매일 오전 10시에 배치 실행, 매주 월요일 12시 마다 실행 etc… crontab, jenkins… Spring Batch 란…? 배치 처리를 하기 위한 Spring Framework 기반 기술 Spring의 주요 기능 사용 가능 (DI, AOP, PSA) 스프링 배치의 실행 단위: Job, Step 비..

[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());..

[Kotlin] - 코프링에서 Validation 사용하기

자프링(=자바 + 스프링)진영에서는 외부의 요청을 검증하기 위해서 Validation을 많이 사용한다. 의존성 설정만해두면... (몇 버전인지 기억이 잘 안나지만 스프링부트 특정 버전 이후부터는 Validation을 따로 추가해줘야한다... 그전에는 스프링부트에 자동으로 포함됐었다 😢) Controller단에서는 DTO로 외부 요청을 받아오고 외부의 요청이 DTO에 할당되는 순간 Validation을 통해 검증이 진행된다. (순서가 정확한지는 모르겠다) 자바에서는 DTO의 필드 위에 `@`로 시작되는 애너테이션을 붙여서 처리한다. 그 사용법도 간단하고 애너테이션 각각의 네이밍이 직관적(@NotNull, @NotBlank, @Pattern, @Size etc..)이라 하나라도 사용을 해봤다면 다른 기능들도..

Spring Framework Module

[Spring] NoUniqueBeanDefinitionException 예외 해결방법

728x90

이 해결법을 모르는 것은 아니지만 이 현상을 자주 맞이하는게 아니다보니, 늘 쓰는방식만쓰고 지나갔던 기억이 있다.
이번 기회에 정리해두고 똑같은 상황을 맞이했을 때 조금 더 현명(?)하게 대처할 수 있도록 기록을 남긴다.

총 3가지 예외 해결방법이 있다.

  1. @Autowired 필드명 매칭
  2. @Qualifier -> @Qualifier끼리 매칭 (Bean 이름 매칭)
  3. @Primary

1. @Autowired 필드명 매칭

Autowired는 우선적으로 타입 매칭을 시도하고 타입이 매칭되는 빈이 여러개라면 이름(파라미터 이름)으로 빈 이름을 매핑한다.
(필드명 매칭은 타입 매칭 결과가 1개뿐이라면 발생하지 않는다)

@Autowired
private FooComponent foo; // FooComponent 타입의 빈이 2개 이상이면, 빈 이름이 foo 빈을 매핑!
private final FooComponent foo;

// 주 생성자 or 롬복으로 만든 생성자 주입 방식도 동일하다
public FooClass(FooComponent foo) {
    this.foo = foo
}

2. @Qualifier

@Qualifier 라는 추가 구분자를 붙여줌으로써 빈을 구분짓는 방법이다. 
(실제로 빈 이름을 변경하는 것은 아니다)

만약 @Qualifier 주입을 할 때, value의 값에 매칭되는 Bean을 찾지 못하면 어떻게 될까? 그러면 value로 입력한 값과 일치하는 스프링 빈을 추가로 찾는다(@Qualifier는 추가적인 식별 방식이지 실제로 빈 이름을 변경하는 작업은 아니라고 언급했었다).

만약 그럼에도 일치하는 Bean을 찾지 못한다면 NoSuchBeanDefinitionException 예외가 발생한다.

하지만 @Qualifier는 @Qualifier를 찾는 용도로만 쓰는 것이 명확하고 좋다. 

3. @Primary 사용 

우선순위를 정하는 방법이다.
@Autowired 시에 여러 빈이 매칭되면 @Primary가 우선권을 가진다.
(@Primary 자체가 빈을 생성하기위한 애너테이션은 아니고, 빈 생성을 위한 애너테이션을 동일하게 붙여줘야한다)

@Component
@Primary
public class MainFooComponent implements FooComponent { } // 우선권을 가진다

@Component
public class NormalFooComponent implements FooComponent { }

우선순위

만약 @Primary와 @Qualifier가 둘 다 있다면 어느 것이 먼저 동작하는가?
@Qualifier가 우선 순위가 높다!

(영한님 왈) 자세한 것이 우선권을 가져간다! 

끝맺음

사실 같은 타입에 대해서 빈이 여러개 존재하고, 이 중 하나의 빈만 가지고와서 사용한 케이스가 생각보다 별로 없었던 것 같다. 🤔
이런 케이스에 대해서 Primary를 쓸 것인가 @Qualifier를 쓸 것인가에 대한 고민이 생길 수 있을 것 같다. 
확실하게 Main인 빈이 확실한 경우라면 Primary도 충분히 고려해볼 수 있겠지만 아니라면 @Qualifier를 써야하지 않을까?

728x90
Spring Framework Module/Spring Batch

[Spring Batch] 구조

728x90

Batch란…?

사전 정의

네이버 영어사전 검색 결과

  • 큰 단위의 작업을 일괄 처리
  • 대부분 처리량이 많고 비 실시간성 처리에 사용
    • 대용량 데이터 계산, 정산, 통계, 데이터베이스, 변환 etc…
  • 컴퓨터 자원을 최대로 활용
    • 컴퓨터 자원 사용이 낮은 시간대에 배치를 처리하거나
    • 배치만 처리하기 위해 사용자가 사용하지 않는 또 다른 컴퓨터 자원을 사용
  • 사용자 상호작용으로 실행되기 보단, 스케줄러 같은 시스템에 의해 실행되는 대상
    • ex) 매일 오전 10시에 배치 실행, 매주 월요일 12시 마다 실행 etc…
    • crontab, jenkins…

Spring Batch 란…?

  • 배치 처리를 하기 위한 Spring Framework 기반 기술
    • Spring의 주요 기능 사용 가능 (DI, AOP, PSA)
  • 스프링 배치의 실행 단위: Job, Step
  • 비교적 간단한 작업(Tasklet) 단위 처리와 대량 묶음(Chunk) 단위 처리

배치 프로그램 실행 시, 주의 사항

  • 별 다름 옵션을 주지 않고 실행하게 되면, 모든 Batch Job이 다 실행되게 된다.
  • 보통은 모든 Job을 한 번에 실행하지 않는다.
    • 따라서 어떤 Job을 실행할 것인지 설정할 수 있다.
    • 실행하려는 특정 Job Name만 설정 값으로 넣어주면 된다.

ex) helloJob Job만 실행하기 위해서는 아래의 설정을 필요로한다.

 

설정한 뒤에 실행 결과

 

별다른 설정이 없는 경우 다른 Job이 실행되는 것을 방지하는 옵션

application.yml

# 모든 배치가 실행되지 않도록 설정 추가!
spring:
  batch:
    job:
      names: ${job.name:NONE}

위와 같이 설정하면 job.name 파라미터로 Job을 실행할 수 있게되고, 추가적으로 설정 값이 없으면 아무런 배치가 실행되지 않도록 막을 수도 있다.

(설정 파라미터 값 spring.batch.job.names에 Job Name을 설정할 필요 없이, job.name 프로퍼티에 Job Name을 추가하면 동일하게 작동한다!)

맘시터 서비스는…?

config 설정에서 jobNames가 없는 경우 jobNames가 NONE 으로 설정되도록 config를 설정함.

 

Spring Batch 기본 구조

실제로 예제를 실행해보면 Bean을 생성만했을 뿐인데, 자동으로 Job이 실행 된다.

Spring Batch는 Job time에 Bean이 생성되면 JobLauncher 객체에 의해서 Job을 수행한다.
JobLauncher --(실행) → Job --(실행) → Step 구조가 된다.

JobRepository는 DB or memory에 Spring Batch가 실행될 수 있도록 배치의 메타데이터를 관리하는 클래스
스프링 배치의 전반적인 데이터를 관리하는 클래스 정도로 이해하면된다.

Job

  • Job은 JobLauncher에 의해 실행
  • Job은 배치의 실행 단위를 의미
  • Job은 N개의 Step을 실행할 수 있으며, 흐름(Flow)을 관리할 수 있음.
    • ex) A Step 실행 후, 조건에 따라 B Step 또는 C Step을 실행하도록 설정할 수 있음.

Step

  • Job의 세부 실행 단위이며, Job은 최소 1개 이상의 Step으로 구성
  • Step의 실행 단위는 크게 2가지로 나뉨
    • Task 기반: 하나의 작업 기반으로 실행
    • Chunk 기반: 하나의 큰 덩어리를 n개씩 나눠서 실행 (chunk = 덩어리 라는 뜻을 가짐
      • ex) 처리 대상이 한 번에 실행해도 컴퓨터 자원에 문제가 없으면 Tasklet을 사용
      • ex) 10,000명의 회원을 1,000명 씩 나눠서 처리한다면 Chunk
  • ItemReader : 배치 처리 대상 객체를 읽어 ItemProcessor 또는 ItemWriter에게 전달
  • ItemProcessor: input 객체를 output 객체로 filtering 또는 processing해 ItemWriter에게 전달
    • ex) ItemReader에서 읽은 데이터를 수정 또는 ItemWriter 대상인지 filtering 한다.
    • ItemProcessor는 optional (있을 수도 있고 없을 수도 있음)
    • ItemProcessor가 하는 일은 ItemReader 또는 ItemWriter가 대신할 수 있다.
  • ItemWriter: 배치 처리 대상 객체를 최종 처리
    • ex) DB Update를 하거나, 처리 대상 사용자에게 알림을 보낸다.

스프링 배치 테이블 구조

Spring Batch Table 구조

 
  • 배치 실행을 위한 메타 데이터가 저장되는 테이블
  • BATCH_JOB_INSTANCE
    • Job이 실행되며 생성되는 최상위 계층의 테이블
    • job_namejob_key를 기준으로 하나의 row가 생성되며, 같은 job_name과 job_key가 저장될 수 없다.
    • job_key는 BATCH_JOB_EXECUTION_PARAMS에 저장되는 Parameter를 나열해 암호화해 저장
  • BATCH_JOB_EXECUTION
    • Job이 실행되는 동안 시작/종료 시간, Job 상태 등을 관리
  • BATCH_JOB_EXECUTION_PARAMS
    • Job을 실행하기 위해 주입된 parameter 정보 저장
  • BATCH_JOB_EXECUTION_CONTEXT
    • Job이 실행되며 공유해야할 데이터를 직렬화해 저장
  • BATCH_STEP_EXECUTION
    • Step이 실행되는 동안 필요한 데이터 또는 실행된 결과 저장
  • BATCH_STEP_EXECUTION_CONTEXT
    • Step이 실행되며 공유해야할 데이터를 직렬화해 저장
  • External Library 하단에 org.springframework/batch/core 하위에 schema 스크립트 파일 존재
  • 스프링 배치를 실행하고 관리하기 위한 테이블
  • schema.sql 설정
    • schema-**.sql의 실행 구분
      • DB 종류별로 script가 구분됌.
    • spring.batch.initialize-schma config로 구분(properties 기준)
    • ALWAYS, EMBEDDED, NEVER로 구분
      • ALWAYS: 항상 실행
      • EMBEDDED: 내장 DB일 때만 실행 (default)
      • NEVER: 항상 실행 안함 (운영환경)

 
 

Job, JobInstance, JobExecution 이해

 
  • JobInstance: BATCH_JOB_INSTANCE 테이블과 매핑
  • JobExecution: BATCH_JOB_EXECUTION 테이블과 매핑
  • JobParameters: BATCH_JOB_EXECUTION_PARAMS 테이블과 매핑
  • ExecutionContext: BATCH_JOB_EXECUTION_CONTEXT 테이블과 매핑
  • JobInstance의 생성 기준은 JobParameters 중복 여부에 따라 생성된다.
  • 다른 parameter로 Job이 실행되면 JobInstance가 생성된다.
  • 같은 parameter로 Job이 실행되면, 이미 생성된 JobInstance가 실행된다.
  • JobExecution은 (재실행 여부와 상관없이) 항상 새롭게 생성된다.
  • ex)
    • 처음 Job 실행 시, data parameter가 2022-01-01로 실행 됐다면, 1번 JobInstance가 생성
    • 다음 Job 실행 시, data parameter가 2022-01-02로 실행 됐다면, 2번 JobInstance가 생성
    • 다음 Job 실행 시, data parameter가 2022-01-02로 실행 됐다면, 2번 JobInstance가 재 실행
      • 이때 Job이 재실행 대상이 아닌 경우 에러가 발생
  • Parameter가 없는 Job을 항상 새로운 JobInstance가 실행되도록 RunIncrementer가 제공
    • RunIdIncrementer는 항상 다른 run.id를 Parameter로 설정

Step, StepExecution 이해

  • StepExecution: BATCH_STEP_EXECUTION 테이블과 매핑
  • ExecutionContext: BATCH_STEP_EXECUTION_CONTEXT 테이블과 매칭
    • ExecutionContext 클래스는 Job과 Step에 Context를 모두 맵핑할 수 있는 클래스이다.
  • Job 내에서 공유할 수 있는 BATCH_JOB_EXECUTION_CONTEXT
  • 하나의 Step에서 공유할 수 있는 BATCH_STEP_EXECUTION_CONTEXT
  • 예제 참고
    • 참고로 mysql DB에서 batch 실습을 진행할 때는 schema.mysql.sql의 내용 그대로 DB에 insert 하면된다. 
728x90
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
카테고리 없음

[Kotlin] - 코프링에서 Validation 사용하기

728x90

자프링(=자바 + 스프링)진영에서는 외부의 요청을 검증하기 위해서 Validation을 많이 사용한다. 

의존성 설정만해두면... (몇 버전인지 기억이 잘 안나지만 스프링부트 특정 버전 이후부터는 Validation을 따로 추가해줘야한다... 그전에는 스프링부트에 자동으로 포함됐었다 😢)

Controller단에서는 DTO로 외부 요청을 받아오고 외부의 요청이 DTO에 할당되는 순간 Validation을 통해 검증이 진행된다. (순서가 정확한지는 모르겠다) 

자바에서는 DTO의 필드 위에 `@`로 시작되는 애너테이션을 붙여서 처리한다. 
그 사용법도 간단하고 애너테이션 각각의 네이밍이 직관적(@NotNull, @NotBlank, @Pattern, @Size etc..)이라 하나라도 사용을 해봤다면 다른 기능들도 충분히 응용하기 쉽다.

그런데 코프링 진영에서는 사용법이 조금 다르다. 실제로 필드위에 자바처럼 애너테이션을 붙여보면, 붙기는 잘 붙고 컴파일도 잘 된다. 
하지만 동작을 안한다.

그리고 코프링에서는 아래와 같이 Validation을 써야한다!  

잘보면 `@field: [기존 자프링의 애너테이션][() 안에서 속성 값 지정 ]` 의 형식이다. 
위의 예시를 보면 느낌이 오겠지만, 하나의 애너테이션당 하나의 @field:...로 처리가 됌을 알 수 있다. 

그렇다면... 왜 사용하는 방식이 다를까? 

코프링에서 어노테이션을 자프링처럼 걸면 생성자의 파라미터에 설정되는것이기 때문이다.
하지만 실제로 우리가 원하는건 필드(or Getter)에 Validation을 걸고 싶기 때문에 @field:를 명시해줘야 한다.
어노테이션을 다시 설정하고 실행해보면 정상적으로 동작하는 것을 알 수 있다.

728x90