Java 在控制器外部渲染scala模板?(第二场)

Java 在控制器外部渲染scala模板?(第二场),java,playframework,playframework-2.2,Java,Playframework,Playframework 2.2,我正在尝试将发送通知电子邮件与导致它们的事件分离。到目前为止,我正在将一个邮件对象(DocumentIssuedMail)从控制器传递给Akka参与者(EmailDispatcher),然后通过play mailer插件的play easymail包装发送邮件。电子邮件正文在传递给参与者后由邮件对象生成,HTML由Scala模板生成 此模板包含通过调用 @routes.SomeController.someAction().absoluteURL() 但是,我在尝试呈现模板时遇到了Runtim

我正在尝试将发送通知电子邮件与导致它们的事件分离。到目前为止,我正在将一个邮件对象(
DocumentIssuedMail
)从控制器传递给Akka参与者(
EmailDispatcher
),然后通过play mailer插件的play easymail包装发送邮件。电子邮件正文在传递给参与者后由邮件对象生成,HTML由Scala模板生成

此模板包含通过调用

@routes.SomeController.someAction().absoluteURL()
但是,我在尝试呈现模板时遇到了RuntimeException

堆栈跟踪如下所示:

java.lang.RuntimeException: There is no HTTP Context available from here.
    at play.mvc.Http$Context.current(Http.java:30)
    at play.mvc.Http$Context$Implicit.ctx(Http.java:196)
    at play.core.j.PlayMagicForJava$.requestHeader(TemplateMagicForJava.scala:56)
    at views.html.email._learner_main$.apply(_learner_main.template.scala:41)
    at views.html.documents.email.new_doc_unregistered$.apply(new_doc_unregistered.template.scala:47)
    at views.html.documents.email.new_doc_unregistered$.render(new_doc_unregistered.template.scala:67)
    at views.html.documents.email.new_doc_unregistered.render(new_doc_unregistered.template.scala)
    at email.DocumentIssuedMail.getUnregisteredMail(DocumentIssuedMail.java:71)
    at email.DocumentIssuedMail.getMail(DocumentIssuedMail.java:67)
    at actors.email.EmailDispatcher.onReceive(EmailDispatcher.java:32)
    at akka.actor.UntypedActor$$anonfun$receive$1.applyOrElse(UntypedActor.scala:167)
    at akka.actor.ActorCell.receiveMessage(ActorCell.scala:498)
    at akka.actor.ActorCell.invoke(ActorCell.scala:456)
    at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:237)
    at akka.dispatch.Mailbox.run(Mailbox.scala:219)
    at akka.dispatch.ForkJoinExecutorConfigurator$AkkaForkJoinTask.exec(AbstractDispatcher.scala:386)
    at scala.concurrent.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
    at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
    at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
    at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)

是否可以在该位置呈现模板,还是需要在原始线程上呈现模板?

此问题的一个可能解决方案是将http请求显式传递给参与者,然后再传递给邮件模板

在模板中,将此请求传递给absoluteURL()方法:

除了DocumentIssuedMail,您还需要将请求传递给参与者。下面是一个简单的DTO

import play.api.mvc.RequestHeader;

public class DocumentIssuedMailWrapper {
    private DocumentIssuedMail documentIssuedMail;
    private RequestHeader requestHeader;

    public DocumentIssuedMailWrapper(DocumentIssuedMail documentIssuedMail, RequestHeader requestHeader) {
        this.documentIssuedMail = documentIssuedMail;
        this.requestHeader = requestHeader;
    }

    public DocumentIssuedMail getDocumentIssuedMail() {
        return documentIssuedMail;
    }

    public RequestHeader getRequestHeader() {
        return requestHeader;
    }
}
参与者将请求作为普通参数从DTO传递到邮件模板

import akka.actor.UntypedActor;
import play.api.templates.Html;
import views.html.mail;

public class EmailDispatcher extends UntypedActor {

    @Override
    public void onReceive(Object message) throws Exception {
        if (message instanceof DocumentIssuedMailWrapper) {
            DocumentIssuedMailWrapper wrapper = (DocumentIssuedMailWrapper) message;
            Html mailTemplate = mail.render(wrapper.getRequestHeader());
            //sending mail
        }
    }

}
在控制器中,您可以通过调用ctx()来获取请求。现在,您只需要使用actor安排一个作业,并使用DTO传递请求

import akka.actor.ActorRef;
import akka.actor.Props;
import play.libs.Akka;
import play.mvc.*;

import scala.concurrent.duration.Duration;

import java.util.concurrent.TimeUnit;

public class Application extends Controller {

    public static Result sendMail() {
        DocumentIssuedMailWrapper wrapper = new DocumentIssuedMailWrapper(new DocumentIssuedMail(), ctx()._requestHeader());

        ActorRef emailDispatcher = Akka.system().actorOf(Props.create(EmailDispatcher.class));
        Akka.system().scheduler().scheduleOnce(Duration.create(0, TimeUnit.MILLISECONDS), emailDispatcher, wrapper, Akka.system().dispatcher(), null);
        return ok("Mail sent");
    }

    public static Result someAction() {
        return ok("Some other action");
    }

}

谢谢,丹尼尔!这实际上与我提出的解决方案非常相似。最后,我们决定在conf文件中定义主机名,而不是使用请求中的主机,因此这一切都没有意义在我当前的项目中,我最终得到了一个类似的解决方案,并将主机名保存在application.conf中。我展示的解决方案可以很好地工作,如果您从一个控制器启动作业,并且可以访问一个请求。如果从全局运行作业,则此作业将不起作用。
import akka.actor.ActorRef;
import akka.actor.Props;
import play.libs.Akka;
import play.mvc.*;

import scala.concurrent.duration.Duration;

import java.util.concurrent.TimeUnit;

public class Application extends Controller {

    public static Result sendMail() {
        DocumentIssuedMailWrapper wrapper = new DocumentIssuedMailWrapper(new DocumentIssuedMail(), ctx()._requestHeader());

        ActorRef emailDispatcher = Akka.system().actorOf(Props.create(EmailDispatcher.class));
        Akka.system().scheduler().scheduleOnce(Duration.create(0, TimeUnit.MILLISECONDS), emailDispatcher, wrapper, Akka.system().dispatcher(), null);
        return ok("Mail sent");
    }

    public static Result someAction() {
        return ok("Some other action");
    }

}