endsharp / study

1 stars 0 forks source link

[20230108] 의존관계 자동 주입 #14

Open TGKim-Peter opened 1 year ago

TGKim-Peter commented 1 year ago

의존관계 자동 주입

LeeeeDain commented 1 year ago

의존관계 자동 주입

주입 방법

옵션 처리

static class TestBean {

    // 1. 자동주입할 빈이 없으면 -> 해당 메서드 자체가 호출안됨
    @Autowired(required = false)
    public void setNoBean1(Member noBean1) {
        System.out.println("noBean1 = " + noBean1);
    }

    // 2. 자동주입할 빈이 없으면 -> null 반환
    @Autowired
    public void setNoBean2(@Nullable Member noBean2) {
        System.out.println("noBean2 = " + noBean2);
    }

    // 3. 자동주입할 빈이 없으면 -> Optional.empty 반환
    @Autowired
    public void setNoBean3(Optional<Member> noBean3) {
        System.out.println("noBean3 = " + noBean3);
    }
}

롬복

타입이 같은 빈이 2개 이상일 경우

애노테이션 만들기

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

자동 빈 등록 vs 수동 빈 등록

롬복 사용시 주의사항

myeongho2 commented 1 year ago

의존관계 자동 주입

다양한 의존관계 주입 방법

생성자 주입

수정자 주입 (사용하는 경우가 있는가?)

@Component
public class OrderServiceImpl implements OrderService {
    private MemberRepository memberRepository;

    @Autowired(required = false) // 수정자 주입
    public void setMemberRepository(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}

@Autowired 의 기본 동작은 주입할 대상이 없으면 오류 발생, 주입할 대상이 없어도 동작하게 하려면 @Autowired(required = false) 지정

필드 주입

@Component
public class OrderServiceImpl implements OrderService {

    @Autowired
    private MemberRepository memberRepository;
}

일반 메서드 주입

@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;
    }
}

옵션 처리

자동 주입 대상을 옵션으로 처리하는 방법은 다음과 같다.

// 출력 결과
setNoBean2 = null
setNoBean3 = Optional.empty

참고: @Nullable, Optional 은 스프링 전반에 걸쳐서 지원되기 때문에 생성자 자동 주입에서 특정 필드에만 사용해도 된다.

생성자 주입을 선택해라!

과거에는 수정자 주입과 필드 주입을 많이 사용했지만, 스프링을 포함한 DI 프레임워크 대부분이 생성자 주입을 권장한다. 그 이유는 다음과 같다.

  • final 키워드를 사용하면 객체 생성 안했을 때 컴파일 오류가 발생하여 실수 방지할 수 있음
  • 수정자 주입을 포함한 나머지 주입 방식은 생성자 이후에 호출되므로, final 키워드를 사용할 수 없다.
  • 생성자 주입 방식을 선택하면 프레임워크에 의존하지 않고, 순수한 자바 언어의 특징을 잘 살리는 방법임
  • 생성자 주입과 수정자 주입 동시에 사용할 수 있기 때문에 필수 값이 아닌 경우에 수정자 주입 방식을 옵션으로 부여(진짜?)
  • 필드 주입 방식은 사용하지 않는게 좋다.(실제로 이런코드 많음, 보통 @Autowired 어노테이션 지우고 @RequiredArgsConstructor 어노테이션 적용했음)

불변

누락

롬복(Lombok)과 최신 트랜드

막상 개발해보면 대부분 불변.. 생성자 주입 자동으로 해줄 수 없을까?

  • [start.spring.io](http://start.spring.io/) 에서 프로젝트 생성할 때 dependency 추가 가능
  • Plugin 에서 Lombok 추가
  • Preferences > Compiler > Annotation Processors 에서 Enable annotation processing 체크
  • 대부분 @RequiredArgsConstructorfinal 키워드 함께 사용

@Autowired 는 타입으로 조회하기 때문에 등록된 Bean 이 2개 이상인 경우 에러 발생

NoUniqueBeanDefinitionException: No qualifying bean of type ..

조회 대상 Bean 이 2개 이상일 때 해결 방법

우선순위는 @Qualifier@Primary 보다 높다.

@Component
@MainDiscountPolicy
public class RateDiscountPolicy implements DiscountPolicy {}
// 생성자 자동 주입
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
                        @MainDiscountPolicy DiscountPolicy discountPolicy) {
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
}
// 수정자 자동 주입도 사용 가능

애노테이션에는 상속이라는 개념이 없다. 애노테이션을 모아서 사용하는 기능은 스프링이 지원해주는 기능임.

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

스프링을 사용하면 전략 패턴을 간단하게 구현 가능하다.

@RequiredArgsConstructor
static class DiscountService {
    private final Map<String, DiscountPolicy> policyMap;
    private final List<DiscountPolicy> policies;

    public int discount(Member member, int price, String discountCode) {
        DiscountPolicy discountPolicy = policyMap.get(discountCode);
        return discountPolicy.discount(member, price);
    }
}

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

**편리한 자동 기능을 기본으로 사용하자”**

“그러면 수동 빈 등록은 언제 사용하면 좋을까?”

“정리”

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

직접 등록하는 기술 지원 객체는 수동 등록

다형성을 적극 활용하는 비즈니스 로직은 수동 등록을 고민해보자

sixhustle commented 1 year ago

@myeongho2 aws sdk bean 생성할 때, 주입코드 공유

myeongho2 commented 1 year ago
@Configuration
@Profile({"!local"})
public class S3Config {

    @Value("${aws.s3.region:us-east-1}")
    private String region;

    @Bean(destroyMethod = "close")
    public S3Client s3Client() {
        return S3Client.builder().region(Region.of(region)).build();
    }
}