본문 바로가기
Kotlin

[깡샘의 코틀린 프로그래밍] 정리 27 - 스프링 IOC

by 들풀민들레 2018. 3. 2.

본 글은 [깡샘의 코틀린 프로그래밍 - 루비페이퍼] 의 내용을 발췌한 것입니다.

좀더 자세한 내용은 책을 통해 확인해 주세요.

 

 

 

27장. 코틀린으로 스프링 프레임워크 개발

 

27.2.1. 스프링 IOC – 의존성 주입

 

IOC는 Inversion of Control의 약어로 스프링 프레임워크의 핵심 개념입니다. 쉽게 이야기하자면 개발자가 만든 클래스를 개발자 코드에서 관리하는 것이 아니라 스프링 프레임워크에서 관리해 주는 개념입니다.

 

개발자가 만든 Bean 클래스(스프링 프레임워크에서 관리하는 클래스를 흔히 Bean이라고 부릅니다)는 원래 개발자가 직접 생성해서 필요한 곳에서 이용해야 하는데, 이렇게 하지 않고 스프링 프레임워크에 등록하여 스프링 프레임워크에 의해 생성되어 필요한 곳에 의존성 주입(DI, Dependency Injection) 으로 이용하는 개념입니다. IOC와 DI에 대한 깊은 이야기를 하자면 많은 지면을 할애해야 합니다. 여기서는 간단하게 설명해서 클래스를 직접 생성하지 않고 스프링 프레임워크에서 전달하는 클래스를 그대로 이용해서 클래스 사이의 결합도를 낮추서 유지 보수성을 높이는 개념 정도로 이해하면 됩니다.

 

코틀린으로 DI에 의한 객체 이용을 구현한다고 해서 기존 자바로 구현했던 것과 크게 차이는 없습니다

 

01 import org.springframework.stereotype.Service

02

03 @Service

04 class IOCTestService {

05 fun sayHello(name: String): String{

06 return "Hello $name"

07 }

08 }

 

위의 소스는 03번 줄의 어노테이션을 제외하고는 개발자가 정의한 클래스입니다. 이 클래스의 객체를 생성해서 다른 곳에서 이용하고자 하는 것이 주목적입니다. 그런데 원래는 이 클래스를 이용하는 곳에서 직접 객체를 생성해서 이용해야 하지만, 이 클래스를 스프링의 Bean으로 등록하여 개발자 코드에 의한 객체 생성 없이 스프링 프레임워크에서 객체를 생성해서 필요한 곳에 주입(DI)하고자 하는 것이 목적입니다.

 

개발자가 만든 클래스를 어떻게 스프링 프레임워크에 등록할까요? 그 역할을 하는 것이 03번 줄의 어노테이션입니다. @Service 어노테이션 이외에 이미 보았던 @Controller, @Repository 등이 모두 스프링 프레임워크에 Bean을 등록하기 위한 어노테이션입니다. 이렇게 어노테이션만 추가해 놓아도 스프링 프레임워크에서 이 클래스를 인지하게 됩니다.

 

이제 이 클래스를 활용하는 쪽의 코드를 보겠습니다.

 

01 @RestController

02 class IOCTestController {

03

04 @Autowired

05 lateinit var service: IOCTestService

06

07 @GetMapping("/ioc")

08 fun hello(@RequestParam(value = "name") name: String) = service.sayHello(name)

09

10 }

 

이전에 살펴보았던 @RestController 어노테이션이 추가된 개발자 Controller 클래스입니다. 클라이언트 요청이 들어올 때 실행되는 클래스이지요. 그런데 이 클래스의 08번 줄에서 IOCTestService 객체를 이용해야 합니다. 이 객체의 sayHello ( ) 함수를 호출하는 것이 주목적인데, 그렇게 하려면 IOCTestService를 생성해서 이용해야 합니다. 하지만 위의 코드에는 IOCTestService 클래스 생성 구문이 없습니다. 결국 개발자 코드에서 객체를 생성하지 않았다는 이야기입니다.

 

이렇게 할 수 있는 이유는 04번 줄의 어노테이션 때문입니다. 05번 줄의 프로퍼티를 선언하면서 @Autowired을 추가하면 프로퍼티의 타입에 해당하는 클래스의 객체를 생성해서 이 프로퍼티에 대입해 줍니다. 개발자가 객체를 생성하지 않아도 스프링 프레임워크가 자동으로 객체를 생성해 이용할 수 있게대입해줍니다. 이 부분이 의존성 주입이라고 부르는 DI입니다.

 

단, 코틀린에서는 프로퍼티를 선언하면서 초기화해야 하는데 이 프로퍼티는 개발자가 초기화하는 게 아니라 스프링 프레임워크가 대입되는 객체로 초기화하므로 lateinit 예약어를 추가해야 합니다.

 

생성자 주입

 

스프링 DI를 이용하면서 위의 소스처럼 클래스 프로퍼티에 @Autowired 어노테이션을 추가해 이용할 수 있습니다. 그런데 DI는 생성자 주입(Constructor Injection)과 Setter 주입도 있습니다. 생성자 주입은 말 그대로 객체를 생성자를 통해 주입받는 것을 의미하며 Setter 주입은 setter 함수의 매개변수를 이용하여 객체를 주입받는 것을 의미합니다. 이 부분을 코틀린에서도 지원합니다.

 

01 @RestController

02 class IOCTestController2 @Autowired constructor(val service: IOCTestService){

03

04 @GetMapping("/ioc2")

05 fun hello(@RequestParam(value = "name") name: String) = service.sayHello(name)

06

07 }

 

위의 소스는 생성자의 매개변수로 객체를 주입받는 생성자 주입을 테스트한 코드입니다. 코틀린은 생성자가 주 생성자와 보조 생성자로 구분됩니다. 보조 생성자는 constructor 예약어로 클래스 { } 안에 추가하지만, 주 생성자는 클래스 선언 영역에 ( )로 추가합니다. 보통 주 생성자는 constructor 예약어를 생략하여 선언하지만 주생성자에 접근 제한자, 어노테이션 등을 함께 선언해야 할 때는 constructor 예약어를 함께 사용하기도 합니다.

 

02번 줄이 주 생성자를 이용해 생성자 주입을 테스트한 부분입니다. 주 생성자 앞에 @Autowired 어노테이션을 추가하면 됩니다. 주 생성자의 매개변수가 val로 선언되어 있으면 생성자의 지역변수가 아니라 클래스의 프로퍼티가 됩니다. 따라서 이렇게 주 생성자를 통해 주입받은s ervice 객체를 05번 줄에서바로 이용할 수 있습니다.

 

물론, 생성자 주입을 보조 생성자에 명시할 수도 있습니다.

 

01 @RestController

02 class IOCTestController3 {

03

04 lateinit var service: IOCTestService

05

06 @Autowired constructor(service: IOCTestService){

07 this.service=service

08 }

09

10 @GetMapping("/ioc3")

11 fun hello(@RequestParam(value = "name") name: String) = service.sayHello(name)

12

13 }

 

클래스 선언 부분이 아닌 클래스 몸체에 constructor 예약어로 추가하는 것이 보조 생성자인데, 06번 줄처럼 생성자 선언에 @Autowired 어노테이션으로 DI를 명시했습니다. 단지, 보조 생성자의 매개변수는 var, val로 선언할 수 없어 그 자체로 클래스의 프로퍼티가 되지 않습니다. 따라서 07번 줄처럼 매개변수로 주입받은 객체를 다시 클래스의 프로퍼티에 대입해 사용합니다. 주 생성자를 이용할 수 있다면 주 생성자를 이용해 객체를 주입받는 게 조금 더 편해 보입니다.

 

Setter 주입

 

Setter 주입이란 클래스의 setter 함수를 이용해 객체를 주입받는 부분입니다. 그런데 자바는 클래스의 변수가 필드라서 setter 함수를 명시적으로 선언하고 그 함수 위에 어노테이션을 추가해 객체를 주입받지만, 코틀린은 필드가 아니라 프로퍼티입니다. 즉, 변수 자체가 set ( ), get ( ) 함수를 내장한다는 개념입니다. 따라서 자바처럼 명시적으로 setter 함수를 추가할 필요는 없습니다. 단지, 프로퍼티의 set ( ) 함수를 통해 객체를 주입해 달라는 식으로 표현만 해주면 됩니다.

 

01 @RestController

02 class IOCTestController4 {

03

04 @set:Autowired lateinit var service: IOCTestService

05

06 @GetMapping("/ioc4")

07 fun hello(@RequestParam(value = "name") name: String) = service.sayHello(name)

08

09 }

 

04번 줄이 Setter 주입을 구현한 부분입니다. 프로퍼티에 @Autowired를 추가하는 것은 같은데 단지 프로퍼티의 set ( ) 함수를 이용한다는 의미에서 @set:Autowired로 추가한 것만 차이가 있습니다.