@Configuration에 대해서 파헤쳐 보았다.

 

@Configuration은 싱글톤을 위해서 만들어졌다.

package hello.core;

import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {
    
    //@Bean memberService -> new MemoryMemberRepository() 
    //@Bean orderService -> new MemoryMemberRepository() 
    
    @Bean
    public MemberService memberService(){
        
        return new MemberServiceImpl(memberRepository());
    }

    @Bean
    public MemoryMemberRepository memberRepository() {
        
        return new MemoryMemberRepository();
    }

    @Bean
    public OrderService orderService(){
        
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

    @Bean
    public DiscountPolicy discountPolicy(){
        
        return new RateDiscountPolicy();
    }

}

AppConfig를 살펴보았다.

AppConfig는 순수한 자바코드이다.

memberService()를 호출하면 memberRepository에 의해서 MemoryMemberRepository가 생성된다.

orderService()를 호출하면 memberRepository에 의해서 또 MemoryMemberRepository가 생성된다.

 

엥? 그렇다면 싱글톤이 깨지는 것이 아닌가..?

 

 //Test
    public MemberRepository getMemberRepository() {
        return memberRepository;
    }

OrderServiceImpl , MemberServiceImpl 두 클래스에 이 코드를 추가한다.

 

package hello.core.singleton;

import hello.core.AppConfig;
import hello.core.member.MemberRepository;
import hello.core.member.MemberServiceImpl;
import hello.core.order.OrderServiceImpl;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class ConfigurationSingletonTest {

    @Test
    void configurationTest(){
        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

        MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
        OrderServiceImpl orderService = ac.getBean("orderService", OrderServiceImpl.class);
        MemberRepository memberRepository = ac.getBean("memberRepository", MemberRepository.class);

        MemberRepository memberRepository1 = memberService.getMemberRepository();
        MemberRepository memberRepository2 = orderService.getMemberRepository();


        System.out.println("memberRepository -> memberRepository = " + memberRepository1);
        System.out.println("orderService -> memberRepository " + memberRepository2);
        System.out.println("memberRepository = " + memberRepository);


        Assertions.assertThat(memberService.getMemberRepository()).isSameAs(memberRepository);
        Assertions.assertThat(orderService.getMemberRepository()).isSameAs(memberRepository);

    }
}

 

3개의 객체를 비교하는 코드를 작성해보았다.

놀랍게도 3객체 모두 같은 객체인 것을 알 수있었다.

 

memberRepository -> memberRepository = hello.core.member.MemoryMemberRepository@1d730606
orderService -> memberRepository hello.core.member.MemoryMemberRepository@1d730606
memberRepository = hello.core.member.MemoryMemberRepository@1d730606

 

콘솔에 찍힌 출력이다.

 

AppConfig의 자바 코드를 보면 분명히 각각 3번 new MemoryMemberRepository를 호출하여 다른 인스턴스가 생성되야

하는데 실험을 통해서 확인해보았다.

 

package hello.core;

import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    //@Bean memberService -> new MemoryMemberRepository()
    //@Bean orderService -> new MemoryMemberRepository()

    @Bean
    public MemberService memberService(){
        System.out.println("call AppConfig.memberService");
        return new MemberServiceImpl(memberRepository());
    }

    @Bean
    public MemoryMemberRepository memberRepository() {
        System.out.println("call AppConfig.memberRepository");
        return new MemoryMemberRepository();
    }

    @Bean
    public OrderService orderService(){
        System.out.println("call AppConfig.orderService");
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

    @Bean
    public DiscountPolicy discountPolicy(){
        return new RateDiscountPolicy();
    }

}

AppConfig클래스이다.

@Bean 안에

출력문을 통해 무엇이 호출되는지 알아보았다.

머리로 생각해보면

 

call AppConfig.memberService

call AppConfig.memberRepository

call AppConfig.memberRepository

call AppConfig.orderServiice

call AppConfig.memberRepository 

 

이렇게 총 5개가 되야 할것같은데 돌려보니

 

각각의 빈들 하나씩만 호출되어 3개가 되는 것을 알 수 있었다.

memberRepository는 3번호출되어야하는데 1번만 호출되었다.

 

스프링이 어떠한 방법을 쓰는지는 모르겠지만

싱글톤을 보장해준다는 것을 알 수 있었다..

싱글톤 방식의 주의점

 

싱글톤 패턴이든, 스프링 같은 싱글톤 컨테이너를 사용하든, 객체 인스턴스를 하나만 생성해서

공유하는 싱글톤 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에, 싱글톤 객체는

상태를 유지하게 설계 하면 안된다.

 

무상태로 설계해야한다.

- 특정 클라이언트에 의존적인 필드가 있으면 안된다.

- 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다.

- 가급적 읽기만 가능해야 한다.

- 필드 대신에 자바에서 공유되지 않는 지역변수, 파라미터, ThreadLocal 등을 사용해야한다. 

 

package hello.core.singleton;

public class StatefulService {

    private int price; //상태를 유지하는 변수

    public void order(String name, int price){
        System.out.println("name = " + name + " price = " + price);
        this.price = price;
    }

    public int getPrice(){
        return price;
    }
}

StatefulService 클래스를 하나 생성하고 이름과, 가격을 출력하는 함수와

가격을 반환하는 함수를 만들었다.

 

package hello.core.singleton;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;

import static org.junit.jupiter.api.Assertions.*;

class StatefulServiceTest {

    @Test
    void statefulServiceSingleton(){
        ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
        StatefulService statefulService1 = ac.getBean(StatefulService.class);
        StatefulService statefulService2 = ac.getBean(StatefulService.class);
        
        
        statefulService1.order("userA",10000);
        statefulService2.order("userB",20000);
        // 2명의 고객의 요청
        
        int price = statefulService1.getPrice();
        System.out.println("price = " + price);

        Assertions.assertThat(statefulService1.getPrice()).isEqualTo(20000);
        
    }

    static class TestConfig{

        @Bean
        public StatefulService statefulService(){
            return new StatefulService();
        }
    }
}

위의 클래스를 테스트 해보았다.

TestConfig 설정정보를 만들고

빈을 정의해준 뒤

스프링 컨테이너를 생성하여 

두명의 고객의 정보를 넣어주고 이때 가격을 확인한다.

A유저는 10000원을 결제해야하는데 20000원이 결제되는 상황이 발생할 코드이다.

 

StatefulService의 price 필드는 공유되는 필드인데, 특정 클라이언트가 값을 변경한다.

공유필드는 항상 조심해야한다. 스프링 빈은 무상태로 설계되어야한다.

 

package hello.core.singleton;

public class StatefulService {
    
    public int order(String name, int price){
        System.out.println("name = " + name + " price = " + price);
        return price;
    }
}

이 코드처럼 공유되는 price필드를 지역변수로 사용하는 방법이 있다.

 

package hello.core.singleton;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;

import static org.junit.jupiter.api.Assertions.*;

class StatefulServiceTest {

    @Test
    void statefulServiceSingleton(){
        ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
        StatefulService statefulService1 = ac.getBean(StatefulService.class);
        StatefulService statefulService2 = ac.getBean(StatefulService.class);
        
        
       int userAPrice = statefulService1.order("userA",10000);
       int userBPrice = statefulService2.order("userB",20000);
        // 2명의 고객의 요청
        
        System.out.println("price = " + userAPrice);

    }

    static class TestConfig{

        @Bean
        public StatefulService statefulService(){
            return new StatefulService();
        }
    }
}

이렇게 코드를 짜면 

 

기대했던 값을 확인 할 수 있었다.

 

실무에서 이런 경우들을 종종 볼 수 있다고한다.. 

그때는 잡기가 어렵고 해결하기 어려운 문제들이 터진다고한다!

싱글톤 컨테이너에 대하여 알아보았다.!

 

스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하면서, 객체 인스턴스를 싱글톤으로

관리한다.

지금까지 써온 스프링 빈이 바로 싱글톤으로 관리되는 빈이다.

 

싱글톤 컨테이너

 

스프링 컨테이너는 싱글톤 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤으로 관리한다.

- 이전에 공부했던 컨테이너 과정을 보니 컨테이너는 객체를 하나만 생성해서 관리한다.

 

스프링 컨테이너는 싱글톤 컨테이너의 역할을 한다. 이렇게 싱글톤 객체를 생성하고 관리하는 기능을 싱글톤 레지스트리라 한다.

 

스프링 컨테이너의 이런 기능 덕분에 싱글톤 패턴의 모든 단점을 해결하면서 객체를 싱글톤으로 유지할 수 있다.

-싱글톤 패턴을 위한 지저분한 코드가 들어가지 않아도 된다.

-DIP, OCP, 테스트, private 생성자로부터 자유롭게 싱글톤을 사용할 수 있다.

 

    @Test
    @DisplayName("스프링 컨테이너와 싱글톤")
    void springContainer(){
        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

        MemberService memberService1 = ac.getBean("memberService",MemberService.class);
        MemberService memberService2 = ac.getBean("memberService",MemberService.class);

        System.out.println("memberService1 = " + memberService1);
        System.out.println("memberService2 = " + memberService2);

        assertThat(memberService1).isSameAs(memberService2);
    }

 실제로 같은 객체를 사용하는지 확인하기 위한 테스트 코드를 작성해보았다.

 

프린트 문을 통해서 나온 값이 같다는것을 확인할 수 있었다.

싱글톤 패턴과 관련있는 코드들이 하나도 없어도 싱글톤 패턴이 적용되있는 것을 알 수 있었다.

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/lecture/55363?speed=0.75&tab=note&mm=close

스프링 컨테이너 덕분에 고객의 요청이 올때마다 객체를 생성하는 것이 아니라 이미 만들어진 객체를

공유하여 효율적으로 재사용 할 수 있다.

 

 

앞서 있었던 문제를 싱글톤 패턴을 이용하여 해결하는 시간을 가졌다.

 

싱글톤 패턴?

 

클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴이다.

그래스 객체 인스턴스를 2개 이상 생성하지 못하도록 막아야한다.

-private 생성자를 사용하여 외부에서 임의로 new 키워드를 사용하지 못하도록 막아야한다.

 

package hello.core.singleton;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class SingletonService {

    private static final SingletonService instance = new SingletonService();
    
    public static SingletonService getInstance(){
        return instance;
    }
    
    private SingletonService(){
        
    }
    
    public void logic(){
        System.out.println("싱글톤 객체 로직 호출");
    }
   
}

1. static 영역에 객체 instance를 미리 하나 생성하여 올려둔다.

2. 이 객체 인스턴스가 필요하면 오직 getInstance() 메서드를 통해서면 조회할 수 있다. 이 메서드를 호출하면

항상 같은 인스턴스를 반환한다.

3. 딱 1개의 객체 인스턴스만 존재해야 하므로, 생성자를 private으로 막아서 혹시라도 외부에서 new 키워드로 객체 인스턴스가

생성되는 것을 막는다.

    @Test
    @DisplayName("싱글톤 패턴을 적용한 객체 사용")
    void singletonServiceTest(){
        SingletonService singletonService1 = SingletonService.getInstance();
        SingletonService singletonService2 = SingletonService.getInstance();

        System.out.println("singletonService1 = " + singletonService1);
        System.out.println("singletonService2 = " + singletonService2);

        assertThat(singletonService1).isSameAs(singletonService2);
    }

테스트 코드로 확인해보았다.

isSameAs : 메모리상 같은 객체를 가리키는지 비교 (주소 비교)
isEqualTo : 객체가 같은 값을 가지고 있는지 비교한다.

같은 인스턴스를 참조하는 것을 확인할 수 있었다.

 

그렇다면? AppConfig를

싱글톤으로 바꾸어어서 getInstance로 반환하면 된다고 생각했는데

그렇게 할 필요가 없었다. 

스프링 컨테이너를 사용하면 스프링 컨테이너가

객체를 싱글톤으로 관리해준다.

 

싱글톤 패턴을 적용하면 고객의 요청이 올때마다 객체를 생성하는 것이 아니라,

이미 만들어진 객체를 공유해서 효율적으로 사용 할 수 있다. 하지만 싱글톤 패턴은 

수 많은 문제점들을 가지고 있다.

 

{

싱글톤 패턴을 구현하는 코드 자체가 많이들어간다.

 

의존관계상 클라이언트가 구체 클래스에 의존한다. -> DIP위반

 

클라이언트가 구체 클래스에 의존해서  OCP원칙을 위반할 가능성이 높다.

 

테스트 하기가 어렵다

 

내부 속성을 변경하거나 초기화하기 어렵다.

 

private생성자로 자식 클래스를 만들기 어렵다.

 

결론적으로 유연성이 떨어진다.

 

안티패턴으로 불리기도한다.

}

이번에는 싱글톤 컨테이너를 알아보았다.

싱글톤 패턴은 정보처리기사에서 디자인 패턴

부분에서 봤던 내용이다.

 

웹 애플리케이션과 싱글톤

 

스프링은 태생이 기업용 온라인 서비스 기술을 지원하기 위해 탄생했다.

대부분의 스프링 애플리케이션은 웹 애플리케이션이다.

웹 애플리케이션은 보통 여러 고객이 동시에 요청을 한다.

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/lecture/55361?speed=0.75&tab=note&mm=close

클라이언트 A,B,C 가 동시에 요청을 한다면?

DI 컨테이너는 새로운 객체를 3개를 생성하여 반환한다.

이때 웹 어플리케이션은 계속 요청을 받아야만 하기때문에

객체를 계속 만들어야하는 문제가 발생한다.

 

package hello.core.singleton;

import hello.core.AppConfig;
import hello.core.member.MemberService;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class SingletonTest {

    @Test
    @DisplayName("스프링 없는 순수한 DI 컨테이너")
    void pureContainer(){
        AppConfig appConfig = new AppConfig();

        // 1.조회 : 호출할때 마다 객체를 생성
        MemberService memberService1 = appConfig.memberService();

        // 2.조회 : 호출할때 마다 객체를 생성
        MemberService memberService2 = appConfig.memberService();

        //참조값이 다른 것을 확인
        System.out.println("memberService1 = " + memberService1);
        System.out.println("memberService2 = " + memberService2);
        

    }
}

참조값을 확인하기 위한 테스트 코드를 작성해보았다.

 

@뒤에 부분이 다르다 

즉 두 객체는 다른것이다. 

웹 어플리케이션의 특징은 고객의 요청이 어마어마하다는 것이다.

이런식으로 객체를 계속 생성하는것은 비효율 적이다.

 

내가 만들었던 스프링 없는 순수한 DI 컨테이너인 AppConfig는 요청할때마다 객체를 생성한다.

고객 트래픽이 초당 100이 나오면 초당 100개 객체가 생성되고 소멸된다. -> 메모리 낭비

해결방안은 해당 객체가 딱 1개만 생성되고, 공유하도록 설계하면 된다. -> 싱글톤 패턴

public Iterator<T> iterator(){
	
	return new MyIterator();
}

private class MyIterator implements Iterator<T>{
	
	private Node<T> nextnode;
	
	public MyIterator() {
		nextnode = head;
	}
	
	public boolean hasNext() {
		return (nextnode != null);
	}
	public T next() {
		if(nextnode == null) {
			throw new NoSuchElementException();
		}
		
		T val = nextnode.data;
		nextnode = nextnode.next;
		return val;
	}
	public void remove() {
		
	}
}

기존 MySingleLinkedList 클래스에 이너클래스로 

MyIterator를 만들어주었다.

 

Node객체를 멤버로 가지고

 

생성자에서 head값을 넣어주고

 

다음 노드가 있는지 확인하는 메서드와

 

다임 노드의 데이터를 반환하는 메서드를 구현하였다.

스프링 빈 설정 메타 정보에 대하여 알아보았다.

스프링은 어떻게 이런 다양한 설정 형식을 지원하는 것일까?

 

그 중심에는 BeanDefinition 이라는 추상화가 있다.

 

쉽게 이야기해서 역할과 구현을 개념적으로 나눈 것이다.

-XML을 읽어서 BeanDefinition을 만들면 된다.

-자바코드를 읽어서 BeanDefinition을 만들면 된다.

-스프링 컨테이너는 자바 코드인지, XML인지 몰라도 된다. 오직 BeanDefinition만 알면 된다.

 

BeanDefinition을 빈 설정 메타정보라 한다.

-@Bean , <bean> 당 각각 하나씩 메타정보가 생긴다.

 

스프링 컨테이너는 이 메타정보를 기반으로 스프링 빈을 생성한다.

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/lecture/55359?speed=0.75&tab=note&mm=close
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/lecture/55359?speed=0.75&tab=note&mm=close

코드레벨에서 들어가보면

AnnotationConfigApplicationContext는 AnnotatedBeanDefinitionReader 라는 것을 사용하여

자바 코드를 설정정보처럼 읽어버린다. 그렇게 BeanDefinition을 생성한다.

 

package hello.core;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class BeanDefinitionTest {

    AnnotationConfigApplicationContext ac =new AnnotationConfigApplicationContext(AppConfig.class);

    @Test
    @DisplayName("빈 설정 메타정보 확인")
    void findApplicationBean(){
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);

            if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION){
                System.out.println("beanDefinitionName = " + beanDefinitionName + " beanDefinition = "+beanDefinition);
            }
        }
    }
}

테스트 코드 작성을 해보았다.

 

콘솔에서 각종 정보들을 볼 수 있었다.

 

BeanDefinition을 직접 생성하여 스프링 컨테이너에 등록할 수 도 있다. 

스프링이 다양한 형태의 설정 정보를 BeanDefinition으로 추상화해서 사용하는 것이다.

 

 

스프링 컨테이너는 다양한 형식의 설정 정보를 받아드릴 수 있게 설계되었다.

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/lecture/55358?speed=0.75&tab=note

 

애노테이션 기반 자바 코드 설정 사용

 

AnnotationConfig는 현재 사용해본 방식이다.

new AnnotationConfigApplicationContext(AppConfig.class);

이 클래스를 사용하면서 자바 코드로된 설정 정보를 넘기면 되낟.

 

XML 설정 사용

 

최근에는 스프링 부트를 많이 사용하면서 XML기반의 설정은 잘 사용하지 않지만

아직 남아있는 곳들도 있다. XML은 컴파일 없이 설정 정보를 변경할 수 있는 장점도 있으므로

알아보았다.

GenericXMLApplicationContext를 사용하면서 xml컨택스트를 넘기면 된다.

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="memberService" class="hello.core.member.MemberServiceImpl">
        <constructor-arg name="memberRepository" ref="memberRepository" />
    </bean>
    <bean id="memberRepository" class="hello.core.member.MemoryMemberRepository" />
    <bean id="orderService" class="hello.core.order.OrderServiceImpl">
        <constructor-arg name="memberRepository" ref="memberRepository" />
        <constructor-arg name="discountPolicy" ref="discountPolicy" />
    </bean>
    <bean id="discountPolicy" class="hello.core.discount.RateDiscountPolicy" />
</beans>

xml 기반의 설정 정보는 AppConfig.java와 거의 동일하다!

 

package hello.core.xml;

import hello.core.member.MemberService;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

import static org.assertj.core.api.Assertions.*;

public class XmlAppContext {

    @Test
    void xmlAppContext(){
        ApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");
        MemberService memberService = ac.getBean("memberService", MemberService.class);
        assertThat(memberService).isInstanceOf(MemberService.class) ;
    }
}

테스트 코드로 실행시켜 보았더니 Singleton Bean이라는 문구와 함께

객체 이름이 반환되었다.

BeanFactory와 ApplicationContext에 대해서 알아보았다.

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/lecture/55357?speed=0.75&tab=note

BeanFactory 

 

스프링 컨테이너의 최상위 인터페이스이다.

스프링 빈을 조회하는 역할을 담당한다.

getBean()을 제공한다.

지금까지 사용했던 대부분의 기능은 BeanFactory가 제공하는 기능이다.

 

ApplicationContext

 

BeanFactory 기능을 모두 상속받아서 제공한다.

빈을 관리하고 검색하는 기능을  BeanFactory가 제공해준다. 그렇다면 둘의 차이는 무엇일까?

애플리케이션을 개발할 때는 빈을 관리하고 조회하는 기능은 물론, 수많은 부가 기능이 필요하다.

 

어떤 부과기능을 제공해주는가?

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/lecture/55357?speed=0.75&tab=note

인터페이스 분리원칙에 의해 여러가지 인터페이스를 상속 받고있다.

 

메시지소스를 활용한 국제화 기능

- 예를 들어 한국에서 들어오면 한국어로, 영어권에서 들어오면 영어로 출력

 

환경변수

- 로컬, 개발, 운영등을 구분해서 처리

 

애플리케이션 이벤트

- 이벤트를 발행하고 구독하는 모델을 편리하게 지원

 

편리한 리소스 조회

- 파일, 클래스패스, 외부 등에서 리소스를 편리하게 조회

스프링 빈 조회 시에

부모타입으로 조회하면, 자식 타입도 함께 조회한다.

그래서 모든 자바 객체의 최고 부모인 Object 타입으로 조회하면, 모든 스프링 빈을 조회한다.

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/lecture/55356?speed=0.75&tab=note

 

package hello.core.beanfind;

import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class ApplicationContextExtendsFindTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);

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

    @Test
    @DisplayName("부모 타입으로 조회시 자식이 둘 이상 있으면 빈 이름을 지정하면 된다..")
    void findBeanByParentTypeBeanName(){
        DiscountPolicy rateDiscountPolicy = ac.getBean("rateDiscountPolicy", DiscountPolicy.class);
        assertThat(rateDiscountPolicy).isInstanceOf(RateDiscountPolicy.class);

    }

    @Test
    @DisplayName("특정 하위 타입으로 조회")
    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)) ;
        }
    }

    @Test
    @DisplayName("부모타입으로 모두 조회하기 - Object")
    void findAllBeanByObjectType(){
        Map<String, Object> beansOfType = ac.getBeansOfType(Object.class);
        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();
        }
    }

}

 

 

개발하거나 설계를 할때 

역할과 구현을 쪼개기로 했었다

그것을 위해 Bean의 반환타입을 부모 타입으로 명시한다.

+ Recent posts