Play!도 Dependency Injection이 존재합니다. Spring에서도 많이 사용하듯이 Play!에서도 DI는 매우 유용한 기능입니다 Dependency Injection은 여러가지 방법이 있지만, Play 2.4에서 많이 사용하는 방식은 Google의 DI Library 인 Guice를 사용하는 방법입니다. 물론 여러가지 포스팅이 많지만, 매우 기초적인(?) 한글 포스팅이 없어서 이렇게 씁니다.
Guice를 이용한 DI는 Cats에서 많이 보는 implicit 방식을 이용한 API, Impli 방식과 유사한 방식을 사용하고 있습니다.(물론 이 방식이 범용적인 방식인지는… 저의 지식의 깊이가 낮아 알 수는 없습니다.)
1. 먼저 필요한 특질을 정의한 trait를 정의 합니다.
2. 그 후, 그것에 대한 세세한 기능을 작성한 Impli Class를 정의합니다.
3. AbstractModule을 상속 받는 Module Class의 configure 함수에 bind할 trait와 Impli Class를 정의합니다. 혹은 @Provider라는 Annotation 단, Instance 함수를 정의합니다.
이제 예제를 들며 살펴봅시다. 먼저 bind함수를 이용한 Dependency Injection를 봅시다.
Bind를 이용한 Dependency Injection
만약 SayHello라는 Trait가 있다고 합시다. 이 Trait는 sayHello라는 함수를 갖고 있고, 그것은 String을 반환하는 함수라는 특질을 갖고 있습니다. 이 trait는 다음과 같이 정의가 되어있습니다.
package models trait SayHello { def sayHello : String }
그리고 SayHello의 동작 방식을 정의한 Impli Class가 다음과 같이 정의가 되어 있다고 합시다.
class SayHelloImpli extends SayHello { override def sayHello: String = "Hello I`m Ktz" }
그리고, 이 SayHello를 사용할 Play Controller가 있다고 합시다. 이것은 SayHello를 받아서 사용합니다. 그렇다면 SayHello를 사용할 SayHelloController는 SayHello와 SayHelloImpli와 다음과 같이 연결되어 있다고 볼 수 있습니다.
이제, SayHello와 SayHelloImpli를 잇는 사실을 Play에 알려줘야 합니다. 그러므로, Guice Library의 AbstractModule을 상속받은, 그 "연결 고리”를 정의하는 Modules Class를 만들어 줍니다.
class SayHelloModule extends AbstractModule{ override def configure(): Unit = bind(classOf[SayHello]) to classOf[SayHelloImpli] }
이 Module을 만든 후, 그것을 Play에 알려주기 위하여 application.conf에 다음과 같이 정의해줍니다.
play.modules.enabled += "models.SayHelloModule"
이후, SayHelloController에 @Inject Annotation을 선언하여 Dependency Injection를 합니다.
package controllers import javax.inject.Inject import models.SayHello import play.api.mvc._ class SayHelloController @Inject()(sayhello : SayHello) extends Controller{ def hello = Action{ Ok(sayhello.sayHello) } }
마지막으로, routes에 Controller Action을 추가합니다.
GET /hello controllers.SayHelloController.hello
이후, http://localhost:9000/hello에 접속하면 SayHelloController의 hello def를 호출하여, "Hello I`m Ktz"라는 글자를 볼 수 있습니다.
@Provide를 이용한 Dependency Injection
이미 정의된 class나, trait에서 Singleton 방식의 Instance를 생성하는 경우가 있습니다. 이 경우, Singleton에서 Factory 방식으로 사용을 할 수 있는데, Guice Library를 이용한다면, @Provide Annotation을 사용하여 Instance를 생성할 수 있습니다.
만약, iam이라는 함수를 호출하는, 자신이 누구인지, String값을 반환하는 함수를 가진 Whoami라는 trait가 있다고 합시다.
trait Whoami { def iam : String }
이 경우, trait를 상속 받아, iam이라는 함수를 정의하는 class를 만들 수 있지만, new을 하여, 그냥 Whoami라는 Instance를 생성할 수 있습니다. 아까, SayHello와 SayHelloImpli를 연결하는 함수인 SayHelloModule의 밑에 @Provides Annotation같이 선언한, iamwho라는 함수를 다음과 같이 추가 해봅시다.
class SayHelloModule extends AbstractModule{ override def configure(): Unit = bind(classOf[SayHello]) to classOf[SayHelloImpli] @Provides def iamwho : Whoami = new Whoami { override def iam: String = "ktz" } }
이 함수는 나의 이름인, ‘ktz“라는 이름을 리턴합니다. 이 경우, ImpliClass에 Inject하는 방식과 Controller에 Inject하는 방식, 두가지가 있습니다. 이 두가지 방식을 그림으로 보면 다음과 같은 차이가 있습니다.
먼저, Controller에 Inject하는 방식은 다음과 같습니다.
일단, ktz라는 이름은 iamwho에서 반환 하므로, SayHelloImpli에서 이름을 뺍시다.
class SayHelloImpli extends SayHello { override def sayHello: String = s"Hello I`m " }
그 후, Controller의 Consctructor Parameter에 Whoami 변수를 새로 추가 합니다.
class SayHelloController @Inject()(sayhello : SayHello, whoami: Whoami) extends Controller{ def hello = Action{ Ok(sayhello.sayHello ++ s"${whoami.iam}") } }
이 후, Browser를 다시 Refresh를 해주면… 아까와 같은 화면을 볼 수 있습니다;;;
혹은 ktz는 SayHelloImpli에서 사용할 수 있습니다. 그렇다면 SayHelloImpli에 @Inject Annotation 다음과 같이 추가합니다.
SayHelloImpli에 Injection
class SayHelloImpli @Inject()(whoami: Whoami) extends SayHello { override def sayHello: String = s"Hello I`m ${whoami.iam}" }
이후, Browser를 다시 Refresh 해주면… 다시 아까와 같은 화면을 볼 수 있습니다.
'02. Web > Play Framework' 카테고리의 다른 글
Play Framework Remote Debugging (0) | 2016.09.17 |
---|