내부적으로 플레이프레임워크는 상향식으로 비동기적이다. 플레이는 매 요청을 비동기적이며 논블럭 방식으로 다룬다.
기본 설정이 비동기식 컨트롤러로 바뀌었는데, 다른 말로 하면 어플리켕션 코드가 컨트롤러에서 블럭되는것을 피해야 한다는 말이다. 즉 JDBC 콜, 스트리밍 API, HTTP 요청 및 오랜 시간이 걸리는 계산 같은 연산에 관련된 코드들 말이다.
블럭되는 컨트롤러에 의해 처리를 하기 위해 동시에 처리되는 요청들을 더 많이 수행하기 위해서는 기본 실행되는 쓰레드의 숫자를 마구 늘릴 수 도 있겠지만 컨트롤러를 비동기식으로 접근하는것이 확장성에 있어서 더 나으며 부하에 대한 시스템의 반응에도 더 유리하다.
(역주: 이런 건 많은 수의 클라이언트 처리에 대해 멀티쓰레딩으로 처리했던 톰캣 보다 node 나 vert.x 가 훨씬 반응성이 좋다는게 입증해준다. 하나의 쓰레드로 좀 더 효율적인 i/o 를 하면 여러개의 쓰레드로는 엄청나나 효율을 가져다 줄 수 있으니..)
논블록 액션 만들기
만약 우리가 아직 결과를 얻지 못했는데 바로 액션에 대한 결과를 처리하려면 어떻게 해야하나? 해답은 퓨쳐 이다. 리턴 받을 때 까지 기다리는게 아니라 즉시 Future[Result] 를 리턴 받아서 다른 클라이언트를 대응하는 일을 할 수 있게 된다. Result 에 실제 리턴값이 담기면 그 때 처리 할 것이다.
웹 클라이언트는 응답을 기다리는 동안 블럭 될 것이긴 하지만 그 클라이언트에 대한 처리를 위해 서버 자체가 블럭 되진 않는 다는 것이다. 즉 서버는 다른 클라이언트들을 처리하는데 사용 될 것이며 약속이 이행되면 바로 그 때 그 결과를 돌려 줄 것이다.
어떻게 Future[Result]를 다룰까
실제 값을 우리에게 줄 퓨처와 그것을 통해 결과를 얻는 퓨처 :
import play.api.libs.concurrent.Execution.Implicits.defaultContext
val futurePIValue:Future[Double]= computePIAsynchronously()
val futureResult:Future[Result]= futurePIValue.map { pi =>Ok("PI value computed: "+ pi)}
플레이 비동기 API 콜 모두는 퓨처를 줄 것이다. 이건 니가 외부로 웹서비스를 요청 하거나 (play.api.libs.WS API) Akka 로 비동기 업무를 수행 할 때 도 사용된 다는 뜻이다. 즉 수행이 끝 날때까지 쓰레드가 놀고 있느게 아니라 퓨처를 받은 후에 즉시 퓨처에 실제 값이 들어 올때까지 다른 일을 하게 된다는 야그..
여기 비동기 블럭을 실행하고 결과를 얻는 Future 예제가 있다.
import play.api.libs.concurrent.Execution.Implicits.defaultContext
val futureInt:Future[Int]= scala.concurrent.Future{
intensiveComputation()}
참고 : 퓨처로 실행되는 스레드 코드를 이해하는 것이 중요합니다. 위의 두 코드 블록에서 기본 실행 컨텍스트를 가져 오고 있습니다. 이것은 콜백을 허용하는 퓨처 API의 모든 메소드로 전달되는 암시 적 매개 변수입니다. 실행 컨텍스트는 종종 스레드 풀과 동일하지만 필수적이지는 않습니다.
퓨처상에서 래핑하여 동기식 IO를 마술처럼 비동기로 전환 할 수는 없습니다. 응용 프로그램의 구조를 변경하여 작업을 차단하지 못하는 경우, 어떤 시점에서 작업을 실행해야하며 해당 스레드가 차단됩니다. 따라서 Future에 작업을 포함하는 것 외에도 예상되는 동시성을 처리하기에 충분한 스레드로 구성된 별도의 실행 컨텍스트에서 실행되도록 구성해야합니다. 자세한 내용은 플레이 스레드 풀 이해를 참조하십시오.
액터를 사용하여 작업을 차단하는 것도 도움이 될 수 있습니다. 액터는 타임 아웃 및 오류 처리, 실행 컨텍스트 블로킹 설정 및 서비스와 관련된 모든 상태 관리를위한 명확한 모델을 제공합니다. 또한 액터는 동시 캐시 및 데이터베이스 요청을 처리하고 백엔드 서버 클러스터에서 원격 실행을 허용하는 ScatterGatherFirstCompletedRouter와 같은 패턴을 제공합니다. 그러나 액터는 필요에 따라 과도하게 동작 될 수 도 있습니다.
퓨쳐 되돌려주기
지금까지 액션을 만들기 위해 Action.apply 빌더 메소드를 사용 했지만 비동기 결과를 보내기 위해Action.async 빌더도 사용된다.
import play.api.libs.concurrent.Execution.Implicits.defaultContext
def index =Action.async {
val futureInt = scala.concurrent.Future{ intensiveComputation()}
futureInt.map(i =>Ok("Got result: "+ i))}
액션은 기본적으로 비동기적이다
플레이 액션은 기본적으로 비동기적이다. 예를 들어 아래 컨트롤러 코드 처럼 말이다. { Ok(...) } 이 부분은 컨트롤러의 메소드 바디가 아니다. 익명 함수이며 Action 객체의 apply 메소드에 전달되며 Action 타입의 객체를 만든다. 내부적으로 익명 함수가 호출 될 것이며 그것의 결과는 퓨처로 감싸질 것이다. 그럼 Action.async 는 왜 만든거지?
val echo =Action{ request =>Ok("Got request ["+ request +"]")}
Note:Action.apply 과 Action.async 양쪽 모두 Action objects 를 만들며 내부적으로 동일하게 다루어 진다. .async 빌더는 그냥 퓨처를 돌려주는 API 기반의 액션을 간단히 만들기 위한 장치일 뿐이다. 비동기코드를 쓰는것을 좀 더 쉽게 만들어 준다는 것일 뿐.
타임아웃 다루기
종종 타임아웃을 적절히 다루게 되는데 웹브라우저가 너무 오랫동안 블럭되면 사용자들이 짜증날테니깐...프라미스에 타임아웃을 설정해서 그 때 까지 결과가 도착 안하면 님이 만든 예러 메세지를 그냥 돌려주게 하자. 아래는 InternalServerError 를 돌려준다.
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import scala.concurrent.duration._
def index =Action.async {
val futureInt = scala.concurrent.Future{ intensiveComputation()}
val timeoutFuture = play.api.libs.concurrent.Promise.timeout("Oops",1.second)Future.firstCompletedOf(Seq(futureInt, timeoutFuture)).map {case i:Int=>Ok("Got result: "+ i)case t:String=>InternalServerError(t)}}
WebSockets 는 웹 브라우저에서 양방향 통신을 가능케 하는 프로토콜 기반으로 사용 될 수 있습니다. (역주: 서버쪽에서 웹브라우저쪽으로도 메세지를 보낸다는 뜻이죠. 기존에는 브라우저에서 서버쪽으로 요청하는 폴링을 주로 사용 했었음) 클라이언트는 메세지를 보낼 수 있으며 서버는 언제라도 메세지를 받을 수 있습니다. 물론 그들 사이에 WebSocket 연결이 액티브 상태일 동안 말이죠.
현재 HTML5 구현이 된 웹 브라우저들은 대부분 자바스크립트 웹소켓 API 를 통한 WebSockets를 지원 하고 있습니다. 그러나 웹소켓이 오직 웹 브라우저에서만 그 의미를 갖는 것은 아닙니다. 많은 웹소켓 클라이언트 라이브러리들을 활용 할 수 있으며 예를들어 서버가 다른 서버와 통신하기 위해서도 사용 할 수 있습니다. 네이티브 모바일 앱과도 말이죠. WebSockets 을 여기서 사용하는것은 플레이 서버가 사용하는 기존 TCP 포트를 재사용할 수 있는 이득을 가져다 줍니다.
액터는 분리 된 메시지를 처리하기위한 더 나은 추상화이지만, iteratees 는 스트림 처리 에있어 더 좋은 추상화입니다. WebSocket 요청을 처리하려면 Action 대신 WebSocket을 사용합니다.
import play.api.mvc._
import play.api.libs.iteratee._
import play.api.libs.concurrent.Execution.Implicits.defaultContext
def socket =WebSocket.using[String]{ request =>// Log events to the console
val in=Iteratee.foreach[String](println).map { _ =>
println("Disconnected")}// Send a single 'Hello!' message
val out=Enumerator("Hello!")(in,out)}
WebSocket은 요청 헤더 (WebSocket 연결을 시작하는 HTTP 요청)에 액세스 할 수 있으므로 표준 헤더와 세션 데이터를 검색 할 수 있습니다. 그러나 요청 본문이나 HTTP 응답에는 액세스 할 수 없습니다. 이 방법으로 WebSocket을 구성 할 때 우리는 in과 out 채널을 모두 반환해야합니다.
- in 채널은 각 메시지에 대해 통지 될 Iteratee [A, Unit] (여기서 A는 메시지 유형 - 여기서 우리는 String을 사용함)이며 소켓이 클라이언트 측에서 닫힐 때 EOF를 수신합니다.
- out 채널은 웹 클라이언트에 보낼 메시지를 생성하는 열거 자 [A]입니다. EOF를 전송하여 서버 측의 연결을 닫을 수 있습니다.
이 예제에서는 각 메시지를 콘솔에 출력하는 간단한 iteratee를 생성합니다. 메시지를 보내려면 간단한 더미 enumerator 를 만들어 하나의 Hello! 메시지를 전송하세요.
Hello!를 전송 한 직후 입력 데이터를 버리고 소켓을 닫는 다른 예제를 작성해 보겠습니다.
import play.api.mvc._
import play.api.libs.iteratee._
def socket =WebSocket.using[String]{ request =>// Just ignore the input
val in=Iteratee.ignore[String]// Send a single 'Hello!' message and close
val out=Enumerator("Hello!").andThen(Enumerator.eof)(in,out)}
다음은 입력 데이터가 표준 출력에 기록되고 Concurrent.broadcast를 사용하여 클라이언트에 브로드 캐스팅되는 다른 예입니다.
import play.api.mvc._
import play.api.libs.iteratee._
import play.api.libs.concurrent.Execution.Implicits.defaultContext
def socket =WebSocket.using[String]{ request =>// Concurrent.broadcast returns (Enumerator, Concurrent.Channel)
val (out, channel)=Concurrent.broadcast[String]// log the message to stdout and send response back to client
val in=Iteratee.foreach[String]{
msg =>
println(msg)// the Enumerator returned by Concurrent.broadcast subscribes to the channel and will// receive the pushed messages
channel push("I received your message: "+ msg)}(in,out)}
사물인터넷 서비스를 생각해보자. 브라우저나 스마트폰을 통해 웹서비스에 명령을 전달하여 전등이 꺼지는 서비스이다. 웹서비스는 명령을 전달 받아서 외부 미들웨어에 전달해야할 것이다. 이때 외부 미들웨어는 아카리모트로 되어 있다고 하자. Play2 는 내부에 아카시스템이 있는데 이 걸 사용할 순 없고 또 다른 하나의 아카리모트 시스템을 만들어서 외부의 아카리모트와 통신하도록 하는 전략을 세워본다.
설정
먼저 아카 시스템을 설정한다. 또하나의 conf 파일을 만들어서 아래와 같이 remote provider 와 접속 대상의 패스를 설정한다.
서비스 객체를 만들고 플레이가 죽을때 원격시스템도 종료시키기 위해서 라이프싸이클 설정을 해줬다. 지금은 tell 패턴을 사용했지만 ask 패턴을 사용한다면 리턴값을 Future 로 받을 수 있을 것이다.
서비스
val config = ConfigFactory.load("remoteProvider") val remoteSystem = ActorSystem("seiws", config.getConfig("Provider")) val runner = remoteSystem.actorOf(Props[Runner], "runner")
case ActorIdentity(`path`, None) => Logger.info(s"Remote actor not available: $path") case ReceiveTimeout => Logger.info(s"ReceiveTimeout"); sendIdentifyRequest() }
def active(actor: ActorRef): Receive = { case msg : String => Logger.info("send a msg to remote actor ") remoteActor ! msg case "Shutdown" => remoteActor ! Shutdown case _ => Logger.info(" receive a invalid msg ") }
설정파일로 부터 원격액터 경로를 가지고서 액터 셀렉션을 통해 액터 참조를 구해서 통신한다. 초기에 원격노드가 살아있는지 체킹 (예제에서는 3초) 하는 과정을 거치고 있다. 원격노드가 살아 있다면 identifying 에서 active 로 상태변경을 하고 메세지 처리를 한다.
때때로 플레이 어플리케이션 안에서 HTTP 서비스를 호출하고 싶을때가 있다.(역주: 마이크로 서비스 패턴에서 주로 사용함) 플레이는 이것을 위해 WS library 를 지원하는데 비동기 HTTP 콜을 할 수 있도록 도와준다. (역주: 시간이 걸릴 듯한 업무에 대한 처리를 맡기는데 사용..즉 푸쉬알람같은거 보내도록 메세지 보내놓고 바로 리턴하기 위함.)
WS API 에는 중요한 2가지가 있는데 리퀘스트를 만드는것과 응답을 처리하는것이다. 먼저 GET POST HTTP 리퀘스트에 대해서 논의할 것이고 WS 부터오는 응답을 어떻게 처리할 것인지 살펴볼 것이다. 마지막으로 사용예에 대해서 살펴보자.
@ In Play 2.0, Play delegated all requests to go through an actor. It heavily depended on Akka's future API and other parts.
@ In Play 2.1, with the move of Akka's future API into Scala 2.10, Play started depending less directly on Akka. It gets all it's execution contexts from Akka, and provides integration with Akka, but that's about the extent of it.
@ In Play 2.3, we're adding new features to aid Akka integration, particularly around WebSockets.
@ In Play 2.4, Play will be ported to the new akka-http (formerly known as spray), at which point, Play will be as built on Akka as you can get.
Akka 는 확장성 높고 병행성있는 어플리케이션을 만들기 위해 더 나은 플랫폼을 제공하고 추상층을 높힌 액터모델을 사용하고 있다. 실패나 예외에 대해 대응하기위해서 ‘고장나게 냅둬라’ 모델을 채택하고있는데 , 절대 멈추지 않고 스스로 치유될 수 있는 어플리케이션을 만듬으로써 텔레콤 산업에서 큰 성공을 거두었다. 액터는 또한 위치투명성을 제공하여 손쉬운 분산프로그래밍, 클러스터링에 적합하다.
아카는 액터 시스템이라는 컨테이너에서 사용된다. 액터 시스템은 그것이 포함하고 있는 리소스들을 관리한다. 플레이 어플리케이션은 특별한 액터 시스템을 정의하여 사용한다. 이 액터 시스템은 어플리케이션 라이프 사이클을 관리하고 어플리케이션이 재시작되었을때 자동적으로 다시 시작하게 해준다.
액터 작성하기
일단 액터하나를 간단히 만들어 보자. "Hello" 에 이름을 붙여서 다시 돌려주는 액터이다.
import akka.actor._
object HelloActor { def props = Props[HelloActor]
case class SayHello(name: String) }
class HelloActor extends Actor { import HelloActor._
def receive = { case SayHello(name: String) => sender() ! "Hello, " + name } }
이 액터는 다음과 같은 아카 관례를 따른다:
- 메세지를 보내고 받을 수 있으며 , 그 프로토콜 (SayHello ) 은 동반 객체에 정의되있다.
- Props 메소드를 동반 객체에 가지고 있다. 액터를 Props 팩토리 메소드를 이용하여 생성한다.
@Singleton class Application @Inject() (system: ActorSystem) extends Controller {
val helloActor = system.actorOf(HelloActor.props, "hello-actor")
//... }
actorOf 는 새로운 액터를 생성하기 위해 사용된다. 싱글턴으로 선언한것을 눈여겨 보시라. 우린 액터와 그 레퍼런스들을 저장할 것 이기 때문에 필요한 부분이다. 만약 그렇지 않다면 컨트롤러가 만들어질때마다 새로운 액터가 만들어질 거란 의미이기 때문이다. 동일한 시스템에 동일한 액터를 가질 수 없다.
액터에게 요청하기
가장 기본적인 일은 액터에게 메세지를 전송하는 일이다. 액터에게 메세지를 보내면 반응이 없을 것인데 기본적으로 "보내고 잊기" 방식이기 때문이다. 보통 tell 패턴으로 알려져있다.
웹어플리케이션에서 tell 패턴 말고 다른 전력이 필요할때가 많이 있다. HTTP 는 요청하고 반응하는 프로토콜인데 이 경우에 ask 패턴을 사용할 가능성이 클 것이다. ask 패턴은 Future 를 리턴하는데 아래 예를 살펴보자.
먼저 ask 패턴은 임포트 되야하고, 이것은 ?연산자를 액터에서 제공한다. (tell 은 ! 이다) 즉 helloActor 에게 SayHello(name) 메세지를 보내는것이다. 보낸 후에 바로 Future[Any] 를 받는다. 그 후에 mapTo 메소드로 기대하는 타입으로 매핑을 하고 , 최종적으로 돌려 받은 메세지를 Ok(message) 로 돌려준다. 타임아웃도 스코프내에 필요한데 타임아웃내에 반응이 오지 않으면 타임아웃 에러를 내보내면 무시한다.
액터 의존성 주입
원한다면 Guice 를 가지고 액터와 액터 설정을 바인드 할 수도 있다. 플레이 설정을 활용하는 액터를 원한다면 이렇게 하면 된다.
플레이는 액터바인딩을 제공하는데 도움을 주는 몇몇 헬퍼를 제공한다. 이것들은 액터 스스로이 DI 되게 하며, 다른 컴포넌트들 안으로 주입되는 액터에 대한 액터 참조를 허용한다. 이런 헬퍼들을 사용하는 액터를 바인드 하기위해 다음 문서에서 나타내는 모듈을 만든다. dependency injection documentation, 그리고 AkkaGuiceSupport 트레잇과 믹스하고 bindActor 메소드를 액터와 바인드 하기위해 사용한다:
key 파라미터가 @Assisted 으로 선언된것을 확인하자. 이것은 수동으로 제공되었다는것을 말한다.
우리는 Factory 트레잇을 정의했다. 이것은 key 를 가지고 액터를 리턴한다. 우린 이것을 구현하지 않을 것인데 Guice 가 우리를 위해 구현 할 것이다. 키 파라미터를 전달하는것 뿐 만 아니라 설정 디펜던시를 위치시키는것 그리고 저것을 주입하는일 등의 구현을 제공한다. 트레잇은 그저 액터를 리턴한다. 이 액터를 테스팅 할때 어떤 액터라도 리턴 할 수 있는 팩토리를 주입 할 수 있을 것이다.예를들어 우리에게 가짜 자식 액터를 주입할 수 있게 할 것이다.
libraryDependencies++=Seq(// ... other libs"com.typesafe.akka"%%"akka-remote"%"2.3.4")
Akka 설정 조정
For API
Play API 를 위해서 conf/application.conf 에 있는 디폴트 Akka 세팅을 다음과 같이 변경 한다.
akka{actor{provider="akka.remote.RemoteActorRefProvider"# offer the provider}remote{enabled-transports=["akka.remote.netty.tcp"]# enable protocolnetty.tcp{hostname="127.0.0.1"# your hostport=2553# port}}}
만약 설정 파일에 아카 오브젝트가 없다면, 설정파일에 그냥 추가하라.
For Actor
위에서 했던 것과 비슷한 설정을src/main/resources/application.conf에 하라.
yourSystem{# the name your actor system is going to useakka{# other thing is just the same as that in APIloglevel="DEBUG"loggers=["akka.event.slf4j.Slf4jLogger"]actor{provider="akka.remote.RemoteActorRefProvider"default-dispatcher{}}remote{enabled-transports=["akka.remote.netty.tcp"]netty.tcp{hostname="127.0.0.1"port=2552# Note if you are running API and Actor program in localhost, make sure they are not using the same port}}}}
그리고나서 코드에 이 세팅을 적용하라.
// ... some codes for launch the programvalsystem=ActorSystem("CoolSystem", config.getConfig("yourSystem"))
Note 위의 provider 를 해당 아카 버전으로 주의 있게 바꾸고 올바른 프로바이더를 선택하라.
publicstaticF.Promise<Result>askYourActorSomething(finalStringinfo){StringactorPath=actorHelper.getPath();// get akka path of your worker, this will not show in my exampleActorSelectionactor=Akka.system().actorSelection(actorPath);returnplay.libs.F.Promise.wrap(ask(actor,newMessageToActor(info),5000)).map(// use ask pattern so that we can get sync response from actor; wrap into PromisenewF.Function<Object,Result>(){// the callback when actor sends back responsepublicResultapply(Objectresp){returnok((String)resp);}});}
여기서 주의깊게 봐야할 포인트는 만약 ask 패턴을 사용한다면 결과를 Promise 로 감싸야한다는점.
Actor
액터 코드:
classWorkerextendsActor{overridedefreceive={caseMessageToActor(info)=>// get message from APIsender!"worked!"// response to API}}
런치 코드
// fetch configsvalremoteConfig=ConfigFactory.load.config.getConfig("yourSystem").getConfig("akka").getConfig("remote").getConfig("netty.tcp")valactorHost=remoteConfig.getString("hostname")valactorPort=remoteConfig.getInt("port")valworkerName="worker"valactorPath="akka.tcp://"+"yourSystem"+"@"+actorHost+":"+actorPort+"/user/"+workerNameprintln(actorPath)// here you know what your actor's path is, well, just for show, don't do this sort of thing your code.valsystem=ActorSystem("CoolSystem",config.getConfig("yourSystem"))valactor=system.actorOf((newWorker()),name=workerName)
이제 만약 컨트롤러 askYourActorSomething 가 호출되면, 경로에 해당하는 당신의 액터에 메세지를 보낼 것이다. 그리고 나서 액터는 이 메세지를 받고 API 컨트롤러에게 다시 문자를 돌려 줄 것이다. 마지막으로 API 는 "worked!" 를 리턴 할 것 이다.
한 가지 더 ..
제품화된 플레이 어플리케이션에서 원격 액터를 사용할 예정이라면 , 특별히 분산 환경이라면 , 좀 더 해야할 것들이 생긴다..
방화벽
API 와 액터 프로그램이 서로 접근하지 못하게 할 것이다.
만약 EC2 를 사용한다면 security groups. 를 세팅해서 해결하라. 서로 다른 그룹의 인바운드에 있는지 확인해야한다.
의존성 주입은 컴포넌트들끼리 서로 독립적이게 하기 위한 방법이다. 서로를 정적으로 포함하기보다는 동적으로 서로에게 주입된다. 플레이는 JSR 330. 기반으로 런타임 의존성 주입을 지원한다. 의존해야하는 특정 컴포넌트를 발견하지 못하면 실행할때까지는 에러를 발견하지 못할것인데 플레이는 컴파일타임 DI 를 또한 지원한다. 플레이에서는 디폴트 의존성 주입 기능으로 Guice 사용한다. 하지만 뭐 다른것을 사용해도 좋다.
디펜던시 선언
컨트롤러나 컴포넌트를 가지고 있을때 다른 컴포넌트를 요구할 수 있는데 이때 @Inject 어노데이션을 사용한다. @Inject 는 필드나 생성자에서 사용될 수 있지만 생성자에서 사용하길 추천한다. 예를 보자:
디폴트로 플레이는 정적라우터를 생성할것이고 모든 액션들이 정적 메소드라고 보면 된다. 설정을 변경해서 injected routes generator 로 변경할 수 있으며 각각 디펜던시로써 라우트 된다. 즉 당신의 컨트롤러들을 스스로 의존성 주입되게 할 수 있다는 뜻이다. (역주: 디폴트로 컨트롤라가 object 였을것이다. 이거 사용하면 class 로 변경가능해짐.)
우리는 injected routes generator 쓰기를 추천한다. 이것을 쓰기위해서 설정을 해야하는데
해당 컴포넌트가 필요 할때 마다 새 인스턴스를 만든다 : 컴포넌트가 한번이상 사용될때 디폴트로 여러번 생성된다. 단지 한번만 생성되게 하고 싶으면 singleton.주석을 사용하라.
필요해 질때, 게으른 초기화로 만들어진다. If a component is never used by another component, then it won’t be created at all. This is usually what you want. For most components there’s no point creating them until they’re needed. However, in some cases you want components to be started up straight away or even if they’re not used by another component. For example, you might want to send a message to a remote system or warm up a cache when the application starts. You can force a component to be created eagerly by using an eager binding.
인스턴스가 자동으로 청소되지 않는다. beyond normal garbage collection. Components will be garbage collected when they’re no longer referenced, but the framework won’t do anything special to shut down the component, like calling aclose method. However, Play provides a special type of component, called theApplicationLifecycle which lets you register components to shut down when the application stops.
Sometimes you may have a component that holds some state, such as a cache, or a connection to an external resource, or a component might be expensive to create. In these cases it may be important that there is only be one instance of that component. This can be achieved using the @Singleton annotation:
Some components may need to be cleaned up when Play shuts down, for example, to stop thread pools. Play provides an ApplicationLifecycle component that can be used to register hooks to stop your component when Play shuts down:
The ApplicationLifecycle will stop all components in reverse order from when they were created. This means any components that you depend on can still safely be used in your components stop hook, since because you depend on them, they must have been created before your component was, and therefore won’t be stopped until after your component is stopped.
Note: It’s very important to ensure that all components that register a stop hook are singletons. Any non singleton components that register stop hooks could potentially be a source of memory leaks, since a new stop hook will be registered each time the component is created.
It is considered good practice to define an trait for a component, and have other classes depend on that trait, rather than the implementation of the component. By doing that, you can inject different implementations, for example you inject a mock implementation when testing your application.
In this case, the DI system needs to know which implementation should be bound to that trait. The way we recommend that you declare this depends on whether you are writing a Play application as an end user of Play, or if you are writing library that other Play applications will consume.
We recommend that Play applications use whatever mechanism is provided by the DI framework that the application is using. Although Play does provide a binding API, this API is somewhat limited, and will not allow you to take full advantage of the power of the framework you’re using.
Since Play provides support for Guice out of the box, the examples below show how to provide bindings for Guice.
In some more complex situations, you may want to provide more complex bindings, such as when you have multiple implementations of the one trait, which are qualified by@Named annotations. In these cases, you can implement a custom Guice Module:
Sometimes you might want to read the Play Configuration or use a ClassLoaderwhen you configure Guice bindings. You can get access to these objects by adding them to your module’s constructor.
In the example below, the Hello binding for each language is read from a configuration file. This allows new Hello bindings to be added by adding new settings in yourapplication.conf file.
import com.google.inject.AbstractModuleimport com.google.inject.name.Namesimport play.api.{Configuration,Environment}classHelloModule(
environment:Environment,
configuration:Configuration)extendsAbstractModule{def configure()={// Expect configuration like:// hello.en = "myapp.EnglishHello"// hello.de = "myapp.GermanHello"
val helloConfiguration:Configuration=
configuration.getConfig("hello").getOrElse(Configuration.empty)
val languages:Set[String]= helloConfiguration.subKeys
// Iterate through all the languages and bind the// class associated with that language. Use Play's// ClassLoader to load the classes.for(l <- languages){
val bindingClassName:String= helloConfiguration.getString(l).get
val bindingClass:Class[_ <:Hello]=
environment.classLoader.loadClass(bindingClassName).asSubclass(classOf[Hello])
bind(classOf[Hello]).annotatedWith(Names.named(l)).to(bindingClass)}}}
Note: In most cases, if you need to access Configuration when you create a component, you should inject the Configuration object into the component itself or into the component’s Provider. Then you can read the Configuration when you create the component. You usually don’t need to read Configuration when you create the bindings for the component.
In the code above, new EnglishHello and GermanHello objects will be created each time they are used. If you only want to create these objects once, perhaps because they’re expensive to create, then you should use the @Singleton annotation asdescribed above. If you want to create them once and also create them eagerly when the application starts up, rather than lazily when they are needed, then you can Guice’s eager singleton binding.
Eager singletons can be used to start up a service when an application starts. They are often combined with a shutdown hook so that the service can clean up its resources when the application stops.
If you’re implementing a library for Play, then you probably want it to be DI framework agnostic, so that your library will work out of the box regardless of which DI framework is being used in an application. For this reason, Play provides a lightweight binding API for providing bindings in a DI framework agnostic way.
To provide bindings, implement a Module to return a sequence of the bindings that you want to provide. The Module trait also provides a DSL for building bindings:
Play’s runtime dependency injection is bootstrapped by the GuiceApplicationLoaderclass. This class loads all the modules, feeds the modules into Guice, then uses Guice to create the application. If you want to control how Guice initializes the application then you can extend the GuiceApplicationLoader class.
There are several methods you can override, but you’ll usually want to override thebuilder method. This method reads the ApplicationLoader.Context and creates aGuiceApplicationBuilder. Below you can see the standard implementation forbuilder, which you can change in any way you like. You can find out how to use theGuiceApplicationBuilder in the section about testing with Guice.
import play.api.ApplicationLoaderimport play.api.Configurationimport play.api.inject._
import play.api.inject.guice._
classCustomApplicationLoaderextendsGuiceApplicationLoader(){overridedef builder(context:ApplicationLoader.Context):GuiceApplicationBuilder={
val extra =Configuration("a"->1)
initialBuilder
.in(context.environment).loadConfig(extra ++ context.initialConfiguration).overrides(overrides(context): _*)}}
When you override the ApplicationLoader you need to tell Play. Add the following setting to your application.conf:
You’re not limited to using Guice for dependency injection. By overriding theApplicationLoader you can take control of how the application is initialized. Find out more in the next section.