의존 관계 주입 방법 4가지
1. 생성자 주입
2. 수정자 주입
3. 필드 주입
4. 메소드 주입
1. 생성자 주입
불변, 필수 의존관계에서 사용된다. 즉 바꿀 필요가 없는 의존 관계이고 필수적으로 필요할 때 사용한다.
-생성자 호출시점에 딱 1번만 호출된다
-불변, 필수 의존 관계에 사용된다
@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;
}
}
@Autowired를 통해 memberRepository와 discountPolicy가 자동 주입된다.
생성자가 하나일 경우에 Autowired 어노테이션을 생략해도 된다!
2. 수정자 주입
setter로 외부에서 지정 및 수정이 가능하도록 의존 관계를 주입하는 방법이다
-선택 변경 가능성이 있는 의존관계에 사용된다.
-자바빈 프로퍼티 규약의 수정자 메서드 방식 사용
cf) 자바 빈 프로퍼티 규약:
setXxx, getXxx 라는 메서드를 통해서 값을 읽거나 수정하는 규칙
@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;
}
}
cf)@Autowired의 기본 동작은 주입할 대상이 없으면 오류가 발생한다. 주입할 대상이 없어도 동작하게
하려면 @Autowired(required = false)로 지정하면 된다.
3. 필드 주입
필드에 바로 주입하는 방식
-간결한 코드가 장점, 외부에서 변경 불가능해 테스트 힘든 점이 단점
-DI 프레임워크 없이는 쓸모 없다
-사용하지 않는 것을 기본 규칙으로 삼자
@Component
public class OrderServiceImpl implements OrderService {
@Autowired
private MemberRepository memberRepository;
@Autowired
private DiscountPolicy discountPolicy;
}
4. 일반 메서드 주입
일반 메서드를 통해서 주입
-한번에 여러 필드 주입 가능
-별로 사용하지 않음
@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;
}
}
옵션 처리
해당 스프링 빈을 찾지 못할 때도 동작해야 될 때가 있다.
@Autowired의 required 기본 옵션값이 true이기 때문에 자동 주입 대상이 없으면 오류가 발생한다.
1. @Autowired(required=false)
자동 주입 대상이 없으면 메서드 자체 호출 안됨
2.@Nullable
자동 주입 대상이 없으면 null이 입력됨
3.Optional<>
자동 주입 대상이 없으면 Optional.empty가 입력됨
// ❌ 빈 없으면 아예 호출 자체 안됨
@Autowired(required = false)
public void setNoBean1(Member member) {
System.out.println("setNoBean1 = " + member);
}
// ✅ 빈 없으면 null로 호출됨
@Autowired
public void setNoBean2(@Nullable Member member) {
System.out.println("setNoBean2 = " + member);
}
// ✅ 빈 없으면 Optional.empty로 호출됨
@Autowired
public void setNoBean3(Optional<Member> member) {
System.out.println("setNoBean3 = " + member);
}
출력 결과
setNoBean2 = null
setNoBean3 = Optional.empty
Member는 스프링 빈이 아니기 때문에 주입이 되지 않는다 그래서
null, Optional.empty.. 등이 출력됨
생성자 주입을 선택해라 - 권장 방법
why?
1. 불변
의존 관계가 주입되고 변경할 일을 거의 없다. 오히려 대부분의 의존관계는 애플리케이션 종료 전까지 불변해야 한다.
if 수정자 주입 사용 -> public으로 열어둬야 한다
그렇게 되면 누군가 실수로 변경할 수도 있다. 변경하면 안되는 메서드를 열어두는 것은 좋지 않음
공연 전에 배역이 모두 정해져야 하는 것이 좋은 감독자의 자세 -> 즉 불변한 것이 좋다
2. 누락
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;
}
// ...
}
위 코드로 테스트 실행
@Test
void createOrder() {
OrderServiceImpl orderService = new OrderServiceImpl();
orderService.createOrder(1L, "itemA", 10000);
}
NPE(Null Point Exception) 발생
memberRepository, discountPolicy 모두 의존관계 주입이 누락 되었기 때문이다.
그러나 생성자 주입을 사용하면 주입 데이터가 누락되었을 때 컴파일 오류가 발생한다.
그리고 IDE에서 어떤 값을 필수로 주입해야 하는지 알 수 있다.
->final 키워드
생성자 주입을 사용하면 final 키워드를 사용할 수 있다.
생성자에서 주입되고 변화할 일이 없기 때문이다
정리
기본으로 생성자 주입을 사용하고, 필수 값이 아닌 경우에는 수정자 주입 방식을 옵션으로 부여하면 된다. 생성자
주입과 수정자 주입을 동시에 사용할 수 있다.
항상 생성자 주입을 선택해라
그리고 가끔 옵션이 필요하면 수정자 주입을 선택해라.
필드 주입은 사용하지 않는게 좋다.
롬복과 최신 트렌드
롬복 적용 전
@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;
}
롬복 라이브러리가 제공하는 `@RequiredArgsConstructor` 기능을 사용하면
final이 붙은 필드를 모아서 생성자를 자동으로 만들어준다.
Lombok
@Getter, @Setter | getter/setter 자동 생성 | user.getName() |
@ToString | toString 메서드 자동 생성 | 객체 정보 출력 |
@EqualsAndHashCode | equals(), hashCode() 자동 생성 | 비교 시 유용 |
@NoArgsConstructor | 기본 생성자 생성 (파라미터 없음) | new A() |
@AllArgsConstructor | 모든 필드 초기화하는 생성자 생성 | new A(x, y, z) |
@RequiredArgsConstructor | final 필드만 초기화하는 생성자 생성 | 스프링 주입에 자주 사용 |
@Builder | 빌더 패턴 생성 | .builder().name("A").build() |
@Data | @Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgsConstructor 합친 것 | DTO에 자주 사용 |
롬복 적용 방법
plugins {
id 'org.springframework.boot' version '2.3.2.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
}
group = 'hello'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
// lombok 설정 추가 시작
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
// lombok 설정 추가 끝
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
// lombok 라이브러리 추가 시작
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
// lombok 라이브러리 추가 끝
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
test {
useJUnitPlatform()
}
조회 빈이 2개 이상인 경우
만약 @Autowired로 DiscountPolicy를 받을 때 Spring은 혼동이 온다.
빈으로 등록된 rateDiscountPolicy와 FixDiscountPolicy 2개가 빈으로 등록되어 있기 때문이다.
그래서 이렇게 의존관계 자동 주입을 실행하면
NoUniqueBeanDefinitionException 오류가 발생한다.
해결 방법
1. 필드명 매칭
private DiscountPolicy discountpolicy
-> private DiscountPolicy rateDiscountPolicy로 바꾼다.
먼저 타입 매칭을 시도 하고 그 결과에 여러 빈이 있을 때 추가로 동작하는 기능
2. @Qualifier 사용
빈이름을 변경하는 것이 아니다. 추가 구분자를 붙여주는 방식이다. 별명 태그를 붙이는 느낌?
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {}
주입 예시
-생성자 주입
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
-수정자 주입
@Autowired
public DiscountPolicy setDiscountPolicy(@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
@Qualifier로 주입할 때 @Qualifier("mainDiscountPolicy") 를 못찾으면 어떻게 될까? 그러면
mainDiscountPolicy라는 이름의 스프링 빈을 추가로 찾는다.
@Qualifier는 @Qualifier를 찾는 용도로만 사용하는게 명확하고 좋다.
3. @Primary 사용
우선 순위를 정하는 방식
여러 개의 빈이 매칭되었을 때 @Primary가 우선권을 가진다
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
이 경우에 RateDiscountPolicy가 우위를 갖는다.
메인 데이터베이스의 커넥션을 획득하는 스프링 빈은 @Primary 를 적용해서 조회하는 곳에서
@Qualifier 지정 없이 편리하게 조회하고,
서브 데이터베이스 커넥션 빈을 획득할 때는 @Qualifier를 지정해서
명시적으로 획득 하는 방식으로 사용하면 코드를 깔끔하게 유지할 수 있다
어노테이션 직접 만들기
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER,
ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier("mainDiscountPolicy") // Qualifier를 감쌈
public @interface MainDiscountPolicy {
}
해당 빈에 위 어노테이션을 붙여보자
1. 해당 빈에 직접 붙이기
@Component
@MainDiscountPolicy
public class RateDiscountPolicy implements DiscountPolicy {
}
2.주입 받는 쪽에서 사용
생성자 주입
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
@MainDiscountPolicy DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
수정자 주입
@Autowired
public void setDiscountPolicy(@MainDiscountPolicy DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
어노테이션에는 상속이라는 개념이 없다!
스프링이 여러 어노테이션을 모아서 사용하는 기증을 지원해주는 것이다.
항목 설명
✅ 타입 안전 | 컴파일 시점에 에러 잡을 수 있음 |
✅ 명확성 | 역할이 명확한 이름으로 표현됨 (@MainDiscountPolicy) |
✅ 재사용성 | 다른 곳에서도 반복 없이 같은 설정 사용 가능 |
✅ 오타 방지 | 문자열이 아닌 애노테이션이니까 실수할 여지 줄어듦 |
@Qualifier("문자열") 대신 커스텀 애노테이션(@MainDiscountPolicy)을 만들면 타입 안정성 + 가독성 + 재사용성을 모두 챙길 수 있다!
조회한 빈이 모두 필요할 때, List, Map
public class DiscountService {
private final Map<String, DiscountPolicy> policyMap;
private final List<DiscountPolicy> policies;
@Autowired
public DiscountService(Map<String, DiscountPolicy> policyMap,
List<DiscountPolicy> policies) {
this.policyMap = policyMap;
this.policies = policies;
}
public int discount(Member member, int price, String discountCode) {
DiscountPolicy discountPolicy = policyMap.get(discountCode);
return discountPolicy.discount(member, price);
}
}
Map<String, DiscountPolicy> | key = 빈 이름, value = DiscountPolicy 빈 객체 |
List<DiscountPolicy> | 등록된 모든 DiscountPolicy 빈 |
discountCode로 key값을 받아서 특정 할인 정책 빈을 가져오고 이를 적용하는 식으로 로직을 형성하였다.
cf) 스프링 컨테이너를 생성하면서 스프링 빈 등록하기
ApplicationContext ac =
new AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class);
1. AnnotationConfigApplicationContext → 스프링 컨테이너 생성
2. 인자로 넘긴 클래스들을 자동으로 빈 등록
→ 즉, 컨테이너 생성 + 빈 등록을 동시에 해줌
자동 VS 수동 - 실무 기준
구분 자동 등록 (@Component) 수동 등록 (@Bean, @Configuration)
✅ 기본 권장 | 편리하고 빠르며 대부분 상황에 적합 | 설정 코드가 길고 복잡하지만 명확 |
💡 사용 시점 | 컨트롤러, 서비스, 리포지토리 등 일반 로직 | 기술 지원 객체, AOP, 공통 로직, 다형성 전략 |
✅ 가시성 | 코드만 보면 어디서 등록됐는지 추적 어려움 | 설정 파일만 보면 한눈에 파악 가능 |
💡 다형성 전략 | Map<String, Type> 사용 시 애매함 → 수동 등록 고려 | 타입-이름-구현 명확히 드러남 |
다형성 전략 시 수동 등록이 가독성이 좋다!
@Configuration
public class DiscountPolicyConfig {
@Bean
public DiscountPolicy rateDiscountPolicy() {
return new RateDiscountPolicy();
}
@Bean
public DiscountPolicy fixDiscountPolicy() {
return new FixDiscountPolicy();
}
}
어떤 구현체가 등록되는지, 이름이 뭔지, 자동 주입 시 어떤 이름을 써야 하는지 모두 한눈에 파악할 수 있다
자동 등록을 기본으로 사용하되, 기술 지원 객체나 다형성 전략 구조는 수동 등록으로 명확하게 드러내는 것이 유지보수에 유리하다.
'WINK-(Web & App) > Spring Boot 스터디' 카테고리의 다른 글
[2025 1학기 스프링부트 스터디] 장민주 #7주차 (0) | 2025.05.27 |
---|---|
[2025 1학기 스프링 부트 스터디] 남윤찬 #7주차 (0) | 2025.05.27 |
[2025 1학기 스프링 부트 스터디] 여민호 #7주차 (1) | 2025.05.26 |
[2025 1학기 스프링 부트 스터디] 이상래 #7주차 (0) | 2025.05.25 |
[2025 1학기 스프링부트 스터디] 고윤정 #7주차 (0) | 2025.05.24 |