When to use component scan?
코드레벨에서 설정 정보가 없어도 자동으로 스프링 빈을 자동 등록하려 할 때
How to use?
class 어노테이션으로 '@Component' 혹은 그 자식 어노테이션을 붙여준다.
이때, 생성자에는 '@Autowired' 등으로 자동으로 DI가 일어날 수 있도록 매핑해주는 것이 좋다.
@Component
public class RateDiscountPolicy implements DiscountPolicy {
private final int discountPercent = 10;
// .. 생략
}
스프링 빈의 기본 이름은 클래스명을 사용하되 맨 앞글자만 소문자를 사용한다. (PascalCase -> camelCase)
빈 이름 직접 지정시 @Component(새로운_빈_이름) 이런 식으로 지정해줄 수 있다.
프로젝트 최상단에
설정정보 클래스(@SpringBootApplication)
를 두는 것이 국룰이되었다.
package milkcoke.core;
// 스프링 부트의 초기화시 자동으로 생성되는 클래스가
// 프로젝트 최상단에 위치한다.
// @SpringBootApplication 은 Component 스캔을 포함한다.
@SpringBootApplication
public class CoreApplication {
public static void main(String[] args) {
SpringApplication.run(CoreApplication.class, args);
}
}
동작 과정
Component Scan
@Component 어노테이션이 붙은 모든 클래스를 뒤져서 Spring Container 에 등록
이 때, camelCase 로 자동으로 변경하여 등록
AutoWired
모든 빈을 '타입'으로 조회하여 자동 의존관계 주입, 동일 타입이 여러개 있을 경우 충돌이 발생할 수 있음.
부모타입 지정시 당연히 예하의 자식 타입은 모두 스캔됨.
생성자 파라미터 개수와 상관없이 알아서 찾아서 자동 주입.
다양한 의존관계 주입 방법
- 생성자 주입
- setter 주입
- 필드 주입
- 일반 메소드 주입
1. 생성자로 주입
생성자로 딱 1회 호출되는 것이 보장된다.
When to use?
불변/필수 의존관계에 사용
거의 항상.
생성자가 유일한 경우 @AutoWired 어노테이션 생략이 가능하다.
bean 을 등록하며 의존관계 주입도 동시에 일어난다. (어차피 Spring Container 가 스프링 빈 객체를 싱글톤으로 생성하여 등록해야하므로)
@Service
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;
}
}
2. setter 주입
Spring container 가
(1) bean 을 우선 등록하고
(2) 의존관계를 찾아 알아서 주입해준다. (`@Autowired` 를 보고)
When to use?
선택/변경 가능성이 있는 의존관계에 사용.
memberRepository, discountPolicy 가 존재하지 않을때도 등록하도록 할 때
@Autowired 의 주입 대상이 존재하지 않아도 동작하게 끔 하는 옵션
(required = false)
@Service
public class OrderServiceImpl implements OrderService{
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
// 필수가 아닌 선택, 변경값으로 지정 가능.
@Autowired(required = false)
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Autowired(required = false)
public void setDiscountPolicy(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
}
3. 필드 주입
간결하지만, 권장하지 않는 방식이다.
Spring Container 도움 없이 순수 자바/코틀린 기반으로 dummy 데이터로 값을 대체해서 커스텀할 수 있는 여지가 없어진다. (별도로 setter 를 만들어야만 한다.)
When to use?
DI 프레임워크 내에서만 동작하는 코드
애플리케이션과 실제 관계 없는 스프링에 종속적인 코드 테스트
필드 주입은 쓰지마라.
public class OrderServiceImpl implements OrderService{
@Autowired private MemberRepository memberRepository;
@Autowired private DiscountPolicy discountPolicy;
// .. 생략
}
테스트 코드 레벨에서 memberRepository, discountPolicy 를 임의지정 하는게 불가능해진다.
=> 안티패턴이므로 사용을 지양할 것
4. 일반 메소드 주입
생성자 주입과 setter 주입 방법이 제공하는 이점이 더 크기 때문에 거의 사용하지 않는다.
@Service
public class OrderServiceImpl implements OrderService{
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
// 일반 메소드에 주입하는 방식
// Spring Bean 등록 => DI 주입 by normal method by the `@Autowired` annotation.
@Autowired
public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
Spring Container 가 관리하는 Bean 만
의존관계 주입이 발생한다.
Autowired 옵션
자동 주입 대상을 옵션으로 처리하는 방법
- Autowired(required = false)
의존관계가 존재하지 않을 때 메소드 호출자체가 일어나지 않는다. - @Nullable annotation
주입 대상이 존재하지 않을 때 null 값을 대입한다. - Optional<T>
주입 대상이 존재하지 않을 때 Optional.empty 값을 대입한다.
public class AutowiredTest {
static class TestBean {
// 의존관계가 존재하지 않을 때, 메소드 자체가 아예 호출되지 않는다.
@Autowired(required = false)
public void setNoBean1(Member member1) {
System.out.println("member1 = " + member1);
}
@Autowired
public void setNoBean2(@Nullable Member member2) {
System.out.println("member2 = " + member2);
}
@Autowired
public void setNoBean2(Optional<Member> member3) {
System.out.println("member3 = " + member3);
}
}
@Test
void autowiredOptionTest() {
// TestBean 클래스에 대한 BeanDefinition 메타 데이터를 생성하고 Spring Bean 을 Spring Container 에 직접 등록
// 현재는 아무런 Bean 정보가 등록되있지 않으므로 깡통 빈이 등록됨.
var ac = new AnnotationConfigApplicationContext(TestBean.class);
}
}
Member.java
Member 클래스는 순수 자바 코드 도메인 클래스일 뿐이기 때문에 의존관계가 존재하지 않는다.
public class Member {
private Long id;
private String name;
private Grade grade;
public Member(Long id, String name, Grade grade) {
this.id = id;
this.name = name;
this.grade = grade;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Grade getGrade() {
return grade;
}
public void setGrade(Grade grade) {
this.grade = grade;
}
}
왜 DI 중 생성자 주입이어야 하나요?
생성자 DI 를 해야하는 이유
- 대부분의 애플리케이션에서 의존관계가 런타임시에 변경되야 하는 일은 없기 때문이다.
- 수정자 주입 사용시 public 으로 setter 메소드를 열어둬야한다. (변경 위험 도래)
- 객체 생성시 딱 1회만 호출되기 때문에 불변하게 설계할 수 있음.
- field 에 final 키워드를 사용하여 개발자의 실수를 방지할 수 있다.
생성자를 통한 모든 멤버를 강제 입력하게하여 의존성 주입 누락을 방지한다.
'JVM > Spring' 카테고리의 다른 글
[SpringBoot] Naming methods in each layer (0) | 2023.02.09 |
---|---|
[Spring] Library 살펴보기 및 스프링 환경설정 (0) | 2022.12.12 |
[Spring] Bean Scope (0) | 2022.12.07 |
[Spring] 빈 생명주기 콜백 (0) | 2022.12.06 |
[Spring] Autowired 사용시 주의 (Feat. Custom Annotation) (0) | 2022.12.01 |