如何从Play Framework 2.5.x构建jar文件

7

我尝试按照文档中描述的方法,使用sbt-assembly从干净的项目构建jar包:

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.11.2") 

import AssemblyKeys._

assemblySettings

mainClass in assembly := Some("play.core.server.ProdServerStart")

fullClasspath in assembly += Attributed.blank(PlayKeys.playPackageAssets.value)

但是它给我很多重复错误。如何从play使用sbt-assembly构建“fat”jar?


你确定那个jar包是最好的选择吗?Sbt-native packager可以快速构建安装包。 - mgosk
@zella,请粘贴一些您的错误日志。 - jacks
1个回答

14

构建Play应用程序为fat jar需要设置几个位。

首先是使用asssembly插件。必须在项目目录中直接放置名为assembly.sbt的文件。确切地说,如果您的项目名称为MyPlayProject,则该文件必须位于"MyPlayProject/project/assembly.sbt"位置,并且应仅包含以下内容。

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.3"

显然,版本可能会变化,但应该可以工作。这将向您的项目添加assembly插件,并且不像其他插件那样将其添加到plugins.sbt文件中。

为了覆盖所有基础知识,还请确保您已经涵盖了标准的sbt项目/构建属性。请注意,对于Play 2.5.x,需要sbt版本13.8或更高版本,详见https://www.playframework.com/documentation/2.5.x/Migration25#sbt-upgrade-to-0.13.11

sbt.version=0.13.11

你遇到问题的可能关键部分是 build.sbt 文件,其中应该包括合并策略。有许多文件在 jar 文件中是标准的(例如 MANIFEST.MF 等),当你将所有 jar 文件合并成一个单独的 fat jar 时,你必须处理这些重复的文件。这是一个基本的例子:

assemblyMergeStrategy in assembly := {
  case r if r.startsWith("reference.conf") => MergeStrategy.concat
  case PathList("META-INF", m) if m.equalsIgnoreCase("MANIFEST.MF") => MergeStrategy.discard
  case x => MergeStrategy.first
}

针对要处理的特定情况,您的里程可能会有所不同,但对于标准的play fat jar来说,这是相当基础的。

上述合并策略的一些基本细节如下:

  • 将所有jar的reference.conf合并到单个文件中以获取fat jar。我忘记了导致这种情况的具体问题,但我认为如果没有这一步,您将无法将Play应用程序作为fat jar运行。我没有证据)
  • 丢弃每个jar的MANIFEST.MF文件。许多在线示例将其显示为“case PathList(“META-INF”,xs @ _ *)=> MergeStrategy.discard”。这通过删除jar的META-INF目录中的任何内容来实现。但是,从Play 2.4开始,依赖注入已经在play中得到了大力推动,如果使用依赖注入,则有一个库依赖于包括用于使用依赖注入的服务文件的net.sf.ehcache。解决方法是仅保留所有其他文件并仅丢弃我所做的MANIFEST.MF文件,或者删除所有文件并不使用任何依赖注入(不建议)
  • 一个通用的catch all情况,保留任何重复文件的第一个并且丢弃其他的。 当您可能有多个常用依赖项使用相同的库时,并且没有理由保留多个副本时很有用。

由于我无法在此处进行评论,因此以下是完整的build.sbt文件示例。

name := """MyPlayProject"""

version := "1.0"

lazy val `root` = (project in file(".")).enablePlugins(PlayScala)

scalaVersion := "2.11.8"

// Set JS Engine to use
JsEngineKeys.engineType := JsEngineKeys.EngineType.Node

// Set repository details for resolving additional depenecies
resolvers ++= Seq(
  "scalaz-bintray" at "https://dl.bintray.com/scalaz/releases",
  "ClouderaRepo" at "https://repository.cloudera.com/content/repositories/releases"
)

// Specifies dependencies to use in project
libraryDependencies ++= Seq(
  "org.apache.kafka" % "kafka_2.11" % "0.9.0.1",
  jdbc,
  cache,
  ws,
  specs2 % Test
)

// Add an additional source content route besides the default
unmanagedResourceDirectories in Test <+=  baseDirectory ( _ /"target/web/public/test" )

unmanagedSourceDirectories in Compile += baseDirectory.value / "src2" / "main" / "scala"

sourceDirectory in Compile <<= baseDirectory / "src2/main/scala"

scalaSource in Compile <<= baseDirectory / "src2/main/scala"

// Informs SBT Assembly how to handle duplicated files when combining project and dependency jars into a single fat jar
assemblyMergeStrategy in assembly := {
  case n if n.startsWith("reference.conf") => MergeStrategy.concat
  case PathList("META-INF", xs @ _*) => MergeStrategy.discard
  case x => MergeStrategy.first
}

在回答之前,我希望能够留下更多详细的评论,这样我的回答就可以更加精确,但是这个问题有点旧了,而且我的声誉也不够高...希望这可以帮到你。

附言:我在寻求帮助解决从play 2.3.4迁移至2.5.4时的合并问题时发现了您的问题。这就是为什么我更改了META-INF合并策略,只舍弃了MANIFEST.MF文件,否则会导致以下异常。我将其与我的答案重新发布,希望能够在搜索结果中被找到,因为我一开始搜索时找到的信息非常少。

Oops, cannot start the server.
com.google.inject.CreationException: Unable to create injector, see the following errors:

1) Error in custom provider, net.sf.ehcache.CacheException: java.lang.AssertionError: No net.sf.ehcache.EhcacheInit services found
  while locating play.api.cache.CacheManagerProvider
  while locating net.sf.ehcache.CacheManager
    for field at play.api.cache.NamedEhCacheProvider.manager(Cache.scala:211)
  while locating play.api.cache.NamedEhCacheProvider
  at com.google.inject.util.Providers$GuicifiedProviderWithDependencies.initialize(Providers.java:149)
  at play.api.cache.EhCacheModule.play$api$cache$EhCacheModule$$bindCache$1(Cache.scala:184):
Binding(interface net.sf.ehcache.Ehcache qualified with QualifierInstance(@play.cache.NamedCache(value=play)) to ProviderTarget(play.api.cache.NamedEhCacheProvider@45312be2)) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$1)

1 error
    at com.google.inject.internal.Errors.throwCreationExceptionIfErrorsExist(Errors.java:466)
    at com.google.inject.internal.InternalInjectorCreator.injectDynamically(InternalInjectorCreator.java:176)
    at com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:110)
    at com.google.inject.Guice.createInjector(Guice.java:96)
    at com.google.inject.Guice.createInjector(Guice.java:84)
    at play.api.inject.guice.GuiceBuilder.injector(GuiceInjectorBuilder.scala:181)
    at play.api.inject.guice.GuiceApplicationBuilder.build(GuiceApplicationBuilder.scala:123)
    at play.api.inject.guice.GuiceApplicationLoader.load(GuiceApplicationLoader.scala:21)
    at play.core.server.ProdServerStart$.start(ProdServerStart.scala:47)
    at play.core.server.ProdServerStart$.main(ProdServerStart.scala:22)
    at play.core.server.ProdServerStart.main(ProdServerStart.scala)
Caused by: net.sf.ehcache.CacheException: java.lang.AssertionError: No net.sf.ehcache.EhcacheInit services found
    at net.sf.ehcache.LibraryInit.init(LibraryInit.java:55)
    at net.sf.ehcache.CacheManager.init(CacheManager.java:366)
    at net.sf.ehcache.CacheManager.<init>(CacheManager.java:259)
    at net.sf.ehcache.CacheManager.newInstance(CacheManager.java:1037)
    at net.sf.ehcache.CacheManager.newInstance(CacheManager.java:936)
    at net.sf.ehcache.CacheManager.create(CacheManager.java:904)
    at play.api.cache.CacheManagerProvider.get$lzycompute(Cache.scala:205)
    at play.api.cache.CacheManagerProvider.get(Cache.scala:202)
    at play.api.cache.CacheManagerProvider.get(Cache.scala:201)
    at com.google.inject.internal.ProviderInternalFactory.provision(ProviderInternalFactory.java:81)
    at com.google.inject.internal.BoundProviderFactory.provision(BoundProviderFactory.java:72)
    at com.google.inject.internal.ProviderInternalFactory.circularGet(ProviderInternalFactory.java:61)
    at com.google.inject.internal.BoundProviderFactory.get(BoundProviderFactory.java:62)
    at com.google.inject.internal.SingleFieldInjector.inject(SingleFieldInjector.java:54)
    at com.google.inject.internal.MembersInjectorImpl.injectMembers(MembersInjectorImpl.java:132)
    at com.google.inject.internal.MembersInjectorImpl$1.call(MembersInjectorImpl.java:93)
    at com.google.inject.internal.MembersInjectorImpl$1.call(MembersInjectorImpl.java:80)
    at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1103)
    at com.google.inject.internal.MembersInjectorImpl.injectAndNotify(MembersInjectorImpl.java:80)
    at com.google.inject.internal.MembersInjectorImpl.injectMembers(MembersInjectorImpl.java:62)
    at com.google.inject.internal.InjectorImpl.injectMembers(InjectorImpl.java:984)
    at com.google.inject.util.Providers$GuicifiedProviderWithDependencies.initialize(Providers.java:149)
    at com.google.inject.util.Providers$GuicifiedProviderWithDependencies$$FastClassByGuice$$2a7177aa.invoke(<generated>)
    at com.google.inject.internal.cglib.reflect.$FastMethod.invoke(FastMethod.java:53)
    at com.google.inject.internal.SingleMethodInjector$1.invoke(SingleMethodInjector.java:57)
    at com.google.inject.internal.SingleMethodInjector.inject(SingleMethodInjector.java:91)
    at com.google.inject.internal.MembersInjectorImpl.injectMembers(MembersInjectorImpl.java:132)
    at com.google.inject.internal.MembersInjectorImpl$1.call(MembersInjectorImpl.java:93)
    at com.google.inject.internal.MembersInjectorImpl$1.call(MembersInjectorImpl.java:80)
    at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1092)
    at com.google.inject.internal.MembersInjectorImpl.injectAndNotify(MembersInjectorImpl.java:80)
    at com.google.inject.internal.Initializer$InjectableReference.get(Initializer.java:174)
    at com.google.inject.internal.Initializer.injectAll(Initializer.java:108)
    at com.google.inject.internal.InternalInjectorCreator.injectDynamically(InternalInjectorCreator.java:174)
    ... 9 more
Caused by: java.lang.AssertionError: No net.sf.ehcache.EhcacheInit services found
    at net.sf.ehcache.LibraryInit.initService(LibraryInit.java:78)
    at net.sf.ehcache.LibraryInit.init(LibraryInit.java:50)
    ... 42 more

一年后,谢谢!我找到了你编辑过的PS文本。我按照你的建议做了,但是在jar包上出现了“无效签名”错误。不过,多亏了你,我知道该去哪里看了。我得到的错误信息是:“org.apache.flink.client.program.ProgramInvocationException: The program's entry point class 'net.tagtog.services.documents.flink.Job' caused an exception during initialization: Invalid signature file digest for Manifest main attributes”。 - juanmirocks
找到解决方案了!(至少对我有效)。这个:case PathList("META-INF", "services", xs @ _*) => MergeStrategy.concat case PathList("META-INF", xs @ _*) => MergeStrategy.discard -- 您需要所有服务文件。 - juanmirocks

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接