토비의 스프링3.1을 읽고 - 1장 정리

제어의 역전(Inversion of Control)

성격이 다른 책임이나 관심사는 분리하자

현재 분리될 기능은 UserDao와 ConnectionMaker구현 클래스의 오브젝트를 만드는 것과

그렇게 만들어진 두개의 오브젝트가 연결돼서 사용될 수 있도록 관계를 맺어준다.

분리시킬 기능을 담당할 클래스(객체의 생성 방법을 결정하고 만들어진 오브젝트를 돌려주는 것)를 생성한다.

=> 오브젝트를 생성하는 쪽과 생성된 오브젝트를 사용하는 쪽의 역할과 책임을 분리한다.
=> `팩토리`
// UserDao의 생성 책임을 맡은 팩토리 클래스
public class DaoFactory{
    public UserDao userDao(){
    // 팩토리의 메소드는 UserDao 타입의 오브젝트를 어떻게 만들고 어떻게 준비시킬지 결정한다.
    ConnectionMaker connectionMaker = new DConnectionMaker();
    UserDao userDao = new UserDao(connectionMaker);
    return userDao;
    }
}

=> UserDaoTest는 DaoFactory(팩토리)로 부터 UserDao 오브젝트를 받아서 사용하기만 하면 된다.

(어떻게 만들어지는지 어떻게 호출하는지는 Test가 알 필요가 없다. ==> Test에만 집중!)

만약 DaoFactory에 UserDao가 아닌 다른 DAO의 생성 기능을 넣으면 어떻게 될까?

ex) AccountDAO, MessageDAO

//DAO 생성 메서드의 추가로 인해 발생하는 중복
public class DaoFactory{

    public UserDao userDao(){
        return new UserDao(new DConnectionMaker());
    }

	public AccountDAO accountDAO(){
		return new AccountDAO(new DConnectionMaker());
	}
	
	public MessageDAO messageDAO(){
		return new MessageDAO(new DConnectionMaker());
	}

}

문제점 : DAO가 많아지면 ConnectionMaker의 구현 클래스를 바꿀때마다 모든 메소드를 수정해야함.

//생성 오브젝트 코드 수정
public class DaoFactory{

    public UserDao userDao(){
        return new UserDao(connectionMaker());
    }

	public AccountDAO accountDAO(){
		return new AccountDAO(connectionMaker());
	}
	
	public MessageDAO messageDAO(){
		return new MessageDAO(connectionMaker());
	}
	
	public ConnectionMaker connectionMaker(){
		return new DConnectionMaker();
	}
}

제어의 역전이란?

오브젝트가 자신이 사용할 오브젝트를 스스로 선택하지 않는다.

당연히 생성하지도 않는다.

또 자신이 어떻게 만들어지고 어디서 사용되는지 알 수 없다.

모든 제어의 권한을 자신이 아닌 다른 대상에게 위임한다.

프레임워크와 라이브러리의 차이점은

라이브러리를 사용하는 애플리케이션 코드는 애플리케이션 흐름을 직접 제어한다.

반면에 프레임워크는 거꾸로 애플리케이션 코드가 프레임워크에 의해 사용된다.(프레임워크는 제어의 역전 개념이 적용된 대표적인 기술이다.)

우리가 만든 UserDao와 DaoFactory에도 제어의 역전이 있다.

원래는 ConnectionMaker의 구현 클래스를 결정하는 것은 UserDao에 있었는데 지금은 DaoFactory에 있다.

ConnectionMaker 구현 클래스를 만들고 사용할지를 결정할 권한을 DaoFactory에 넘겼으니 UserDao는 수동적인 존재가 됐다.

유연하고 확장 가능한 구조로 만들기 위해 DaoFactory를 도입했던 과정이 IoC적용 과정이였다.


11/25

public class test {

	int iv; // 인스턴스 변수
	static int cv; // 클래스 변수
	
	void method() {
		int lv; // 지역 변수
	}
}

인스턴스 변수 : 각각의 인스턴스마다 고유의 값을 가져야할 때는 인스턴스 변수로 선언

클래스 변수 :  클래스의 모든 인스턴스들이 공통적인 값을 가져야할  클래스 변수로 선언 
클래스가 로딩될  생성 -> 메모리에  한번만 올라간다.

지역 변수 : 메서드가 실행될  메모리를 할당 받으며 메서드가 끝나면 소멸되어 사용할  없다.

왜 스프링은 싱글톤으로 빈을 만들까?

스프링이 처음 설계됐던 대규모 엔터프라이즈 서버환경은 서버 하나당 최대로 초당 수십에서 수백 번씩

브라우저나 다른 시스템에서 요청을 받아 처리해야 하는 환경이였다.

매번 클라이언트에서 요청이 올때마다 로직을 담당하고 있는 오브젝트를 새로 생성한다고 하면

요청 한번에 5개의 오브젝트가 만들어지면 초당 500개의 요청이 들어오면 초당 2500개의 오브젝트가 생성된다..

이러한 부하가 걸리면 서버가 감담하기 힘들다.

따라서 서블릿 클래스 당 하나의 오브젝트만 만들어두고, 사용자의 요청을 담당하는 여러 스레드에서

하나의 오브젝트를 공유해 동시에 사용한다.

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);

        Assertions.assertThat(memberService1).isNotSameAs(memberService2);
    }
}

싱글톤 패턴이란 JVM에서 클래스의 객체가 단 하나만 존재하게 하는 디자인 패턴이다.

가장 원초적인 방법은 클래스 내부에서 private static final 키워드로 객체를 만들면

외부에서는 해당 클래스의 객체를 새로 생성할 수 없으므로 싱글톤 패턴 조건을 만족하게 된다.

public class SingletonService {

    private static final SingletonService instance = new SingletonService();

    public static SingletonService getInstance() {
        return instance;
    }

    private SingletonService() {
        
    }
}

// SingletonService 클래스 객체는 getInstance 메서드를 통해서만 접근가능.


@Test
void singletonServiceTest(){
SingletonService singletonService1 = SingletonService.getInstance();
SingletonService singletonService2 = SingletonService.getInstance();

    System.out.println("singletonService1 = " + singletonService1);
    System.out.println("singletonService2 = " + singletonService2);
    
    Assertions.assertThat(singletonService1).isSameAs(singletonService2);
}

// 동일한 객체가 반환된다.

여기까지는 순수 Java 코드로 싱글톤 패턴을 적용하는 방법이었는데 

실제로 사용하기에는 불편하고 실질적인 문제점들이 존재한다.

싱글톤 패턴을 구현하기 위한 코드가 늘어남.

내부 속성을 변경, 초기화하기가 어렵다.

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

유연성이 떨어진다.

스프링 컨테이너

스프링에서는 사용자가 이렇게 일일이 구현하지 않고 싱글톤 패턴의 문제점들도 보완해주면서

싱글톤 패턴으로 클래스의 인스턴스를 사용하게 해 주는데 이것을 싱글톤 컨테이너라고 한다.

스프링 컨테이너는 싱글톤 컨테이너의 역할을 하며 싱글톤 객체를 생성, 관리하는 주체를 싱글톤 레지스트리라고 부른다.

스프링 컨테이너의 기능으로 인해 순수 Java에서 싱글톤 패턴을 구현하기 위한 코드를 사용하지 않아도 된다.

@Test
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);

    Assertions.assertThat(memberService1).isSameAs(memberService2);

}