Bean 추가 내용

마자나다·2023년 12월 27일

Spring

목록 보기
5/9

개념이 조금 부족한것 같아서 Bean의 내용을 조금 보충하겠다!

AppConfig

  • 스프링의 컨테이너가 나오기전 AppConfig라는 클래스를 따로만들어서 의존성 주입과 제어의 역할을 따로 하도록 클래스를 분류 하였다.
 public class AppConfig {
     public MemberService memberService() {
         return new MemberServiceImpl(new MemoryMemberRepository());
    }
     public OrderService orderService() {
         return new OrderServiceImpl(
    }
}

위 코드처럼 AppConfig에서 객체를 생성하고, 연결하는 책임을 가지는 별도의 설정 클래스를 만들어 사용했었다.

좋은 객체 지향 설계의 원칙

위와같은 AppConfig클래스를 따로 분류하면서 지켜지는 원칙이 있다.

1. SRP 단일 책인 원칙 : 한 클래스는 하나의 책임만 가져야한다.
2. DIP 의존 관계의 원칙 : 추상화에 의존해야지, 구체화에 의존해선 안된다.
3. OCP : 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀있어야한다.

위 3가지 원칙이 AppConfig클래스가 모두 담당하게 됨으로, 개발자는 로직에 조금더 집중할수있고 프로그램 제어 흐름은 AppConfig가 담당하게 된다. 이로써 앞서 정리한 IoC 제어의 역전 (프로그램 제어 흐름을 직접 제어하는것이 아닌 외부에서 관리하게 된다.)이 가능해진다.

IoC컨테이너, DI컨테이너

  • AppConfig처럼 의존 관계를 연결해주는 것을 IoC컨테이너, DI컨테이너라고 한다.

Spring으로 전환

  • 앞선 블로그에서 다양한 방법으로 SpringBean으로 전환을 하게되고 사용을 하게 된다.
  • 위에 설명한 것과 같이 Bean은 좋은 객체지향 설계를 위해 AppConfig처럼 사용되게 되었다.
ApplicationContext applicationContext = new
 AnnotationConfigApplicationContext(AppConfig.class);
         MemberService memberService =
  • 롬복과 컴포넌트 스캔 기능을 활용하여 지금은 빈을 등록하지만 위 코드처럼 ApplicationContext의 스프링 컨테이너를 이용하여 Bean이 들어있는 클래스를 찾은뒤 .getBean 메서드를 활용하여 Bean을 사용하곤 한다.

BeanDefinition

  • 그런데 스프링은 어떻게 xml, 어노테이션, Config클래스 등 다양한 방법으로 Bean을 지원하는 것 일까? 바로 BeanDefinition이라는 추상화가 있다.
  • 위와같은 다양한 방법으로 구성되어있는 메타정보를 BeanDefinition이 만들어져 Bean의 정보가 저장된다.
  • 실무에선 거의 쓰일일이 없다. 스프링이 다양한 형태의 설정 정보를 BeanDefinition으로 추상화해서 사용하는 것 정도만 이해하면 된다.

  • 위와같이 다양한 기능들이 있다.

싱글톤의 보충 내용

스프링의 빈은 싱글톤이다! 라는 말로 저번 블로그를 작성하였다 조금 의미를 보충하도록 하겠다

  • 웹 어플리케이션은 많은 고객이 한꺼번에 신청한다. 그런데 고객의 요청이 있을때마다 DI컨테이너에서 계속 객체를 생성하게 되면 매우 비효율적이다.
  • 때문에 싱글톤으로 만들어서 한꺼번에 많은 객체 요청이 들어와도 같은 객체를 반환하도록 싱글톤 객체를 사용한다.

싱글톤 방식의 주의점!!

  • 싱글톤 객체는 상태를 유지하게 설계해선 안된다! 절대로 무상태로 설계하여야 한다
  • 상태를 가지고 있게 된다면 같은 객체를 사용하다 보니 동시에 멀티스레드로 다른 요청이 들어오게 되면 요청이 제대로 반영되지 않고 상태를 들고있던 정보로 부작용이 생길 수있다. (ex : 돈과 관련된 문제가 생기면 증말 큰일난다.. 1만원 주문했는데 10만원 주문한 사람과 꼬여서 10만원이 결제될수도있다..)
  • 때문에 상태를 저장하지않고 바로바로 Return 해주어야한다.
  • @Configuration클래스에 @Bean으로 등록을 하는데 @Configuration이 없더라도 @Baen등록은 무리가 없다. 하지만! 싱글톤이 전혀 보장되지 않는다!!!

의존관계 주입 방법

의존관계 주입방법은 다양하게 있다
1. 생성자 주입
2. 수정자 주입(setter 주입)
3. 필드 주입
4. 일반 메서드 주입 (사실상 수정자 주입이랑 똑같다. 그래서 잘 사용되지 않는다)

생성자 주입만 사용해라

  • 사실상 생성자 주입만 사용되는 상황이다.
  • 생성자를 사용하면 불변이되고, 누락을 막을 수 있다.
  • 오직 생성자 주입에서만 final을 사용할 수 있기 때문에 누락방지도 되고 매우 좋다.

만약 조회하는 빈이 2개 이상이라면?

빈을 사용할려했지만 구현체가 2개이상 만들어져있는 Bean이라면 스프링 NoUniqueBeanDefinitionException 오류가 발생한다.
예시

ac.getBean(DiscountPolicy.class) // 다른 클래스에서 DiscountPolicy빈을 호출

 @Component
 public class FixDiscountPolicy implements DiscountPolicy {}

 @Component
 public class RateDiscountPolicy implements DiscountPolicy {}
 
 // `DiscountPolicy` 의 하위 타입인 `FixDiscountPolicy` , `RateDiscountPolicy` 둘다 스프링 빈으로 선언 되어있음.

그럼 대처하기위해 어떻게 할까?

  1. 필드 명을 빈 이름으로 변경
@Autowired private DiscountPolicy rateDiscountPolicy

위와같이 필드명을 빈이름으로 만들어준다.
@Autowired는 타입을 매칭하고 여러개가 발견되면 필드명을 매칭해서 빈을 찾는다!
2. Qualifier 사용
빈 등록시 @Qualifier를 붙여 준다.

 @Component
 @Qualifier("mainDiscountPolicy")
 public class RateDiscountPolicy implements DiscountPolicy {}


 @Component
 @Qualifier("fixDiscountPolicy")
 public class FixDiscountPolicy implements DiscountPolicy {}

주입시에 @Qualifier를 붙여주고 등록한 이름을 적어준다. 생성자 자동 주입 예시

 @Autowired
 public OrderServiceImpl(MemberRepository memberRepository,
                         @Qualifier("mainDiscountPolicy") DiscountPolicy
 discountPolicy) {
     this.memberRepository = memberRepository;
     this.discountPolicy = discountPolicy;
}
  1. @Primary 사용
    @Primary 는 우선순위를 정하는 방법이다. @Autowired 시에 여러 빈이 매칭되면 @Primary 가 우선권을 가진다.
 @Component
 @Primary
 public class RateDiscountPolicy implements DiscountPolicy {}
 @Component
 public class FixDiscountPolicy implements DiscountPolicy {}

어노테이션 만들어서 사용하기

@Qualifier("mainDiscountPolicy") 이렇게 문자를 적으면 컴파일시 타입 체크가 안된다. 다음과 같은 애노 테이션을 만들어서 문제를 해결할 수 있다.

 @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER,
 ElementType.TYPE, ElementType.ANNOTATION_TYPE})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 @Qualifier("mainDiscountPolicy")
 public @interface MainDiscountPolicy {
}

 @Component
 @MainDiscountPolicy
public class RateDiscountPolicy implements DiscountPolicy {}
  • 위 코드와 같이 어노테이션 범위를 지정하고 사용될 어노테이션을 모아준 뒤 빈을 등록할때 @MainDiscountPolicy을 붙여준다.
// 생성자 자동주입
 @Autowired
 public OrderServiceImpl(MemberRepository memberRepository,
                         @MainDiscountPolicy DiscountPolicy discountPolicy) {
     this.memberRepository = memberRepository;
     this.discountPolicy = discountPolicy;
 }
  • 그리고 생성자에서 매개변수에 어노테이션을 붙여 원하는 빈을 등록할 수 있다.

조회할 빈이 모두 필요할 때

예를 들어서 할인 서비스를 제공하는데, 클라이언트가 할인의 종류(rate, fix)를 선택할 수 있다고 가정해보자. 스프링을 사용하면 소위 말하는 전략 패턴을 매우 간단하게 구현할 수 있다

public class AllBeanTest {
    @Test
    void findAllBean() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class);
        DiscountService discountService = ac.getBean(DiscountService.class);
        Member member = new Member(1L, "userA", Grade.VIP);
        int discountPrice = discountService.discount(member, 10000, "fixDiscountPolicy");
        assertThat(discountService).isInstanceOf(DiscountService.class);
        assertThat(discountPrice).isEqualTo(1000);
    }
    static class DiscountService {
        private final Map<String, DiscountPolicy> policyMap;
        private final List<DiscountPolicy> policies;
        public DiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> policies) {
            this.policyMap = policyMap;
            this.policies = policies;
            System.out.println("policyMap = " + policyMap);
            System.out.println("policies = " + policies);
        }
        public int discount(Member member, int price, String discountCode) {
            DiscountPolicy discountPolicy = policyMap.get(discountCode);
            System.out.println("discountCode = " + discountCode);
            System.out.println("discountPolicy = " + discountPolicy);
            return discountPolicy.discount(member, price);
} }
 }
  • DiscountService는 Map으로 모든 DiscountPolicy 를 주입받는다. 이때 fixDiscountPolicy ,
    rateDiscountPolicy 가 주입된다.
  • ap<String, DiscountPolicy>: map의 키에 스프링 빈의 이름을 넣어주고, 그 값으로DiscountPolicy` 타입으로 조회한 모든 스프링 빈을 담아준다.

출처 및 참고: 김영한의 스프링 핵심원리 기본편 강의

profile
우왕좌왕 개발

0개의 댓글