scala入门_Scala和Scalatra入门–第四部分

scala入门

欢迎来到本系列有关scala和scalatra的教程的最后一部分。 在这一部分中,我们将研究如何使用Akka使用异步调度程序来处理请求,如何使用subcut进行依赖项注入以及最后如何在云中运行完整的API。 在此示例中,我使用了来自JBoss的openshift在JBoss Application Server 7.1上运行API。 现在我们在以前的教程中看到了什么:

本教程中的示例假定您已经完成了前三个教程。 我们不会显示所有细节,而是着重于向现有应用程序中添加新功能(第三部分)。 确切地说,在此示例中,我们将向您显示以下步骤:

  1. 首先,我们将subcut引入应用程序以进行依赖项注入
  2. 接下来,我们将使用Akka的期货使请求异步
  3. 最后,我们将启用CORS,打包应用程序并将其部署到openshift

我们将拥有一个可以在openshift云上调用的API。

让我们从子切入开始, 向应用程序添加依赖项注入

在Java中,有许多依赖项注入框架。 大多数人都听说过Spring和Guice,并且依赖注入甚至具有自己的JSR和规范。 但是在scala中,情况并非如此。 关于scala应用程序是否需要DI框架的讨论很多,因为也可以使用标准Scala语言构造来应用这些概念。 当您开始研究Scala的依赖项注入时,您会很快遇到蛋糕模式(请参阅此处以获取详细说明)。 我不会详细介绍为什么应该使用或不应该使用蛋糕图案,但是对我个人而言,感觉就像引入了太多的残篇和胶合代码,我想简化一些事情。 对于本文,我将使用subcut 。 Subcut是一个非常小巧且易于使用的框架,这使得在Scala中使用DI非常容易且不引人注目。

没有什么比示例更有效。 因此,您需要做什么让subcut管理您的依赖项。 首先,我们当然需要很好地将实现与接口/特征分开。 在第三部分中,我们创建了一组存储库,将它们创建为类变量,可以直接从scalatra路由中使用它们:

// repo stores our items
  val itemRepo = new ItemRepository;
  val bidRepo = new BidRepository;

问题是这将我们的路由直接绑定到实现,这是我们不想要的。 因此,首先让我们通过定义这些存储库的特征来扩展存储库。

trait BidRepo {
 
  def get(bid:Long , user: String ) : Option [Bid]
  def create(bid: Bid): Bid 
  def delete(user: String , bid: Long ) : Option [Bid] 
}
 
trait ItemRepo {
  def get(id: Number ) : Option [Item]
  def delete(id: Number ) : Option [Item]
}
 
trait KeyRepo {
  def validateKey(key: String , app: String , server: String ): Boolean
}

没有什么不寻常的。 我们从实现中使用此特征,如下所示,我们已经完成了。

class BidRepository extends RepositoryBase with BidRepo {
 ...
}

既然我们已经定义了特征,就可以开始使用子切割来管理依赖项了。 为此,我们需要注意以下几点:

  1. 哪种实现方式绑定到什么特征
  2. 哪些课程需要注入资源
  3. 用我们的配置引导“ root”对象

在我们开始之前。 我们首先需要使用subcut依赖项更新build.sbt并添加正确的存储库。

libraryDependencies ++= Seq(
  'com.escalatesoft.subcut' %% 'subcut' % '2.0-SNAPSHOT',
  'org.scalaquery' %% 'scalaquery' % '0.10.0-M1',
  'postgresql' % 'postgresql' % '9.1-901.jdbc4',
  'net.liftweb' %% 'lift-json' % '2.4',
  'org.scalatra' % 'scalatra' % '2.1.0',
  'org.scalatra' % 'scalatra-scalate' % '2.1.0',
  'org.scalatra' % 'scalatra-specs2' % '2.1.0',
  'org.scalatra' % 'scalatra-akka' % '2.1.0',
  'ch.qos.logback' % 'logback-classic' % '1.0.6' % 'runtime',
  'org.eclipse.jetty' % 'jetty-webapp' % '8.1.5.v20120716' % 'container',
  'org.eclipse.jetty' % 'test-jetty-servlet' % '8.1.5.v20120716' % 'test',
  'org.eclipse.jetty.orbit' % 'javax.servlet' % '3.0.0.v201112011016' % 'container;provided;test' artifacts (Artifact('javax.servlet', 'jar', 'jar'))
)
 
resolvers ++= Seq('Scala-Tools Maven2 Snapshots Repository' at 'https://oss.sonatype.org/content/groups/public/',
                  'Typesafe Repository' at 'http://repo.typesafe.com/typesafe/releases/')

这不仅添加了subcut依赖项,还添加了akka一次,我们将在本文中进一步介绍

将实现绑定到特征

子切割中的绑定是在绑定模块中定义的。 因此,通过扩展模块,可以为应用程序创建配置。 例如,您可以定义一个测试配置,一个配置用于质量检查,另一个配置用于生产。

// this defines which components are available for this module
    // for this example we won't have that much to inject. So lets
    // just inject the repositories.
    object ProjectConfiguration extends NewBindingModule(module => {
      import module._   // can now use bind directly
 
      // in our example we only need to bind to singletons, all bindings will
      // return the same instance.
      bind [BidRepo] toSingle new BidRepository
      bind [ItemRepo] toSingle new ItemRepository
 
      // with subcut however, we have many binding option as an example we bind the keyrepo and want a new
      // instance every time the binding is injected. We'll use the toProvider option for this
      bind [KeyRepo] toProvider {new KeyRepository}
    }
 )

无需潜入过深的子切口。 在此代码段中,我们要做的是将实现绑定到特征。 我们对要注入的所有资源执行此操作,因此subcut会在遇到特定接口时知道要创建哪个实现。 如果我们想注入特定特征的不同实现,我们还可以在绑定中添加一个id,因此我们可以唯一地引用它们。

配置需要注入资源的类

现在,我们具有绑定到实现的一组特征,我们可以让subcut注入资源。 为此,我们需要做两件事。 首先,我们需要向HelloScalatraServlet类添加一个隐式val。

class HelloScalatraServlet(implicit val bindingModule: BindingModule) extends ScalatraServlet with Authentication
                                                   with RESTRoutes {
 ....
}

这需要添加到所有希望通过子切割注入资源的类中。 有了此隐式值,子cut可以访问配置并可以使用它来注入依赖项。 我们已经在RESTRoutes特性中定义了路由,因此让我们看一下如何配置此特性以与subcut一起使用:

trait RESTRoutes extends ScalatraBase with Injectable {
 
    // simple logger
  val logger = Logger(classOf[RESTRoutes]);
 
  // This repository is injected based on type. If no type can be found an exception is thrown
  val itemRepo = inject[ItemRepo]
  // This repo is injected optionally. If none is provided a standard one will be created
  val bidRepo = injectOptional[BidRepo] getOrElse {new BidRepository};
 
  ...
}

我们从子切割中添加了Injectable特征,因此我们可以使用inject函数(其中有多个变体)。 在此示例中,使用注入功能注入了itemRepo。 如果找不到合适的实现,则会引发错误消息。 然后使用injectOptional注入bidRepo。 如果未绑定任何内容,则使用默认值。 由于该特性由servlet使用(我们刚刚看到的(带有隐式bindingmodule的特性)),它可以访问绑定配置,并且subcut将注入所需的依赖项。

用我们的配置引导“ root”对象

现在我们需要做的就是告诉我们的根对象(Servlet)它应该使用哪种配置,所有东西都将被连接在一起。 我们从生成的Scalatra侦听器执行此操作,在其中添加以下内容:

...
  override def init(context: ServletContext) {
 
    // reference the project configuation, this is implicatly passed into the 
    // helloScalatraServlet
    implicit val bindingModule = ProjectConfiguration
 
    // Mount one or more servlets, this will inject the projectconfiguration
    context.mount(new HelloScalatraServlet, '/*')
  }
 
  ...

在这里,我们创建bindingModule,该绑定模块隐式传递到HelloScalatraServlet的构造函数中。 就是这样,当您现在启动应用程序时,子切割将确定需要注入哪个依赖项。 就是这样。 如果我们现在启动,则应用程序子剪切将处理依赖项。 如果一切顺利,并且找到了所有依赖项,则应用程序将成功启动。 如果找不到依赖项之一,则会引发如下错误:

15:05:51.112 [main] WARN  o.eclipse.jetty.webapp.WebAppContext - Failed startup of context o.e.j.w.WebAppContext{/,file:/Users/jos/Dev/scalatra/firststeps/hello-scalatra/src/main/webapp/},src/main/webapp
org.scala_tools.subcut.inject.BindingException: No binding for key BindingKey(org.smartjava.scalatra.repository.ItemRepo,None)
 at org.scala_tools.subcut.inject.BindingModule$class.inject(BindingModule.scala:66) ~[subcut_2.9.1-2.0-SNAPSHOT.jar:2.0-SNAPSHOT]

转到列表中的下一项,Akka。

使用Akka添加异步处理

Akka为您提供了完整的Actor框架,可用于创建可扩展的多线程应用程序。 Scalatra开箱即用地支持Akka,因此使其易于使用非常容易。 只需添加正确的特征,将功能与Future函数包装在一起,就可以完成工作。 所有动作都发生在我们定义路线的RESTRoutes特性中。 让我们启用其中两种方法来使用Akka。

trait RESTRoutes extends ScalatraBase with Injectable with AkkaSupport{
 
   ...
 
  /**
   * Handles get based on items id. This operation doesn't have a specific
   * media-type since we're doing a simple GET without content. This operation
   * returns an item of the type application/vnd.smartbid.item+json
   */
  get('/items/:id') {
    // set the result content type
 contentType = 'application/vnd.smartbid.item+json'
 
 // the future can't access params directly, so extract them first
 val id = params('id').toInt;  
 
    Future {
     // convert response to json and return as OK
     itemRepo.get(id) match {
       case Some(x) => Ok(write(x));
       case None =>NotFound ('Item with id ' + id + ' not found');
     }
    }
  }
 
  /**
   * Delete the specified item
   */
  delete('/items/:id') {
   val id = params('id').toInt;
 
    Future {
       itemRepo.delete(id) match {
        case Some(x) => NoContent();
        case None => NotFound ('Item with id ' + id + ' not found');
      }
    }
  }
  ...
}

太多没有看到这里。 我们只是添加了AkkaSupport特性,并用Future函数包装了我们的方法主体。 这将异步运行代码块。 Scalatra将等待直到完成此块,然后返回结果。 这里要注意的一件事是,您无权访问scalatra提供的请求上下文变量。 因此,如果您想设置响应内容类型,则需要在以后进行。 例如,对于访问参数或请求正文也是如此。
现在您需要做的就是设置一个Akka ActorSystem。 最简单的方法是仅使用默认的actor系统。 有关高级选项,请参阅Akka文档。

class HelloScalatraServlet(implicit val bindingModule: BindingModule) extends ScalatraServlet with Authentication
                                                   with AkkaSupport
                                                   with RESTRoutes {
 
  // create a default actor system. This is used from the futures in the web routes
  val system = ActorSystem()
}

现在,当您运行servlet容器时,将使用Akka期货来处理请求。

添加CORS并在云上部署

最后一步,添加CORS 。 使用CORS,您可以打开自己的API,以供其他域使用。 这样可以避免使用JSONP。 在scalatra中使用它非常简单。 只需添加特征CorsSupport,就完成了。 启动应用程序时,您将看到类似以下内容:

15:31:28.505 [main] DEBUG o.s.scalatra.HelloScalatraServlet - Enabled CORS Support with:
allowedOrigins:
 *
allowedMethods:
 GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH
allowedHeaders:
 Cookie, Host, X-Forwarded-For, Accept-Charset, If-Modified-Since, Accept-Language, X-Forwarded-Port, Connection, X-Forwarded-Proto, User-Agent, Referer, Accept-Encoding, X-Requested-With, Authorization, Accept, Content-Type

您可以使用此处说明的一组init参数来微调支持的内容。

现在剩下的就是打包所有内容,并将其部署到openshift 。 如果还没有注册,请在openshift上注册(免费)。 在我的示例中,我使用标准的“ JBoss Application Server 7.1”应用程序,没有任何盒带。

我不想配置postgresql,所以我创建了一个虚拟仓库实现:

class DummyBidRepository extends BidRepo{
 
  val dummy = new Bid(Option (10l),10,10,20,'FL',10l,12345l, List ());
 
  def get(bid: Long , user: String ) : Option [Bid] = {
    Option (dummy);
  }
  def create(bid: Bid): Bid = {
    dummy;
  }
  def delete(user: String , bid: Long ) : Option [Bid] = {
    Option (dummy);
  }
}

并使用subcut注入了这个子库,而不是需要数据库的仓库:

bind [BidRepo] toSingle new DummyBidRepository

有了这个小的更改,我们就可以使用sbt创建war文件。

jos@Joss-MacBook-Pro.local:~/Dev/scalatra/firststeps/hello-scalatra$ sbt package && cp target/scala-2.9.1/hello-scalatra_2.9.1-0.1.0-SNAPSHOT.war ~/dev/git/smartjava/deployments/
[info] Loading project definition from /Users/jos/Dev/scalatra/firststeps/hello-scalatra/project
[info] Set current project to hello-scalatra (in build file:/Users/jos/Dev/scalatra/firststeps/hello-scalatra/)
[info] Compiling 2 Scala sources to /Users/jos/Dev/scalatra/firststeps/hello-scalatra/target/scala-2.9.1/classes...
[info] Packaging /Users/jos/Dev/scalatra/firststeps/hello-scalatra/target/scala-2.9.1/hello-scalatra_2.9.1-0.1.0-SNAPSHOT.war ...
[info] Done packaging.
[success] Total time: 7 s, completed Oct 5, 2012 1:57:12 PM

并使用git将其部署到openshift

jos@Joss-MacBook-Pro.local:~/git/smartjava/deployments$ git add hello-scalatra_2.9.1-0.1.0-SNAPSHOT.war && git commit -m 'update' && git push
[master b1c6eae] update
 1 files changed, 0 insertions(+), 0 deletions(-)
Counting objects: 7, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 11.16 KiB, done.
Total 4 (delta 3), reused 0 (delta 0)
remote: Stopping application...
remote: Done
remote: ~/git/smartjava.git ~/git/smartjava.git
remote: ~/git/smartjava.git
remote: Running .openshift/action_hooks/pre_build
...
remote: Emptying tmp dir: /var/lib/stickshift/3bc81f5b0d7c48ad84442698c9da3ac4/smartjava/jbossas-7/standalone/tmp/work
remote: Running .openshift/action_hooks/deploy
remote: Starting application...
remote: Done
remote: Running .openshift/action_hooks/post_deploy
To ssh://3bc81f5b0d7c48ad84442698c9da3ac4@smartjava-scalatra.rhcloud.com/~/git/smartjava.git/
   a45121a..b1c6eae  master -> master

您可能会看到类似的内容,现在完成了。 或者至少,差不多完成了。 导致访问资源时发生的情况:

嗯..出了点问题。 这是我们感兴趣的消息:

java.lang.IllegalStateException: The servlet or filters that are being used by this request do not support async operation

嗯..显然JBoss AS处理servlet的方式与Jetty有所不同。 我们看到此消息的原因是,根据Servlet 3.0规范,默认情况下,未启用Servlet支持异步操作。 由于我们在路线上使用Akka Futures,因此我们需要这种异步支持。 通常,您可以在web.xml或使用Servlet上的注释来启用此支持。 但是,在我们的情况下,我们的servlet是从侦听器启动的:

override def init(context: ServletContext) {
 
    // reference the project configuation, this is implicatly passed into the 
    // helloScalatraServlet
    implicit val bindingModule = ProjectConfiguration
 
    // Mount one or more servlets, this will inject the projectconfiguration
    context.mount(new HelloScalatraServlet, '/*')
  }

Context.mount是scalatra提供的一种便捷方法,用于注册servlet。 但是,这不会启用异步支持。 如果我们自己注册servlet,则可以启用此异步支持。 因此,用以下函数替换先前的函数:

override def init(context: ServletContext) {
 
    // reference the project configuation, this is implicatly passed into the 
    // helloScalatraServlet
    implicit val bindingModule = ProjectConfiguration
 
    val servlet = new HelloScalatraServlet
 
    val reg = context.addServlet(servlet.getClass.getName,servlet);
    reg.addMapping('/*');
    reg.setAsyncSupported(true);
  }

现在,我们明确启用异步支持。 再次创建一个程序包,并使用git将Web应用程序部署到openshift

sbt package 
git add hello-scalatra_2.9.1-0.1.0-SNAPSHOT.war && git commit -m 'update' && git push

现在,您已经在openshift上运行了API的有效版本!

祝您编程愉快,别忘了分享!

参考: 教程:scala和scalatra入门– Smart Java博客上JCG合作伙伴 Jos Dirksen的第四部分


翻译自: https://www.javacodegeeks.com/2012/10/getting-started-with-scala-and-scalatra.html

scala入门

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值