자바코드 기반
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 |