我是scala的新手,刚从我的scala第一个应用程序开始。
我已经在资源文件夹application.conf下定义了配置文件
projectname{
"application" {
"host":127.0.0.1
"port":8080
}
}
我编写了一个配置解析器文件,用于从配置文件解析到案例类
case class AppConfig (config: Config) {
val host = config.getString("projectname.application.host")
val port = config.getInt("projectname.application.port")
}
在我的grpc服务器文件中,我已将config声明为
val config = AppConfig(ConfigFactory.load("application.conf"))
我想在整个应用程序中使用此配置变量,而不是每次都加载application.conf文件。
我想拥有一个引导功能,它将一次解析此配置,使其可在整个应用程序中使用
答案 0 :(得分:4)
您可以使用PureConfig自动执行此操作。
通过以下方式向您build.sbt
添加Pure Config:
libraryDependencies += "com.github.pureconfig" %% "pureconfig" % "0.11.0"
并重新加载sbt shell并更新您的依赖项。
现在,假设您拥有以下resource.conf
文件:
host: example.com
port: 80
user: admin
password: admin_password
您可以定义一个名为AppConfig
的案例类:
case class AppConfig(
host: String,
port: Int,
user: String,
password: String
)
并使用loadConfig
方法创建由应用程序配置填充的实例:
import pureconfig.generic.auto._
val errorsOrConfig: Either[ConfigReaderFailures, AppConfig] = pureconfig.loadConfig[AppConfig]
这将返回错误或AppConfig,具体取决于配置本身中的值。
例如,如果上面的port
的值为eighty
,而不是80,则将得到一个详细的错误,指出第二个配置行(带有port: eighty
)包含一个字符串,但唯一有效的预期类型是数字:
ConfigReaderFailures(
ConvertFailure(
reason = WrongType(
foundType = STRING,
expectedTypes = Set(NUMBER)
),
location = Some(
ConfigValueLocation(
new URL("file:~/repos/example-project/target/scala-2.12/classes/application.conf"),
lineNumber = 2
)
),
path = "port"
)
)
如果您想获得loadConfigOrThrow
而不是Either,则可以使用AppConfig
。
在应用程序启动时(尽可能接近主函数)加载此配置一次之后,只需在构造函数中传递AppConfig即可使用依赖项注入将其传递给所有其他类。
如果您想使用MacWire与config case类连接您的Logic类(和其他服务),如Krzysztof在他的选择之一中建议的那样,您可以看到我的答案here。
一个简单的示例(没有 MacWire )如下所示:
package com.example
import com.example.config.AppConfig
object HelloWorld extends App {
val config: AppConfig = pureconfig.loadConfigOrThrow[AppConfig]
val logic = new Logic(config)
}
class Logic(config: AppConfig) {
// do something with config
}
在AppConfig.scala
中定义了AppConfig的地方
package com.example.config
case class AppConfig(
host: String,
port: Int,
user: String,
password: String
)
作为奖励,当您在IDE中使用此配置变量时,将获得代码完成。
此外,您的配置可能是根据supported types构建的,例如String,Boolean,Int等,也可能是根据受支持的类型构建的其他case类(这是因为case类表示一个值对象,其中包含数据)以及受支持类型的列表和选项。
这使您可以“分类”复杂的配置文件并完成代码。例如,在application.conf
中:
name: hotels_best_dishes
host: "https://example.com"
port: 80
hotels: [
"Club Hotel Lutraky Greece",
"Four Seasons",
"Ritz",
"Waldorf Astoria"
]
min-duration: 2 days
currency-by-location {
us = usd
england = gbp
il = nis
}
accepted-currency: [usd, gbp, nis]
application-id: 00112233-4455-6677-8899-aabbccddeeff
ssh-directory: /home/whoever/.ssh
developer: {
name: alice,
age: 20
}
然后在您的代码中定义一个配置案例类:
import java.net.URL
import java.util.UUID
import scala.concurrent.duration.FiniteDuration
import pureconfig.generic.EnumCoproductHint
import pureconfig.generic.auto._
case class Person(name: String, age: Int)
sealed trait Currency
case object Usd extends Currency
case object Gbp extends Currency
case object Nis extends Currency
object Currency {
implicit val currencyHint: EnumCoproductHint[Currency] = new EnumCoproductHint[Currency]
}
case class Config(
name: String,
host: URL,
port: Int,
hotels: List[String],
minDuration: FiniteDuration,
currencyByLocation: Map[String, Currency],
acceptedCurrency: List[Currency],
applicationId: UUID,
sshDirectory: java.nio.file.Path,
developer: Person
)
并加载:
val config: Config = pureconfig.loadConfigOrThrow[Config]
答案 1 :(得分:3)
有一些可能解决您的问题:
使用运行时依赖注入框架,例如 guice 。您可以使用extension for scala。
使用隐式对其进行处理。您只需要创建一个对象,该对象将保留您的隐式配置:
object Implicits {
implicit val config = AppConfig(ConfigFactory.load("application.conf"))
}
然后您可以在需要时将implicit config: Config
添加到参数列表中:
def process(n: Int)(implicit val config: Config) = ??? //as method parameter
case class Processor(n: Int)(implicit val config: AppConfig) //or as class field
并像这样使用它:
import Implicits._
process(5) //config passed implicitly here
Processor(10) //and here
它的一个很大的优点是您可以手动通过config
进行测试:
process(5)(config)
这种方法的缺点是,您的应用程序中包含很多隐式分辨率,这会使编译速度变慢,但是如果您的应用程序不笨拙,那应该不是问题。
配置类的一个字段(称为构造函数注入)。
class Foo(config: Config).
然后,您可以手动连接依赖项,例如:
val config: AppConfig = AppConfig()
val foo = Foo(config) //you need to pass config manually to constructors in your object graph
或者您可以使用可以为您自动化的框架,例如macwire:
val config = wire[AppConfig]
val foo = wire[Foo]
您可以使用称为cake-pattern的模式。它适用于小型应用程序,但是您的应用程序越大,这种方法就越笨拙。
什么是否?一种好的方法是使用全局单例,如下所示:
object ConfigHolder {
val Config: AppConfig = ???
}
然后像这样使用它:
def process(n: Int) = {
val host = ConfigHolder.Config.host // anti-pattern
}
这很糟糕,因为它使模拟测试配置变得非常困难,并且整个测试过程变得笨拙。
我认为,如果您的应用程序不是很大,则应使用隐式。
如果您想了解更多有关此主题的信息,请选中this great guide。
答案 2 :(得分:0)
您试图实现的模式称为“依赖注入”。摘自Martin Fowler的,关于这个主题
“依赖注入”的基本思想是拥有一个单独的对象,即一个汇编器,该对象将使用finder接口的适当实现填充lister类中的字段。
在诸如post之类的依赖注入工具中注册此配置实例。
class AppModule(conf: AppConfiguration) extends AbstractModule {
override def configure(): Unit = {
bind(classOf[AppConfiguration]).toInstance(conf)
}
}
....
// somewhere in the code
import com.google.inject.Inject
class FooClass @Inject() (config: AppConfiguration)
答案 3 :(得分:0)
您应该将字段定义为案例类的参数。
final case class AppConfig(host: String, port: Int)
然后您重载伴侣对象的apply方法
object AppConfig {
def apply(config: Config): AppConfig = {
val host = config.getString("projectname.application.host")
val port = config.getInt("projectname.application.port")
AppConfig(host, port)
}
}
但是,处理带有案例类的配置的最简单方法是使用pureconfig。
答案 4 :(得分:0)
我想在整个应用程序中使用此配置变量,而不是每次都加载application.conf文件。
只需将其放在object
中,例如
object MyConfig {
lazy val config = AppConfig(ConfigFactory.load("application.conf"))
}
我想拥有一个引导功能,它将一次解析此配置,使其可在整个应用程序中使用
一旦您调用MyConfig.config
,它就会被加载一次-因为object
是 Singleton 。因此,不需要特殊的引导程序。