Sbt dist打包后无法访问非托管资源?求解决方案
开发环境下sbt run会直接读取本地文件系统中的works目录,而sbt dist打包后,应用是从编译好的JAR包运行的——Assets.at默认指向的是打包进JAR的类路径资源,但你works目录里的文件是运行时动态生成的,根本不在打包后的JAR里,自然会返回404。
而且你原来的routes配置里path="/works/"也有问题:这个路径在生产环境中会被解析为JAR内部的路径,而非外部文件系统的目录。
针对运行时动态生成的资源,Play提供了专门的ExternalAssets控制器(Play 2.6及以上版本支持),用来访问文件系统上的外部目录,而非打包进应用的静态资源。具体步骤如下:
1. 修改路由配置
打开conf/routes,把原来的works路由替换为ExternalAssets:
GET /works/*file controllers.ExternalAssets.at(path=conf.getString("works.dir"), file)
2. 配置外部目录路径
在conf/application.conf中添加works.dir的配置,指定运行时works目录的位置(建议用相对路径,方便部署):
# 应用运行时的works目录,默认放在应用启动目录下 works.dir = ${user.dir}/works
3. 自动创建目录(可选但推荐)
因为sbt dist打包后不会自动创建works目录,所以最好在应用启动时自动生成这个目录,避免运行时报错。
方式一:使用ApplicationLoader(Play 2.7+推荐)
创建一个自定义的ApplicationLoader:
import play.api._ import java.nio.file.{Files, Paths} class CustomApplicationLoader extends ApplicationLoader { override def load(context: ApplicationLoader.Context): Application = { // 读取配置中的works目录路径 val worksDirPath = context.environment.config .getString("works.dir") .getOrElse("./works") // 创建目录(递归创建父目录) Files.createDirectories(Paths.get(worksDirPath)) // 加载应用 new BuiltInComponentsFromContext(context).application } }
然后在application.conf中指定使用这个Loader:
play.application.loader = "com.yourpackage.CustomApplicationLoader"
方式二:使用GlobalSettings(旧版本Play)
如果你的Play版本低于2.7,可以用GlobalSettings:
import play.api._ import java.io.File object Global extends GlobalSettings { override def onStart(app: Application): Unit = { val worksDirPath = app.configuration .getString("works.dir") .getOrElse("./works") new File(worksDirPath).mkdirs() } }
4. 开发环境适配(保持一致性)
开发时你可以继续用sbt run,只要本地的works目录和配置中的路径一致,ExternalAssets就能正常访问,不需要保留原来的unmanagedResourceDirectories配置(当然保留也不冲突,但统一用ExternalAssets能让开发/生产环境行为一致)。
sbt dist完全是Play应用生产部署的标准方式,没有问题——它会把应用打包成一个可解压的ZIP包,包含编译后的代码、依赖和启动脚本,非常适合生产环境部署。
内容的提问来源于stack exchange,提问作者min

