본문 바로가기

Spring/기본

의존성

자바코드 기반


1.  기본적인 방식의 의존 

MemberService service = new MemberServiceImpl();

문제점: 

  •  OCP & DIP 문제 
  • 특히 DIP에서는 인터페이스와 구현 클래스 함께 의존 

 

 

2. AppConfig를 통해 의존성 분리 

public class AppConfig{

	public MemberService memberService(){
    	return new MemberServiceImpl(memberRepository());
    }
    
    public MemberRepository memberRepository(){
    	return new MemoryMemberRepository();
    }
}

//Impl 생성자
public MemeryServiceImpl(MembmerRepository memberRepository){
	this.memberRepository = memberRepository //인터페이스
}

 

  • AppConfi처럼 객체를 생성하고 관리하고 의존관계를 연결해 주는 것을 IoC컨테이너, DI컨테이너 라고 한다 (또는 어샘블러,  오브젝트 펙토리)

 

스프링 기반

 

3. ApplicationContext

//1. AppConfigd에 @Configuration & @Bean 붙이기 = 스프링 컨테이너에 등록됨. 

//2. main 코드

ApplicationContext aplicationContext = new AnnotationConfigApplicationContext(AppConfig.class)
MemberService memberService = applicationContext.getBean("memberService", MemerServie.class);


//log
Creating shared instance of singleton bean '....Annotation'
Creating shared instance of singleton bean 'memberService'

 

  • 개발자가 직접 AppConfig등록 할 필요 없이 @Configuration이 붙은 AppConfi구성 정보로 사용해서 @Bean 적힌 메서드 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록. 
  • 등록된 객체 = 스프링 빈
  • ApplicationContext는 스프링 컨테이너라고 한다. 그리고 이것은 인터페이스다. 
  • **빈 이름은 항상 다른 이름을 부여해야 함. 

- 모든 빈 출력

AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class)


//스프링 내부에 등록된 모든 빈
@Test
void findAllBean(){
	String[] beanDefinitionNames = ac.getBeanDefinitionNames();
    for(String beanDefinitionName : beanDefinitionNames){
    	Object bean = ac.getBean(beanDefinitionNames);
        System.out.println("name =" + beanDefinitionName + " Object = " bean);
        
    }
}

// 내가 등록한 빈
@Test
void findAllBean(){
	String[] beanDefinitionNames = ac.getBeanDefinitionNames();
    for(String beanDefinitionName : beanDefinitionNames){
    	BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
        
        if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION){
        	Object bean = ac.getBean(beanDefinitionName);
            System.out.println("name = " + beanDefinitionName + " object = " + bean);
        }
    }	
}

//특정 타입조회
@Test
void findAllBeanByType(){
	Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
    for(String key : beansOfType.keySet()){
    	System.out.println("key = " + key + " value = " + beansOfType.get(key));
    }
}

 

  • ROLE_INFRASTRUCTURE : 스프링이 운영에 사용하는 빈. 

- 조회

@Test
@DisplayName("이름으로 조회")
void findBeanByName(){
	MemberService memberService = ac.getBean("memberService", MemberService.class);
    assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}

@Test
@DisplayName("이름없이 타입으로 조회")
void findBeanByName(){
	MemberService memberService = ac.getBean(MemberService.class);
    assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}

@Test
@DisplayName("구체 타입으로 조회")
void findBeanByName(){
	MemberService memberService = ac.getBean("memberService", MemberServiceImpl.class);
    assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}

@Test
@DisplayName("빈 이름으로 조회 안됨")
void findBeanByNameX(){
	assertThrows(NoSuchBeanDefinitionException.class,
    	() -> ac.getBean9"xxxx", MemberService.class));
}

 

- 같은 타입 둘 이상시 중복 오류

@Test
@DisplayName("타입 조회시 같은 타입 둘 이상")
void findBeanByTypeDuplicate(){
	assertThrow(NoUniqueBeanDefinitionException.class, 
    		() -> ac.getBean(MemberRepository.class));
}

@Test
@DisplayName("빈 이름 지정")
void findBeanByTypeDuplicate(){

	MemberRepository memberRepository = ac.getBean("memberREpositor1", MemberRepository.class)
    assertThat(memberRepository).isInstanceOf(MemberRepository.class);
}


@Configuration
static class SameBeanConfig{

	@Bean
    public MemberRepository memberRepository1(){
    	return new MemoryRepository();
    }
    
	@Bean
    public MemberRepository memberRepository2(){
    	return new MemoryRepository();
    }
}

 

 

4. 상속관계

  • 부모 타입으로 조회하면 자식 타입도 함께 조회
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);

@Test
@DisplayName("부모 타입 조회시, 자식 둘 이상이면 중복 오류")
void findBeanByParentTypeDuplicate(){
    assertThrows(NoUniqueBeanDefinitionException.class, 
    	() -> ac.ac.getBean(DiscountPolicy.class));
}

@Test
@Display("이름 지정")
void findBeanByParentTypeBeanName(){
	DiscountPOlicy rateDiscountPolicy = ac.getBean("rateDiscountPolicy", DiscountPolicy.class);
    assertThat(rateDiscouuntPolicy).isInstanceOf(RateDiscountPolicy.class);
}

@Test
@DisplaName("특정 하위 타입으로 조회")//비추천
void findBeanBySubType(){
	RateDiscountPolicy bean = ac.getBean(RateDiscountPolicy.class);
    assertThat(bean).isInstanceOf(RateDiscountPolicy.class);
}

@Test
@DisplayName("부모 타입으로 모두 조회")
void findAllBeanByParentType(){
	Map<String, DiscountPolicy> beansOfType = ac.getBeansOfType(DiscountPolicy.class);
    assertThat(beansOfType.size()).isEqualTo(2);
    for (String key : beansOfType.keySet()){
    	System.out.println("key = " + key + " value = " + beansOfType.get(key));
    }
}

@Configuration
static class TestConfig{
	@Bean
    public DiscountPolicy rateDiscountPolicy(){
    	return new RateDiscountPolicy();
    }
	@Bean
    public DiscountPolicy fixDiscountPolicy(){
    	return new FixDiscountPolicy();
    }
}

 

  • <Interface> Applicationcontext
    • BeanFactory 상속받아서 제공. 거기에 부가 기능
    • 메시지 소스를 활용한 국제화 기능 (<Interface>MessageSource)
    • 환경변수 관리: 로컬, 개발, 운영등을 구분해서 처리 (<Interface>EnvironmentCapble)
    • 어플리케이션 이벤트 : 이벤트 발행하고 구독하는 모델 지원 (<Interface>ApplicationEventPublisher)
    • 편리한 리소스 조회 : 파일, 클래스패스, 외부 등에서 리소스 조회 (<Interface> ResourceLoader)

 

5. BeanDefinition

- ApplicationContext -> AnnotationConfigAC, GenericXmlAC 등 에서 설정정보 읽어서 BeanDefinition생성. (BeanDefinition으로 추상화 하여 관리)

-> 커스터마이징 가능. 

 

6. 의존성 주입

-1. 생성자 주입 (사용 권장)

  • 생성자 호출 시점에 딱 1번만 호출되는 것 보장
  • 불변, 필수 의존관계에 사용
    • 대부분의 의존관계 주입은 한번 일어나면 어플리케이션 종료까지 의존 관계 변경할일 없음
    • 오히려 종료까지 불변해야함.
    • 수정자 주입 사용시 누군가 변경 할수 있어서 좋은 설계가 아님. 
  • 생성자가 1개만 있다면 @Autowired없어도 자동 주입
  • 테스트를 해야한다면 테스트때는 수동으로 생성자에 넣어주면 테스트 가능하다. 
  • **final을 사용하면 개발시 생성자 주입 코드를 안썼는지 컴파일 오류로 알 수 있다.

-2. 수정자주입(Setter)

  • 선택, 변경 가능성이 있는 의존관계에 사용
  • 자바빈 프로퍼티 규약의 수정자 메서드 방식을 사용

-3. 필드주입

  • 코드가 간결해서 하지만 외부에서 변경이 불가능해서 테스트하기 힘들다.
  • DI프레임워크 없으면 사용할 수 없다.
  • 사용 지양
    • 애플리케이션의 실제 코드와 관계없는 테스트 코드
    • 스프링 설정 목적으로 하는 @Configuration같은 곳에서만 특별한 용도로 사용

-4.  일반 메서드 주입

  • 한번에 여러필드 주입 받을 수 있다.
  • 스프링 컨테이너가 관리하는 빈이어야 동작.
  • 일반적으로 잘 사용하지 않는다. 

- 5.주입할 스프링 빈 없이도 동작해야 할때 사용

  • @Autowired(required-false)
  • @Nullable
  • Optional<>
@Test
void AutowiredOption(){
		ApplicationContext ac = new AnnotationConfigApplicationContext(TestBea.class);
}

static class TestBean{

	@Autowired(required = false)
    public void seetNoBean1(Member noBean1){
    	System.out.println("noBean1 = " + noBean1);
    }
    
    @Autowired
    public void seetNoBean2(@Nullable Member noBean2){
    	System.out.println("noBean2 = " + noBean2);
    }
    
    
    @Autowired
    public void seetNoBean3(Optional<Member> Member noBean3){
    	System.out.println("noBean3 = " + noBean3);
    }
    
    
}

 

-6. **조회 빈이 2개 이상 문제

  • @Autowired는 타입으로 조회한다. 그래서 같은 타입이 2개 이상 등록될때 문제 발생. (NoUniqueBean 예외)
  • 해결법
    • 첫번쨰 타입매칭후 만약에 중복되면 필드 명, 파라미터 명으로 재시도. 
    • @Qualifier 파라미터 주입 시 추가(@Component에도 등록해 둬야함.)
      • Annotation으로 만들어서 컴파일 체크하여 사용할 수 있다.
    • @Primary붙은 것이 우선권.

-7. 조회한 빈이 모두 필요할때 (할인권 선택등) 

  • 스프링으로 간단하게 전략패턴 구현가능.
@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);
    
    int rateDiscountPrice = discountService.discount(member, 20000, "rateDiscount");
    assertThat(rateDiscountPrice).isEqualTo(2000); 
}

static 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;
        System.out.println("policyMap = " + policyMap);
        System.out.println("policies = " + policies); 
   }
   
   public int discount(Member member, int price, String discountCode){
	DiscountPolicy discountPolicy = policyMap.get(discountCode);
	return discountPolicy.discount(member, price);
   }
}

 

7. 롬복(Lombok)

  • 주의
    • 플러그인 롬복 설치해야함.
    • preference에서 annotation Processor에서 Enable annotation processing켜줘야 함.
  • 기본: @Getter, @Setter, @ToString, @EqualsAndHashCode
  • @RequiredArgsConstructors: final붙은것 생성자 만들어줌 (필수니까)
  • 위의 5개 합친게 @Data
  • @NoArgsConstructor: 기본 생성자
  • @AllArgsConstructor: 모든 생성자

8. 실무에서

  • 스프링이 나온 후에 자동 선호 추세. 
  • 업무로직(비즈니스 로직) 은 숫자가 매우 많고 어느정도 유사한 패턴이 있으며 문제 파악도 명확하게 되어서 자동 기능 사용 권장
  • 기술 지원 로직은 광범위해서 파악 어려울수 있어 가급적 수동 빈 등록으로 명확하게 들어내는 것이 좋다. 
  • 그러나 비즈니스 로직 중에서 다형성을 적극사용할 떄는 수동으로 등록하면 한번에 볼 수 있어 좋다. 
 @Configuration
 public class DiscountPolicyConfig {
     @Bean
     public DiscountPolicy rateDiscountPolicy() {
         return new RateDiscountPolicy();
     }
     @Bean
     public DiscountPolicy fixDiscountPolicy() {
         return new FixDiscountPolicy();
     }
}

 

 

'Spring > 기본' 카테고리의 다른 글

컴포넌트 스캔  (2) 2024.01.17
싱글톤 (웹과 관련하여)  (1) 2024.01.17