Sunday Study

IoC/DI

제어의 역전 (Inversion of Control)

IoC(Inversion Of Control): 제어권의 역전, 프로그램의 제어 흐름이 뒤바뀌는 것.

들어가기에 앞서

UserDao 클래스는 사용자의 정보를 DB에 넣고 관리할 수 있는 클래스이다. (토비의 스프링 1장 초난감 DAO 참조)

예시를 들어 설명해보자.(다음 예시는 Spring과 별개의 프로그램으로 생각한다.) 다음과 같이 UserDao는 main내에서 생성되고 ConnectionMaker 생성자에 userDao를 넘겨준다. 통상적으로 다음과 같이 userDao 인스턴스를 직접 만들어서 또다른 생성자에 넘겨주는건 흔하고 일반적이다.

fun main() {
	val userDao = UserDao()
	val connectionMaker = ConnectionMaker(userDao)
	connectionMaker.disconnect()
}

제어의 역전은 이런 흐름을 뒤집는다. 제어의 역전에서는 오브젝트가 자신이 사용할 오브젝트를 스스로 선택하지 않는다. 다음 코드 예시를 보자

fun main(connectionMaker: ConnectionMaker) {
	connectionMaker.disconnect()
}

첫번째와 다르게 main에서 connectionMaker에 대해 직접 제어 하지 않는다. connectionMaker 에 대한 인스턴스 생성은 main 에서 이뤄지지 않고 main을 호출하는 또 다른 어딘가에서 이를 관리한다.

이것을 제어의 역전 이라고 한다.

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

스프링에서의 IoC

@Configuration 을 이용한 어플리케이션 컨텍스트 설정 예시

스프링에서 Swagger 를 사용한다고 생각해보자. 이를 위해서 SwaggerConfiguration 이라는 클래스를 만들어서 다음과 같이 설정할 수 있다.

@Configuration
class SwaggerConfiguration {
		@Bean
    fun getOpenAPI(): OpenAPI {
        return OpenAPI()
            .servers(listOf(Server().apply { url = "/" }))
            .info(
                Info().title("api")
                    .description("API")
                    .version("v0.1.0")
            )
            .components(
                Components()
                    .addSecuritySchemes(
                        securityRequirementName,
                        securityScheme
                    )
            )
            .externalDocs(
                ExternalDocumentation()
                    .description("API")
            )
    }
}

용어 정리

오브젝트 스코프

@Configuration 어노테이션으로 애플리케이션 컨텍스트에 등록하는 경우, 가져오는 오브젝트(Bean)은 동일한 오브젝트를 가져올 것이다. 이는 스프링의 애플리케이션 컨텍스트가 오브젝트를 기본적으로 싱글톤(Singleton)으로 취급한다는 것을 알 수 있다.

주의: 참고로 스프링에서 취급하는 싱글톤 오브젝트는 일반적으로 디자인 패턴에서 구현되는 싱글톤 패턴의 구현방법과는 약간 다르다. (후술)

왜 스프링에서는 오브젝트(Bean)를 싱글톤으로 만들까

싱글톤의 한계

싱글톤 레지스트리

앞서 말했듯이 스프링의 싱글톤은 약간 구현방식이 다른데, 이를 위해 스프링에서 제공되는 것 중 하나가 ‘싱글톤 레지스트리’ 이다. 일반적으로 자바에서 구현하는 싱글톤 패턴과 다르게 싱글톤 레지스트리에서는 public 생성자를 가진다. 따라서 테스트 환경에서도 사용하기가 편하다.

싱글톤과 오브젝트 상태

싱글톤은 멀티스레드 환경이라면 여러 스레드가 동시에 접근하는 것이 가능하다. 그러다 보니 싱글톤 인스턴스를 만들때는 가급적 stateless 하게 만들어야 한다. 즉, 읽고 쓰는 가변상태가 없어야 한다는 것이다.

가변 상태가 있는 예시(Stateful)

class Stateful {
	private var orderNo = 0

	fun order(orderNo: Int) {
		this.orderNo = orderNo // 여기서 문제가 발생한다.
	}
}

가변 상태가 없는 예시(Stateless)

class UserDao {
	private val connectionMaker = ConnectionMaker()

	constructor() {
	 // connectionMaker 를 초기화
	}

	fun getUserDao() {
		// userDAO를 반환
	}
}

일반적인 자바/코틀린에서의 싱글톤 패턴 구현

스프링 부트에서 오브젝트 스코프 (추가)

앞서 설명했듯 기본은 싱글톤 스코프이지만 이외에도 여러 스코프가 존재한다.