목표

1. 스프링은 크게 3가지 방법으로 빈 생명주기 콜백을 지원한다. 각 방식마다의 특징을 기억하자!
    -> 인터페이스, 설정 정보에 초기화 및 소멸 메서드 지정, 애노테이션.

'빈 생명주기 콜백' 목차

1. 빈 생명주기 콜백 시작

2. 인터페이스 InitializingBean, DisposableBean (이번 포스팅)

3. 빈 등록 초기화, 소멸 메서드 

4. 애노테이션 @PostConstruct, @PreDestroy


2.  인터페이스 InitializingBean, DisposableBean 

1) 초기화 콜백: InitializingBean 인터페이스를 구현하자.

들어가보면, afterPropertiesSet() 메서드가 있다. 
"프로퍼티들의 세팅이 끝나면, 즉 의존관계 주입이 끝나면" 호출하는 메서드라는 뜻이다. 

의존관계 주입 후, 초기화 메서드 afterPropertiesSet() 가 호출된다. 

2) 소멸 전 콜백: DisposableBean 인터페이스를 구현하자.

destroy() 메서드는 빈이 소멸될 때 호출된다. 

두 개의 인터페이스를 구현한 후의 코드. 

이전 시간에 작성한 NetworkClient 테스트코드를 실행하고 출력을 확인하자.

스프링 컨테이너를 생성하고 빈을 조회하고, 컨테이너를 종료하는 내용이었다. 

객체 생성(생성자 호출)때는 당연히 url이 값이 없다. 

초기화 콜백  : 의존관계 주입 직후, connect() , call() 이 호출됬다. 

소멸전 콜백  : 컨테이너 종료(빈 소멸) 할 때 close를 출력하는 distroy()가 호출됬다. 

 

3) 초기화, 소멸 인터페이스의 단점

  • 이 인터페이스는 스프링 전용 인터페이스다. 해당 코드가 스프링 전용 인터페이스에 의존한다.
  • 초기화, 소멸 메서드의 이름을 변경할 수 없다.
  • 내가 코드를 고칠 수 없는 외부 라이브러리에 적용할 수 없다. 
  • 인터페이스 방식은 2003년에 나온 방법이고, 지금은 더 나은 방법들이 있어서 거의 안 쓴다.

3. 빈 등록 초기화, 소멸 메서드 

설정 정보에 @Bean(initMethod = "메서드이름" , destroyMethod = "메서드이름") 으로 초기화, 소멸 메서드를 지정할 수 있다. 

1) 특징 

* 메서드 이름을 자유롭게 줄 수 있다.

* '설정 정보'를 사용하니까 외부 라이브러리에도 초기화, 소멸 메서드를 적용할 수 있다. 

 

2) 종료 메서드 추론 (inferred)

@Bean( destroyMethod = "메서드이름")에 특별한 기능이 있다. 

대부분 (외부)라이브러리의 종료 메서드 이름이 close, shutdown 임을 이용한 기능이다. 

@Bean 의 destroyMethod의 기본값이 (inferred) 로 등록되어 있다.

그래서 직접 스프링 빈으로 등록하면, close, shutdown 이름의 메서드를 자동으로 호출해준다. 

추론 기능을 사용하지 않으려면, @Bean( destroyMethod = "") 처럼 공백을 지정하면 된다.


4. [권장] 애노테이션 @PostConstruct, @PreDestroy 

내가 정의한 메서드에 애노테이션을 붙이는 방법이다. 편리하다. 

1) 특징 

* 최신 스프링에서 가장 권장하는 방법이다. 

* 패키지가 javax 로 시작하는데. 자바 표준이라는 의미다. 따라서 스프링이 아닌 다른 컨테이너에서도 잘 동작한다. 

* 유일한 단점은 외부 라이브러리에는 적용하지 못한다는 것이다. 

* 외부 라이브러리에는 @Bean(initMethod = "메서드이름") 방식을 쓰자. 

 


다음 시간에는 빈 스코프를 배운다. 

공부 내용 출처 :  스프링 핵심 원리 기본편 

728x90

목표

1. 스프링 빈이 생성되거나 소멸하기직전에 메서드를 호출해주는 간단한 기능이다. 
2. 스프링은 크게 3가지 방법으로 빈 생명주기 콜백을 지원한다. 각 방식마다의 특징을 기억하자!
    -> 인터페이스, 설정 정보에 초기화 및 소멸 메서드 지정, 애노테이션.

'빈 생명주기 콜백' 목차

1. 빈 생명주기 콜백 시작 (이번 포스팅)

2. 인터페이스 InitializingBean, DisposableBean

3. 빈 등록 초기화, 소멸 메서드 

4. 애노테이션 @PostConstruct, @PreDestroy


1.  빈 생명주기 콜백 시작

데이터베이스 커넥션 풀이나 네트워크 소켓처럼 tcp ip handshaking 등 연결을 위한 작업이 좀 많은 경우, 

미리 연결을 해두고 요청이 오면 연결을 바로 재활용 한다. 소켓도 미리 잡아놓아야 요청이 왔을 때 즉시 응답을 줄 수 있다. 

이런 건 애플리케이션 시작 시점에 필요한 연결을 미리 해둬야 한다.

DB 연결 종료와 소켓 닫는것도 애플리케이션 종료 직전에 해둬야 한다. 

이번 시간에는 스프링을 통해 객체의 초기화 작업과 종료 작업을 어떻게 진행하는지 예제로 알아보자. 

 

간단하게 외부 네트워크에 미리 연결하는 객체를 하나 생성한다고 가정해보자. 

실제 네트워크에 연결하는 건 아니고 문자만 출력한다. 

 

1) NetworkClient 클래스 

  • 애플리케이션 시작 시점에 connect() 를 호출해서 연결을 맺어두어야 한다.
  • 애플리케이션이 종료되면 disConnect() 을 호출해서 연결을 끊어야 한다. 

생성자에서 connect()를 호출하도록 했다. 

 

2) NetworkClient 클래스 테스트 코드 

 

1. 애플리케이션을 시작할 때 스프링 빈이 등록된다. 

    -> NetworkClient 클래스의 생성자에서 connect() 가 호출된다. 

// 스프링 컨테이너 생성
ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);

2. 애플리케이션 종료할 때 스프링 컨테이너의 close() 를 호출한다. 

ac.close();

 출력 결과는 아래와 같다. 

너무 당연한 얘기지만, 빈이 등록될 때 생성자를 호출하는데 이 때는 url 이 없으니까 url 이 null 로 출력된다. 


3) 스프링 빈의 라이프 사이클 

객체 생성 -> 의존관계 주입 

스프링 빈은 객체를 생성하고, 의존관계 주입이 끝난 다음에야 필요한 데이터를 사용할 수 있는 준비가 완료된다.

즉, 의존관계 주입이 끝나야 필드나 메서드를 변경하거나 사용할 수 있게 된다는 의미다. 필드를 수정하는 이러한 작업을 초기화 작업이라고 한다. 

객체 생성 -> 의존관계 주입 -> 초기화 작업 가능 시점 

 

4) 개발자는 초기화 작업을 할 수 있는 시점을 어떻게 알까 ?

스프링은 의존관계 주입이 끝나면, 스프링 빈에게 콜백 메서드를 통해서 초기화 시점을 알려준다. 

그리고 스프링 컨테이너가 종료되기 직전에, 소멸 콜백을 준다. 따라서 안전하게 종료 작업을 진행할 수 있다. 

 

스프링 빈의 이벤트 라이프사이클 

스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> 사용
-> 소멸전 콜백 -> 스프링 종료 

 * 초기화 콜백 : 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출되는 콜백.

 * 소멸전 콜백 : 빈이 소멸되기 직전에 호출되는 콜백.

 

5) 객체의 생성과 초기화를 분리하자 

생성자는 필수 정보만 파라미터로 받아서 메모리를 할당해서 객체를 생성하는 것만 해야 한다. 딱 생성하는 것에만 집중해야 한다. 

반면에 초기화는 외부 커넥션을 생성하는 등 무거운 동작을 수행한다. 

대부분의 경우, 둘을 합쳐놓는 것 보다는 객체 생성과 초기화를 분리하는 것이 유지보수 관점에서 좋다. 

물론 초기화 작업이 내부 값을 약간 변경하는 정도의 단순 작업이면 생성자에서 다 처리하는 것이 나을 수 있다. 

 

6) 객체의 생성과 초기화를 분리했을 때 장점

이런 것을 '지연' 이라고 하는데. 

예를 들어, DB 커넥션 풀을 애플리케이션 시작 할 때 100개 연결 생성만 만들어 둔다고 하자. 이 때 초기화는 안 한다. 
최초에 액션이 (
실제 요청이) 들어오면 초기화를 해서 사용하는 동작을 지연시키는 장점이 있다. 

 

[ 참고 ] 

스프링 컨테이너가 종료될 때 싱글톤 빈들이 함께 종료된다. 그래서 스프링이 종료될 때 소멸전 콜백이 일어난다. 

뒤에서 '스코프'를 설명할 때 자세히 배우겠지만, 생명주기가 짧은 빈들은 스프링 컨테이너 종료와는 무관하게 해당 빈이 종료하기 직전에 소멸전 콜백이 일어난다. 


스프링은 3가지 방법으로 빈 생명주기 콜백을 지원한다. 다음 시간에 배우자. 

공부 내용 출처 :  스프링 핵심 원리 기본편 

728x90

목표

1. 의도적으로 해당 타입의 스프링 빈이 전부 필요한 경우가 있다. 스프링으로 '전략 패턴'을 간단히 구현해보자.
2. "언제 수동 빈 등록이 필요할까?" 생각해본다. 

'의존관계 자동 주입' 목차

1. 다양한 의존관계 주입 방법 

2. 옵션 처리 

3. 생성자 주입을 선택해라!  

4. 롬복과 최신 트랜드

5. 조회 빈이 2개 이상 - 문제

6. @Autowired 필드 명, @Qualifier, @Primary

7. 애노테이션 직접 만들기 

8. 조회한 빈이 모두 필요할 때 List, Map (이번 포스팅)

9. 자동, 수동의 올바른 실무 운영 기준


8.  조회한 빈이 모두 필요할 때 List, Map에 담기 

고객이 주문할 때 할인 정책을 어떤 것을 이용할지(Rate or Fix) 선택할 수 있게 됬다. 

인터페이스 DiscountPolicy 를 구현한 클래스 RateDiscountPolicy와 클래스 FixDiscountPolicy 는 타입이 똑같다. 

타입이 같은 스프링 빈을 둘다 등록해서 사용해야 한다. 어떻게 해결할 수 있을까? 

 

1) AutoAppConfig.class -> 컴포넌트 스캔 및 Autowired 로 스프링 빈을 생성하고 의존관계를 주입한다. 

2) DiscountPolicy.class -> DiscountPolicy 타입의 스프링 빈을 등록한다. 

DiscountPolicy 타입들의 빈을 Map 에 담을 때 { 스프링 빈 이름 = 인스턴스 레퍼런스} 쌍으로 담겨있다. 

policyMap = 
{fixDiscountPolicy=hello.corebasic.discount.FixDiscountPolicy@5d1659ea, 
rateDiscountPolicy=hello.corebasic.discount.RateDiscountPolicy@793138bd}

DiscountPolicy 타입들의 빈을 List에 담을 때  [ 인스턴스 레퍼런스 , 인스턴스 레퍼런스, ... ]  로 담겨있다. 

policyList = 
[hello.corebasic.discount.FixDiscountPolicy@5d1659ea, 
hello.corebasic.discount.RateDiscountPolicy@793138bd]

3) 고객이 10000원 주문했다면, VIP 고객일 때 1000원의 할인금액이 계산되는지 확인하자. 

discount() 메서드 : 고객의 주문 금액과 할인코드 문자열을 입력받아서 할인금액을 리턴하는 메서드가 필요하다. 

3) DiscountService.class  에  discount() 메서드를 작성한다. 

"할인코드"에 대응하는 "스프링 빈"이름을 찾아서 인스턴스를 꺼낸다.

인터페이스 DiscountPolicy 에 선언된 discount() 메서드를 호출하여 할인금액을 계산하여 리턴한다. 

rate 할인 정책일 때와 fix 할인 정책일 때의 discount() 메서드 구현 내용은 다르다. -> 다형성을 기반으로 유연한 전략 패턴 제공

4) rate 할인 정책일 때와 fix 할인 정책에 맞게 할인금액을 계산하는지 테스트 코드를 작성해서 확인한다. 

 


9.  자동, 수동의 올바른 실무 운영 기준 

1) 편리한 자동 기능을 기본으로 사용하자. 

스프링은 점점 자동화를 선호하는 추세로 발전하고 있다. 최근에 범용으로 쓰는 스프링 부트는 컴포넌트 스캔을 기본으로 사용한다. 

설정 정보를 기반으로 애플리케이션을 구성하는 구분과, 실제 동작하는 부분을 명확하게 나누는 것이 이상적이긴 하다. 

하지만 개발자 입장에서는 일일히 @Configuration 설정 정보에 가서 @Bean 을 적고 객체 생성, 주입 하는 것은 고역이다. 프로젝트가 커지면 수 백개 클래스에 이 작업이 필요하다. 

그리고 결정적으로 자동 빈 등록을 사용해도 DIP와 OCP를 지킬 수 있다.

 

2) 수동 빈 등록은 언제 사용해야 좋을까? 

애플리케이션 개발해보면 비즈니스 업무 로직과 기술 지원 로직으로 나뉜다. 

* 업무 로직 빈
: 비즈니스 요구사항에 따라 변경된다.
웹을 지원하는 컨트롤러, 핵심 비즈니스 로직이 있는 서비스, 데이터 계층 로직을 처리하는 리포지토리. 

* 기술 지원 빈

: 기술적인 문제나 공통 관심사(AOP)를 처리할 때 주로 사용된다. 로직을 지원하기 위한 하부 기술이나 공통 기술. 
DB 연결이나 공통 로그 처리 등이 있다. 

애플리케이션에 광범위하게 영향을 주는 기술지원 객체는
수동 빈으로 등록해서 설정 정보에 딱! 티나게 나타내는 것이 유지보수 하기 좋다.

* 업무 로직 빈의 경우, 굉장히 개수가 많지만 어느 정도 유사한 패턴이 있다. -> 자동 빈 사용이 적절하다. 

* 기술 지원 빈의 경우, 업무 로직 빈에 비해 개수가 매우 적다. 하지만 애플리케이션 전반에 걸쳐서 광범위하게 영향을 미친다. 올바르게 동작하고 있는지 파악하기가 어려운 경우가 많다. -> 수동 빈 사용이 적절하다. 

 

3) 비즈니스 로직 중에서 다형성을 적극 활용할 때 수동 빈을 쓰자 

방금 전에 DiscountPolicy 인터페이스의 다형성을 기반으로 적절한 인스턴스를 쓰는 방법을 배웠다. 

하지만 실무에서 이러한 코드를 만나면, 인터페이스의 다형성을 이용한다는 점을 바로 알기 어렵다.

자동 등록을 사용하고 있기 때문이다. 파고 들어가야 한다. 
   " DiscountPolicy 관련이긴 한데 Map 은 왜 쓰지 ... (어리둥절) " 내가 할 땐 좋은데. 남이 해논 추상화가 힘들 때가 있다.

다형성을 적극 활용하는 경우에는 이 부분을 별도의 설정 정보로 만들고 수동 등록하자

   " DiscountPolicy 에는 rate와 fix가 있구나! " -> 한 눈에 들어온다. 유지보수에 훨씬 도움되는 구조!

그리고 반드시 특정 패키지에 같이 묶어둬야 한다. 

결론 :
자동을 하든 수동을 하든, 다형성을 사용할 땐 한눈에 들어오도록 신경써줘야 한다. 혼자 개발하는게 아니기 때문이다.


다음 강의에서는 빈 생명주기 콜백을 배운다. 

공부 내용 출처 :  스프링 핵심 원리 기본편 

728x90

목표

1. 생성자 주입을 선택해야하는 이유를 배운다. 
2. 롬복의 @RequiredArgsConstructor  를 써서 코드를 최적화하자. 

'의존관계 자동 주입' 목차

1. 다양한 의존관계 주입 방법 

2. 옵션 처리 

3. 생성자 주입을 선택해라!  (이번 포스팅)

4. 롬복과 최신 트랜드

5. 조회 빈이 2개 이상 - 문제

6. @Autowired 필드 명, @Qualifier, @Primary

7. 애노테이션 직접 만들기

8. 조회한 빈이 모두 필요할 때, List, Map

9. 자동, 수동의 올바른 실무 운영 기준


3. 생성자 주입을 선택해라! 

최근에는 스프링을 포함한 DI 컨테이너들은 생성자 주입을 권장한다. 그 이유는 다음과 같다. 

 

1)  불변 

   * 생성자 호출시점에 딱 1번만 호출되는 것이 보장된다. 

   * 불변, 필수 의존관계에 사용한다. 

   * 애플리케이션을 종료할 때 까지 의존관계를 변경할 일이 있을까? 없다. 거의 없다. 오히려 불변해야한다. 

   * 수정자 주입을 사용하면, setXxx 메서드를 public으로 열어둬야 한다. 누군가 실수로 setXxx으로 변경할 수 있어서 위험한 설계다. 

2)  누락 

  * 생성자 주입을 사용하면 주입 데이터를 누락했을 때 바로 컴파일 에러를 낸다.

 가장 좋은 에러는 컴파일에러. 그리고 IDE에서 바로 어떤 값을 주입하라고 알려준다.

2)  final 키워드 

 * 생성자 주입을 사용하면 필드에 final 키워드를 사용할 수 있다. 혹시라도 생성자에 값이 주입되지 않은 오류를 컴파일 시점에 막아준다. 

 

A. 수정자 주입으로 테스트 코드 작성

setter로 (수정자 주입)을 사용하여 테스트 코드를 작성한 예시를 보자.

 OrderServiceImpl 에서 수정자 주입으로 의존관계를 주입했다. 

순수 자바 코드를 이용해서 OrderServiceImpl 의 '주문 요청'메서드를 테스트하자. 

문제 없이 동작할까? 테스트를 실행해보자. 

NullPointerException 이 발생한다. 

OrderServiceImpl 을 생성해서 테스트 코드를 작성하는 상황에서는 의존관계를 주입해야 하는 것을 발견하지 못하기 때문이다. 

set은 '객체가 생성된 후에' 의존관계 주입을 하는거니까. 생성시에는 별 문제가 안된다. 

 

B. 생성자 주입으로 테스트 코드 작성

OrderServiceImpl 의 의존관계를 '생성자 주입'으로 변경했다. 

테스트 코드로 돌아가보자. 코드에 빨간 밑줄이 벌써 떠있고 constructor 오류가 난다. 

세상에서 가장 좋은 오류 컴파일 오류.

 

C. [ final 키워드 시용시 ] 개발자가 생성자 주입에서 필드 하나 주입하는것을 잊어버린다면? 

바로 빨간줄 뜨고 자바 컴파일러가 생성자에 의존관계 주입하라고 컴파일 시점에 잡을 수 있다. 

결론 : 항상 생성자 주입을 사용하자. 


4. 롬복과 최신 트랜드 

막상 개발을 해보면 대부분이 다 불변이다. 그래서 생성자에 final 키워드를 붙이게 된다. 

그런데 생성자도 만들고, 주입 받는 코드도 만들고.. 영 반복 작업 같다. 

아래 OrderServiceImpl 을 최적화 해보자. 

1) 생성자가 딱 1개만 있으면 @Autowired  를 생략할 수 있다. 

 

2) 롬복 라이브러리를 적용해서 생성자를 자동으로 만들자. 

@RequiredArgsConstructor 가 final 이 붙은 필드를 가지고 생성자 코드를 그대로 만들어준다. 

[ lombok 적용 방법 ] 

lombok 의존성을 build.gradle 에 추가하자. 

intelliJ에 Annotation Processors 를 사용하도록 체크하자.

getter, setter 메서드를 직접 작성할 필요 없이, 클래스에 @Getter, @Setter 를 달아놓으면 자동으로 만들어준다. 

 

3) OrderServiceImpl 를 최적화한 결과 코드 


다음 강의에서는 조회 빈이 2개 이상일 때의 처리 방법을 배운다. 

공부 내용 출처 :  스프링 핵심 원리 기본편 

728x90

목표

1. 생성자, setter, 필드, 일반 메서드. 이렇게 의존관계 주입 방법 4가지를 배운다. 각 방법의 특징을 기억하자. 
2. 의존관계 주입을 옵션처리 하는 방법을 배운다. 

'의존관계 자동 주입' 목차

1. 다양한 의존관계 주입 방법  (이번 포스팅)

2. 옵션 처리 

3. 생성자 주입을 선택해라!

4. 롬복과 최신 트랜드

5. 조회 빈이 2개 이상 - 문제

6. @Autowired 필드 명, @Qualifier, @Primary

7. 애노테이션 직접 만들기

8. 조회한 빈이 모두 필요할 때, List, Map

9. 자동, 수동의 올바른 실무 운영 기준


1. 다양한 의존관계 주입 방법 

1) 생성자 주입

이름 그대로 생성자를 통해 의존 관계를 주입 받는 방법이다. 지금까지 해온 생성자에 @Autowired 를 붙이는 방식이다. 

특징

   * 생성자 호출시점에 딱 1번만 호출되는 것이 보장된다. 

   * 불변, 필수 의존관계에 사용한다. 

 

# 여담 - 이렇게 생성자 주입으로 의존관계를 세팅하면 좋은 이유?

스프링 빈을 생성할 때 생성자를 호출하니까. 이 시점에 의존관계 주입을 1번 딱 세팅하고 시작하고 싶은 것이다. 

생성자 주입은 (빈 등록할 때 생성자를 호출하니까) 빈 등록과 의존관계 주입이 같이 일어난다.

개발할 때는 불변의 범위를 확실히 잡고가는 것이 중요하다. 

 

# 여담 -  웬만하면 생성자에는 값을 다 채워 넣어야 한다고 생각하는게 관례다.

따로 생성자에 null을 허용한다는 얘기가 없으면, 비워두지 말고 값을 꼭 넣자. 

 

[ 중요 ]

스프링 빈의 생성자가 1개인 경우, 생성자에 @Autowired 를 붙이지 않아도 생성자를 통해 의존관계가 주입된다! 

아래 두 코드는 똑같이 생성자 주입으로 의존관계가 주입된다. 

 

2) 수정자 주입(setter 주입)

필드 값을 변경하는 setter 메서드를 통해 의존관계를 주입하는 방법이다. 

setter 메서드는 변수명 앞에 set을 붙이는 것이 관례다. 아래 처럼 setter 메서드를 만들고  @Autowired를 붙인다. 

특징

   * 선택, 변경 가능성이 있는 의존관계에 사용한다. (사실 거의 변경할 일은 드물다)

단, setter로 주입하니까. private MemberRepository 변수에는 final(상수)를 붙이면 안된다. 

 

[ 참고 ]

@Autowired 의 기본 동작은 주입할 대상이 없으면 오류가 발생한다.

주입할 대상이 없어도 동작하게 하려면 @Autowired (required = false)로 지정하면 된다. 

[ 참고 ] 자바빈 프로퍼티 규약 

setXxx, getXxx 형태의 메서드로 필드 값을 수정하거나 읽자는 규약이다. 직접 필드값을 변경 하지 말자는 것이다. 

 

3) 필드 주입

이름 그대로 필드에 @Autowired 붙이면 의존관계를 빡 주입한다. 

하지만 필드 주입을 권장하지 않는다. 안티패턴이다.

특징

   * 코드가 간결해서 많은 개발자들을 유혹하지만 외부에서 변경이 불가능해서 테스트 하기 힘들다는 치명적인 단점이 있다. 

   * DI 프레임워크가 없으면 아무것도 할 수 없다. 순수한 자바코드로 제대로 테스트 할 수 없다. 

   * 애플리케이션의 실제 코드와 관계 없는 테스트 코드에서는 사용할 수도 있다. 

 

4) 일반 메서드 주입

일반 메서드를 통해서 주입 받을 수 있다. 

특징 

   * 한번에 여러 필드를 주입 받을 수 있다. 

   * 일반적으로 잘 안 쓴다. 

[ 참고 ]

어쩌면 당연한 이야기이지만 의존관계 자동주입은 스프링 컨테이너가 관리하는 스프링 빈이어야 동작한다.

 


2. 옵션 처리

주입할 스프링 빈이 없어도 동작해야 할 때가 있다. 자동 주입 대상을 옵션으로 처리하는 방법 3가지를 알아보자. 

 

Member 클래스는 스프링 빈이 아니다. 스프링 빈이 아닌 것을 @Autowired 하면, 자동 주입할 대상이 없는 상황인 것이다. 

이 때 스프링 컨테이너를 생성해서 빈 등록을 시도해보자. 

아래의 자동 주입 대상을 옵션으로 처리하는 방법을 사용하면 에러 없이 테스트가 성공한다. 

 

1) @Autowired (required = false)

@Autowired 는 기본으로 자동주입 대상이 없으면 에러를 낸다. 기본값이 (required = true) 이다. 

(required = false) 의미 : "자동 주입할 대상이 없으면(== 의존관계가 없으면), 수정자 메서드 자체를 호출하지 않는다. "

 

2) @Nullable

자동주입할 대상이 없으면 null이 입력된다.

3) 자바8의 Optional<>

자동주입할 대상이 없으면 Optional.empty 가 입력된다. 

 

스프링 빈이 아닌 것을 @Autowired 할 때, 자동 주입 대상을 옵션으로 처리하는 코드 

public class AutowiredTest { /** 의존관계 옵션 처리 */

    @Test
    void AutowiredOption(){
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestBean.class);
    }

    static class TestBean{

        @Autowired(required = false)
        public void setNoBean1(Member nobean1){
            System.out.println("nobean1 = " + nobean1);
        }

        @Autowired
        public void setNoBean2(@Nullable Member nobean2){
            System.out.println("nobean2 = " + nobean2);
        }

        @Autowired
        public void setNoBean3(Optional<Member> nobean3){
            System.out.println("nobean3 = " + nobean3);
        }
    }
}

 

[ 참고 ]

Optional<> 과 @Nullable은 스프링 전반에 전부 지원되는 기능이다. 


다음 강의에서는 생성자 주입을 선택해야 하는 이유를 배운다. 

공부 내용 출처 :  스프링 핵심 원리 기본편 

728x90

목표

1. 필터를 이용해 컴포넌트 스캔에서 대상을 제외하거나 추가하는 것을 배운다. 
2. 빈 이름이 중복 되었을 때 처리를 배운다. 

'컴포넌트 스캔' 목차

1. 컴포넌트 스캔과 의존관계 자동 주입 시작하기

2. 탐색 위치와 기본 스캔 대상

3. 필터 (이번 포스팅)

4. 중복 등록과 충돌 


3. 필터

1) 컴포넌트 스캔 필터에 쓸 애노테이션을 만들어보자. 

애노테이션 2개를 만드는데, '포함'한다는 의미로 MyIncludeComponent 라고 이름붙인다.

애노테이션 MyExcludeComponent 는 제외의 용도로 만든다. 

클래스 2개를 만들고 각각 다른 애노테이션을 붙인다.

BeanA 클래스에는 @MyIncludeComponent 를 붙인다. BeanB 에는 @MyExcludeComponent 를 붙인다. 

2) 컴포넌트 스캔에 필터를 적용한 테스트 코드를 작성한다.

@ComponentScan 에 includeFilters와 excludeFilters를 적용할 수 있다. 

includeFilters 에는 포함할 애노테이션인 MyIncludeComponent.class를 써주고,

excludeFilters 에는 제외하는 필터 애노테이션이니까 MyExcludeComponent.class를 써주자. 

3) 필터가 적용된 컴포넌트 스캔을 하되, 스프링 빈이 생성됬는지 확인하자.

BeanA만 컴포넌트 스캔에 포함되어야 한다.  BeanA 스프링 빈으로 등록되고,  BeanB 라는 빈은 찾을 수 없어야 테스트 성공이다. 

3) FilterType 옵션 5가지를 알아보자. 

 

* ANNOTATION  : 기본값. 애노테이션을 인식해서 동작하는 것이 기본이다. 

" type = FilterType.ANNOTATION "을 지워도 똑같이 동작한다. 

* ASSIONABLE_TYPE  : 지정한 타입과 자식 타입을 인식해서 동작한다 

    org.example.SomeClass

ASPECTJ  : AspectJ 패턴을 사용한다 

    org.example..*Service+

* REGEX  : 정규표현식을 줄 수 있다 

    org\.example\.Default.*

* CUSTOM  : TypeFilter 라는 인터페이스를 구현해서 처리한다 

    org.example.MyTypeFilter

 

[ 참고 ]

@Component 애노테이션으로 충분하기 때문에 includeFilters 를 사용할 일은 드물다. excludeFilters는 아주 가끔 쓴다. 

스프링 부트가 기본으로 제공하는 컴포넌트 스캔을 사용하는 것을 권장한다. 


4. 중복 등록과 충돌

컴포넌트 스캔에서 같은 빈 이름을 등록하면 어떻게 될까? 

다음 두 가지 상황이 있다.

 

1) 자동 빈 등록 vs 자동 빈 등록 

컴포넌트 스캔에 의해 자동으로 스프링 빈이 등록될 때 이름이 같으면 오류가 발생한다. 

 

예를 들어, 2개의 빈에 service라고 이름 붙여보자. 

MemberServiceImpl 과 OrderServiceImpl에 둘 다 @Component("service")라고 붙이자. 

컴포넌트 스캔을 해보면, 빈이 충돌난다고  ConflictingBeanDefinitionException  에러가 난다. 

org.springframework.beans.factory.BeanDefinitionStoreException: 
Failed to parse configuration class [hello.corebasic.AutoAppConfig]; 
nested exception is org.springframework.context.annotation.ConflictingBeanDefinitionException: 

Annotation-specified bean name 'service' for bean class [hello.corebasic.order.OrderServiceImpl] conflicts with existing, 
non-compatible bean definition of same name and class [hello.corebasic.member.MemberServiceImpl]

에러 내용이 굉장히 친절하다. 애노테이션으로 이름지어진 'service'라는 빈 이름이 있는데,

MemberServiceImpl 에도 정의되어 있고, OrderServiceImpl 에도 같은 이름으로 정의되어 있다는 내용이다. 

 

2) 수동 빈 등록 vs 자동 빈 등록 

이 경우, 수동 빈 등록이 우선권을 가진다. 수동 빈이 자동 빈을 오버라이딩한다. 

 

예를 들어, MemoryMemberRepository@Component 를 달아놔서 컴포넌트 스캔의 대상이되고

빈 이름은 기본으로 클래스 명 앞글자 하나만 소문자로 바꿔서 memoryMemberRepository이름지어진다. 

@ComponentScan 하는 설정에서 memoryMemberRepository라는 똑같은 이름으로 MemoryMemberRepository 빈 등록을 시도해보자. 

컴포넌트 스캔 하는 테스트를 실행하면? 성공하긴 한다. 

수동 빈이 자동 빈을 오버라이딩 한다고 로그 메시지가 출력된다. 

Overriding bean definition for bean 'memoryMemberRepository' with a different definition: 
replacing [Generic bean: class [hello.corebasic.member.MemoryMemberRepository];

 

하지만 현실은 개발자가 의도적으로 설정해서 이런 결과를 낳기보다는 여러 설정들이 꼬여서 이렇게 되는 경우가 다반사다!

이러면 정말 잡기 어려운 버그가 만들어진다. 항상 잡기 어려운 버그는 애매한 버그다. 

 

3) 최근 스프링 부트에서는 수동 빈 등록과 자동 빈 등록이 이름 충돌이 나면 아래 내용의 오류를 내도록 바뀌었다. 

스프링 부트를 실행해보자. 

"이미 AutoAppConfig.class 에 정의된(수동으로 등록한) 'memoryMemberRepository' 빈이 등록되지 못했다. 

MemoryMemberRepository 에 정의된 이름으로 빈이 등록되었기 때문이다. 그리고 오버라이딩이 불가능하다. "

The bean 'memoryMemberRepository', 
defined in class path resource [hello/corebasic/AutoAppConfig.class], 
could not be registered. 
A bean with that name has already been defined in file [/Users/.../hello/corebasic
/member/MemoryMemberRepository.class] and overriding is disabled.

스프링 부트의 기본 설정으로는 이렇게 오류를 내지만, 

application.yml 애플리케이션 프로퍼티 파일에 옵션을 아래처럼 바꾸면 수동 등록 빈 오버라이딩을 허용해준다. 

Consider renaming one of the beans or enabling overriding by setting 
spring.main.allow-bean-definition-overriding=true

 

[ 참고 ] 

어설픈 추상화를 하지 말자. 코드 양이 좀더 나오더라도 명확하게 코딩하는 것이 낫다.

애매한 상황과 코드는 피해야 한다. 애매한 버그가 제일 잡기 어렵기 때문이다. 그리고 개발은 혼자하는 것이 아니기 때문이다. 


다음 강의에서는 다양한 의존관계 주입 방법을 배운다. 

공부 내용 출처 :  스프링 핵심 원리 기본편 

728x90

목표

1. 자바 설정 정보 없이 스프링 빈을 등록할 수 있는 '컴포넌트 스캔'을 배운다. 
2. 의존관계를 자동으로 주입하는 @Autowired 기능을 배운다. 

'컴포넌트 스캔' 목차

1. 컴포넌트 스캔과 의존관계 자동 주입 시작하기 (이번 포스팅)

2. 탐색 위치와 기본 스캔 대상 

3. 필터 

4. 중복 등록과 충돌 


1. 컴포넌트 스캔과 의존관계 자동 주입 시작하기

지금까지 애노테이션 기반 자바 설정 정보로 스프링 빈을 생성했다. 

실무에서는 스프링 빈이 수십 수백개가 필요한데, 관리할 것이 많으면 실수가 나오게 된다. 좀 더 편리한 방법이 있을까? 

스프링은 설정 정보 파일이 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔 기능을 제공한다. 

 

컴포넌트 스캔으로 자바 설정 정보를 대체해보자. 

AppConfig.class 는 그대로 두고, 새롭게 AutoAppConfig.class 를 만들자. 이제 컴포넌트 스캔으로 스프링 빈을 등록해보자. 

 

1) 자바 설정 정보 AutoAppConfig.class 만들고, @Configuration 과 @ComponentScan 를 붙인다. 

기존의 AppConfig.class와는 다르게 @Bean으로 등록한 클래스가 하나도 없다. 

@ComponentScan 애노테이션은 @Component가 붙은 클래스를 찾아서 전부 스프링 빈으로 등록한다. 

 

2) [ 참고 ]

excludeFilters = @ComponentScan.Filter() 는 기존 예제 코드들을 유지하기 위한 작업이다.

AppConfig.class와 여러 테스트 코드 에 @Configuration이 붙어있다. 이것들을 컴포넌트 스캔에서 제외하기 위한 코드를 입력하자. 

@Configuration 를 열어보면 @Component 애노테이션이 붙어있다. 그래서 @Configuration 도 컴포넌트 스캔의 대상이다. 

 

3) 각 클래스가 컴포넌트 스캔의 대상이 되도록 @Component를 붙여준다. 

MemoryMemberRepository, RateDiscountPolicy, MemberServiceImpl 에 붙인다. 

4) '컴포넌트 스캔'에서 의존관계 주입은 어떻게 할까?

이전 자바 설정 파일에서는  MemberServiceImpl 구현체에 MemberRepository 의존관계를 생성자를 통해 주입했다.

컴포넌트 스캔에서는 생성자에 @Autowired 를 붙여서 자동으로 의존관계를 주입한다. 

이전에 설정 파일 AppConfig.class 에서는 @Bean 으로 직접 설정 정보를 작성했고 의존관계도 직접 명시했다. 

이제는 설정 정보 파일 자체가 없기 때문에 의존관계 주입도 클래스 안에서 애노테이션으로 해결해야 한다. 

 

OrderServiceImple 클래스에도 @Component를 붙여서 스프링 빈으로 등록하고 @Autowired 로 의존관계를 주입하자. 

5) 컴포넌스 스캔으로 스프링 빈을 등록하고 Autowired로 의존관계가 주입됬는지 테스트 코드를 작성해보자.

* 로그에서 컴포넌트 스캔의 내용을 확인할 수 있다. 

org.springframework.context.annotation.ClassPathBeanDefinitionScanner 
- Identified candidate component class: 
file [/Users../corebasic/discount/RateDiscountPolicy.class]

org.springframework.context.annotation.ClassPathBeanDefinitionScanner 
- Identified candidate component class: 
file [/Users../corebasic/member/MemberServiceImpl.class]

* 로그에서 의존관계 주입을 확인할 수 있다. memoryMemberRepository 이름의 빈이 생성자를 통해 자동주입됨. 

Creating shared instance of singleton bean 'orderServiceImpl'
Autowiring by type from bean name 'orderServiceImpl' via constructor to bean named 'memoryMemberRepository'
Autowiring by type from bean name 'orderServiceImpl' via constructor to bean named 'rateDiscountPolicy'

6) 컴포넌트 스캔과 자동 의존관계 주입이 어떻게 동작하는지 그림으로 알아보자. 

 

@ComponentScan @Component가 붙은 클래스를 찾아서 전부 스프링 빈으로 등록한다. 

스프링 빈의 기본 이름은 클래스명을 쓰는데 앞글자만 소문자로 바꿔서 쓴다. 직접 빈 이름을 지정할 수 도 있다. 

MemberServiceImpl - > memberServiceImpl

생성자에 @Autowired 를 붙이면, 스프링 컨테이너가 자동으로 해당 빈을 찾아서 주입한다. 

기본 조회 전략은 타입이 같은 빈을 찾는 것이다. 

아래 코드를 보면 MemberRepository 빈을 의존관계로 주입해야 하는 상황이다.

MemberRepository와 타입이 같은 것을 찾는다. 

memoryMemberRepository 가 MemberRepository의 구현체니까 타입이 맞다.

그래서 memoryMemberRepository를 꺼내서 주입한다. 

getBean(MemberRepository.class)와 동일하다고 이해하면 된다. 


2. 탐색 위치와 기본 스캔 대상

컴포넌트 스캔을 시작하는 위치와 대상에 대해 알아보자. 

 

1) 탐색할 패키지의 시작 위치 지정 : 탐색 위치를 지정 할 수 있다!

AutoAppConfig.class 파일을 열어보자. 어떤 패키지부터 탐색할 지 basePackages 를 지정할 수 있다. 

모든 자바 클래스를 다 컴포넌트 스캔하면 시간이 오래 걸리기 때문이다. 그래서 꼭 필요한 위치부터 탐색하도록 시작 위치를 지정할 수 있다. 

현재 프로젝트 네비게이션은 아래와 같이 여러개의 패키지로 구성되어 있다. 

member 패키지를 basePackages 로 지정하고 컴포넌트 스캔하면 어떻게 될까? 

basePackages = member 패키지를 포함해서 하위 패키지를 모두 탐색한다.

그래서 memberServiceImpl, memoryMemberRepository 만 스프링 빈으로 등록됬다.

orderServiceImpl같은 다른 패키지의 빈은 제외됬다. 

2) 탐색 위치를 지정하지 않으면 어떻게 될까? 

@ComponentScan 이 붙은 설정 정보 클래스의 위치 부터 하위 패키지를 모두 탐색한다. 

hello.corebasic 패키지 하위의 모든 자바 파일에 @Component가 붙었는지 검사한다. 

3) 권장하는 방법 : @ComponentScan 이 붙은 설정 정보 클래스의 위치를 최상단에 두자 ! 

최근 스프링부트도 이 방법을 기본으로 제공한다. 

 

예를 들어, 패키지가 아래와 같은 구조라면, hello.corebasic 여기가 프로젝트 시작 루트가 된다. (지금 AutoAppConfig.class위치)

프로젝트 시작 루트에 @ComponentScan 를 붙인 메인 설정 정보 클래스를 두고 basePackage 지정은 생략한다.

이렇게 하면 hello.corebasic  를 포함한 하위는 모두 컴포넌트 스캔의 대상이 된다. 

 

[ 참고 ] 스프링 부트를 사용하는 경우

스프링 부트의 대표 시작 정보인 @SpringBootApplication 를 이 프로젝트 시작 루트 위치에 두는 것이 관례이다. 

 

스프링 부트 프로젝트를 만들면 main() 메서드가 있는 프로젝트명으로 된 클래스가 자동 생성되어 있다. 

이 클래스에 이미 @SpringBootApplication 가 붙어있다. 

 @SpringBootApplication  애노테이션이 뭔지 싶어서 ctrl 을 누르고 들어가보면,  @ComponentScan 이 붙어있음을 확인할 수 있다.

그래서 스프링 부트를 실행하면 hello.corebasic 위치 부터 하위까지 다 스프링 빈이 등록된다.

스프링 부트를 쓰면 @ComponentScan 을 따로 달아줄 필요가 없다. 

 


4) 컴포넌트 스캔의 기본 대상

@ComponentScan뿐만 아니라 아래 내용도 컴포넌트 스캔 대상에 포함된다. 

  *  @Component 

  *  @Controller 

  *   @Service

  *   @Repository

  *   @Configuration

스프링 MVC를 다뤄봤다면 몇개는 익숙한 애노테이션일 것이다. 해당 클래스의 소스코드를 열어보면 @Component를 포함하고 있음을 확인할 수 있다. 

 

[ 참고 ] 애노테이션에는 상속관계 라는 것이 없다. 

애노테이션이 특정 애노테이션을 인식할 수 있는 것인 자바 언어가 지원하는게 아니라, 스프링이 지원하는 기능임을 인지하고 넘어가자. 

 

컴포넌트 스캔의 대상이 되면서도 아래의 애노테이션이 있으면 스프링은 부가 기능을 수행한다. 

예를 들어, @Controller 라면, MVC 컨트롤러로 인식한다. @Repository라면 데이터 계층의 예외를 스프링 예외로 변환해준다. 

 


다음 강의에서는 '컴포넌트 스캔의 필터와 빈 이름 중복'에 대해  배운다. 

공부 내용 출처 :  스프링 핵심 원리 기본편 

728x90

+ Recent posts