본문 바로가기
Spring | SpringBoot

[스프링 스터디] 6주차 - 스프링 핵심 원리.기본편

by saniii 2022. 2. 17.

공부한 github : https://github.com/anso33/SpringStudy_second.git


[ 섹션 7. 의존관계 자동 주입 ]

# 의존관계 주입 방법

+ 생성자 주입

: 생성자를 통해서 의존 관계를 주입 받는 방식

@Component
public class OrderServiceImpl implements OrderService {
      
    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;
	  
    @Autowired
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
}

- 생성자를 1번만 호출하는 것을 보장

- 생성자가 1개만 있을 때는 @Autowired를 생략해도 자동 주입할 수 있다. (스프링 빈에서)

- 불변, 필수 의존관계에 사용

 

 

+ 수정자 주입 (setter)

: 수정자 메서드 setter()을 통해서 의존관계를 주입하는 방식

@Component
public class OrderServiceImpl implements OrderService {
	
    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;
        
    @Autowired
    public void setMemberRepository(MemberRepository memberRepository) {
    	this.memberRepository = memberRepository;
    }
        
    @Autowired
    public void setDiscountPolicy(DiscountPolicy discountPolicy) {
        this.discountPolicy = discountPolicy;
    }
}

- 자바빈 프로퍼티 규약에 일치하는 방법

** 자바빈 프로퍼티 : 필드의 값을 직접 변경하지 않고 메서드 setter()를 이용하여 값을 읽거나 수정하는 규칙

 

 

+ 필드 주입

: 필드에 바로 주입하는 방식

@Component
public class OrderServiceImpl implements OrderService {

	@Autowired
	private MemberRepository memberRepository;

	@Autowired
	private DiscountPolicy discountPolicy;
}

- 외부에서 변경이 불가능해서 테스트하기 힘들다

- DI 프레임워크가 있어야만 한다.

- 쓰지마세요

   > 스프링 설정을 목적으로 하는 @Configuration같은 곳에서만 특별한 용도로 사용한다. 

 

 

+ 일반 메서드 주입

: 일반 메서드를 통해서 주입 받는 방식

@Component
public class OrderServiceImpl implements OrderService {
	
    private MemberRepository memberRepository;
	private DiscountPolicy discountPolicy;
	
    @Autowired
	public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
		this.memberRepository = memberRepository;
		this.discountPolicy = discountPolicy;
	}
}

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

- 일반적으로 잘 사용하지 않는다. 

 

 

 

# 최근의 경향으로는 생성자 주입 방식을 권장

  • 불변

-  의존관계의 대부분은 애플리케이션이 종료되기 전까지 불변해야한다.

- 수정자 주입을 사용하려면 setter()을 public 처리해야한다.

 

  • 누락

- 생성자 주입 방식을 사용하면 주입할 데이터를 누락했을때 컴파일 오류(Null Point Exception)가 발생하여 어떤 값을 필수로 주입해야하는지 알 수 있다. 

 

  • final 

- 생성자 주입 방식을 선택하면 필드에 final을 사용할 수 있어, 생성자에 값이 생성되지 않았을 때

컴파일 오류(variable discountPolicy might not have been initialized)가 발생한다. 

 

 

 

# 롬복

: getter, setter, toString 등의 메서드 코드를 줄여주는 JAVA 라이브러리

 

>> 이 코드를

@Component
public class OrderServiceImpl implements OrderService {

	private final MemberRepository memberRepository;
	private final DiscountPolicy discountPolicy;

	public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
		
        this.memberRepository = memberRepository;
		this.discountPolicy = discountPolicy;
	}
}

롬복을 사용하면 다음과 같이 줄일 수 있다. 

@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {

	private final MemberRepository memberRepository;
	private final DiscountPolicy discountPolicy;
}

 

 

 

>>> 생성자를 1개 두고 @Autowired는 생략, @RequiredArgsConstructor(롬복 라이브러리)를 사용하여 기능은 모두 유지하면서 코드는 간소화한다. 

 

 

 

# 조회하는 빈이 2개 이상일 때 오류가 발생한다. 

+ @Autowired는 빈을 Type으로 조회한다. > 타입으로 조회했을 때 선택된 빈이 2개 이상이면 오류가 발생한다.

 

ex)  FixDiscountPolicy와 RateDiscountPolicy를 동시에 @component (스프링빈 등록)하면 오류(NoUniqueBeanDefinitionException)가 발생

NoUniqueBeanDefinitionException: No qualifying bean of type
'hello.core.discount.DiscountPolicy' available: expected single matching bean
but found 2: fixDiscountPolicy,rateDiscountPolicy

 

# 해결 방법

@Autowired 필드 명 매칭  ▶  @Qualifier → @Qualifier끼리 매칭 → 빈 이름 매칭  ▶  @Primary 사용

 


[ 섹션 8. 빈 생명주기 콜백 ]

 

# 빈 생명주기(Bean LifeCycle)

: 객체가 생성되는 과정부터 어떤 작업을 수행하고, 어떻게 소멸되는지의 과정.

- 스프링 컨테이너는 이런 빈(Bean) 객체의 생명주기를 컨테이너의 생명 주기 내에서 관리하고, 객체 생성이나 소멸 시 호출될 수 있는 콜백 메서드를 제공한다. 

 

[ 참고 ]

https://haruhiism.tistory.com/186

 

 

# 빈 생명주기 콜백 시작

: 객체의 초기화와 종료 작업

- connect()를 호출해서 연결을 맺고, disConnect()를 호출해서 연결을 끊는다.

 

> 실행한 결과 

여기서 객체를 생성하는 단계에는 url이 없어 url정보 없이 connect가 호출된다. 

객체를 생성한 다음 외부에서 수정자 주입을 통해 setUrl()을 호출하면 url이 존재하게 된다. 

 

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

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

 

 

# 스프링이 Bean 생명주기 콜백을 지원하는 3가지 방법

  • 인터페이스 InitializingBean, DisposableBean
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

- InitializingBean : afterPropertiesSet()으로 초기화를 지원

- DisposableBean : Destroy()로 소멸을 지원

 
>> 결과

스프링 컨테이너의 종료가 호출되자 소멸 메서드가 호출되었다. 

 

** 단점

- 해당 인터페이스는 스프링 전용 인터페이스

- 초기화, 소멸 메서드의 이름을 변경할 수 없다.

- 개발자가 직접 코드를 고칠 수 없는 외부 라이브러리에는 적용할 수 없다.

 

 

 

  • 설정 정보에 초기화 메서드, 종료 메서드 지정 

: 설정 정보에 초기화, 소멸 메서드를 지정할 수 있다.

ex)  @Bean(initMethod = "init", destroyMethod = "close") 

>> 사용하여 실행한 결과

 

+ 설정 정보 사용 특징

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

- 스프링 빈이 스프링 코드에 의존하지 않는다.

- 코드가 아니라 설정 정보를 사용하기 때문에 코드를 고칠 수 없는 외부 라이브러리에도 초기화, 종료 메서드를 적용할 수 있다.

 

+ 종료 메서드 추론

- @Bean의 destroyMethod 속성에는 아주 특별한 기능이 있다.
- 라이브러리는 대부분 close , shutdown 이라는 이름의 종료 메서드를 사용한다.
- @Bean의 destroyMethod 는 기본값이 (inferred)(추론)으로 등록되어 있다.
- 이 추론 기능은 close , shutdown 라는 이름의 메서드를 자동으로 호출해준다. 이름 그대로 종료 메서드를 추론해서 호출해준다.
- 따라서 직접 스프링 빈으로 등록하면 종료 메서드는 따로 적어주지 않아도 잘 동작한다.
- 추론 기능을 사용하기 싫으면 destroyMethod="" 처럼 빈 공백을 지정하면 된다.

 

 

  •  @PostConstruct, @PreDestroy 애노테이션 지원

- 최신 스프링에서 가장 권장하는 방법이다.
- 애노테이션 하나만 붙이면 모두 해결!
- 패키지가 javax.annotation.PostConstruct == 스프링에 종속적인 기술이 아니라 JSR-250 라는 자바 표준

즉, 스프링이 아닌 다른 컨테이너에서도 동작한다.

 

- 컴포넌트 스캔과 잘 어울린다.

 

 

**단점

: 외부 라이브러리에는 적용하지 못한다

(해결방법) : 외부 라이브러리를 초기화, 종료 해야 하면 @Bean의 기능을 사용하자.

 

 

>> @PostConstruct, @PreDestroy 애노테이션을 사용하고 

코드를 고칠 수 없는 외부 라이브러리를 초기화, 종료해야할 때는 @Bean initMethod , destroyMethod 를 사용하자.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

댓글