Scala/Play framework

Play Framework 2.5. Singleton과 Inject

partner_jun 2017. 4. 13. 13:32

Play Framework는 2.0 버전부터 @Singleton과 @Inject 어노테이션을 기반으로 한 DI가 추가되었다. Spring과 다르게 Play Framework에서는 DI가 강제적인 요소가 아니다. 하지만 싱글톤 패턴은 객체간의 결합성을 떨어뜨리고 테스트를 용이하게 하기 때문에 대부분의 경우 유용하게 사용할 수 있다.



1. 기본적인 사용

@Singleton 어노테이션을 이용해 간단하게 싱글톤 클래스를 설정한다.

import javax.inject.{Named, Singleton}
@Singleton
@Named("s1") // Spring의 Qualifier와 같은 역할
class Something{
val message = "hello"
}


@Inject 어노테이션을 이용해 객체를 주입한다.

import javax.inject.{Inject, Named, Singleton}
@Singleton
class SimpleController @Inject()
(@Named("s1") s: Something) extends Controller{

def index = Action { implicit request =>
Ok(s.message)
}

}

@Inject()(객체, 객체) 혹은 @Inject()(객체)(객체) 두 가지 형식 모두 사용할 수 있다.



또, 클래스가 아닌 Trait 형태로 객체를 주입할 수도 있다.

import com.google.inject.ImplementedBy

@ImplementedBy(classOf[Something])
trait SomethingTrait {
def say: String = "Hello"
}
@Singleton
class Something extends SomethingTrait{
}
@Singleton
class SimpleController @Inject()
(s: SomethingTrait) extends Controller{
def index = Action { implicit request =>
Ok(s.say)
}
}


Something 클래스가 아닌 또 다른 클래스 'Something2'가 SomethingTrait을 상속받고 있고, Something2 클래스를 주입받고 싶다면 모듈 설정이 필요하다.




2. Module을 이용한 세부 설정

기본 어노테이션만으로 객체를 생성할 수 없거나 특별한 설정이 필요한 경우 AbstractModule을 상속받아 만들어진 모듈을 추가하여 세부 사항을 설정할 수 있다.


먼저 AbstractModule을 상속받은 모듈 클래스를 만든다.

import com.google.inject.AbstractModule

class SomethingModule extends AbstractModule{
override def configure(): Unit = {
}
}


conf/application.conf 파일에 만든 모듈을 추가한다.

play.modules.enabled += models.test.SomethingModule
이제 모듈 클래스의 configure 메소드에 설정해 원하는 형태로 싱글톤 객체를 만들 수 있다.



1) 싱글톤 객체 생성

@Singleton 어노테이션이 없더라도 원하는 형태로 싱글톤 객체를 만들 수 있다.

import com.google.inject.AbstractModule
import com.google.inject.name.Names

class SomethingModule extends AbstractModule{
override def configure(): Unit = {

bind(classOf[SomethingTrait])
.annotatedWith(Names.named("s1")) // 생략 가능
.to(classOf[Something]) // 생략 가능

}
}

위 모듈은 @Named("s1") 어노테이션이 붙은 Something 클래스의 객체를 SomethingTrait으로 바인드한다.

bind point(bind 메소드의 파라미터)가 Trait이 아니고, 바인드할 클래스와 같다면 to 메소드를 생략해야 한다.



2) 직접 만든 객체를 싱글톤으로

객체를 생성할 때 파라미터가 필요한 경우나 특별한 이유로 직접 객체를 만들어야 할 때도 Module을 설정한다.

class Something2(message: String) {
def getMessage: String = message
}
class SomethingModule extends AbstractModule{
override def configure(): Unit = {
bind(classOf[Something2])
.toInstance(new Something2("World"))
}
}

toInstance의 파라미터로 직접 만든 객체를 입력한다.


위 모듈은 new Something2("World")로 만들어진 객체를 Something2 클래스로 바인드한다. 다른 컴포넌트에서 Something2 클래스의 객체를 주입하면 new Something2("World")로 만들어진 객체가 주입된다.



3) Eager Singleton

Play Framework에서는 싱글톤 객체가 lazy하게 생성된다. 컴포넌트에 객체를 주입해 사용할 때 비로소 객체가 생성된다는 것이다. 싱글톤 객체가 기본적으로 eager하게 생성되는 Spring과는 대조적이다. 

import com.google.inject.AbstractModule

class SomethingModule extends AbstractModule{
override def configure(): Unit = {
bind(classOf[Something]).asEagerSingleton()
}
}

models.test.SomethingModule.scala


프로젝트를 시작하면 곧바로 Something 객체가 만들어진다. Something 객체에 메시지를 출력하는 생성자를 추가하면 직접 확인할 수 있다.



이 외에도 싱글톤 객체의 라이프사이클에 특별한 동작을 추가하는 방법은 공식 문서에 소개되어 있다.