是否可以让我的应用程序在运行时更新配置设置?我可以轻松地在我的 UI 中公开我想要的设置,但是有没有办法允许用户更新设置并使它们永久化,即将它们保存到 config.yaml 文件中?我可以看到它手动更新文件然后重新启动服务器的唯一方法,这似乎有点限制。
是的。可以在运行时重新加载服务类。
Dropwizard本身没有办法重新加载应用程序,但泽西有。
Jersey 在内部使用容器对象来维护正在运行的应用程序。Dropwizard 使用 Jersey 的 ServletContainer 类来运行应用程序。
如何在不重新启动的情况下重新加载应用程序 -
-
获得球衣内部使用的容器的手柄
您可以通过在启动应用程序之前在 Dropwizard 环境中注册 AbstractContainerLifeCycleListener 来执行此操作,并实现其 onStartup 方法,如下所示 -
在启动应用程序的主要方法中 -
//getting the container instance
environment.jersey().register(new AbstractContainerLifecycleListener() {
@Override
public void onStartup(Container container) {
//initializing container - which will be used to reload the app
_container = container;
}
});
- 向应用添加方法以重新加载应用。它将接收字符串列表,这些字符串是您要重新加载的服务类的名称。此方法将使用新的自定义 DropWizardConfiguration 实例调用容器的重新加载方法。
在您的应用程序类中
public static synchronized void reloadApp(List<String> reloadClasses) {
DropwizardResourceConfig dropwizardResourceConfig = new DropwizardResourceConfig();
for (String className : reloadClasses) {
try {
Class<?> serviceClass = Class.forName(className);
dropwizardResourceConfig.registerClasses(serviceClass);
System.out.printf(" + loaded class %s.n", className);
} catch (ClassNotFoundException ex) {
System.out.printf(" ! class %s not found.n", className);
}
}
_container.reload(dropwizardResourceConfig);
}
有关更多详细信息,请参阅球衣的示例文档 - 重新加载的球衣示例
考虑浏览 Dropwizard/Jersey 中以下文件的代码和文档,以便更好地理解 -
容器.java
ContainerLifeCycleListener.java
ServletContainer.java
AbstractContainerLifeCycleListener.java
DropWizardResourceConfig.java
资源配置.java
No.
Yaml 文件在启动时进行分析,并作为配置对象一劳永逸地提供给应用程序。我相信您可以在之后更改文件,但在重新启动它之前不会影响您的应用程序。
可能的后续问题:是否可以以编程方式重新启动服务?
AFAIK,没有。我已经为此进行了一些研究和阅读代码,但还没有找到一种方法。如果有的话,我很想听听:)。
我做了一个重新加载主yaml文件的任务(如果文件中的某些内容发生变化,这将很有用(。但是,它不会重新加载环境。在对此进行研究之后,Dropwizard使用了许多最终变量,如果不重新启动应用程序,则很难在旅途中重新加载这些变量。
class ReloadYAMLTask extends Task {
private String yamlFileName;
ReloadYAMLTask(String yamlFileName) {
super("reloadYaml");
this.yamlFileName = yamlFileName;
}
@Override
public void execute(ImmutableMultimap<String, String> parameters, PrintWriter output) throws Exception {
if (yamlFileName != null) {
ConfigurationFactoryFactory configurationFactoryFactory = new DefaultConfigurationFactoryFactory<ReportingServiceConfiguration>();
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();
ObjectMapper objectMapper = Jackson.newObjectMapper();
final ConfigurationFactory<ServiceConfiguration> configurationFactory = configurationFactoryFactory.create(ServiceConfiguration.class, validator, objectMapper, "dw");
File confFile = new File(yamlFileName);
configurationFactory.build(new File(confFile.toURI()));
}
}
}
您可以在 YAML 中更改配置,并在应用程序运行时读取它。但是,这不会重新启动服务器或更改任何服务器配置。您将能够读取任何更改的自定义配置并使用它们。例如,您可以在运行时更改日志记录级别或重新加载其他自定义设置。
我的解决方案 -
定义自定义服务器命令。您应该使用此命令而不是"服务器"命令来启动应用程序。
ArgsServerCommand.java
public class ArgsServerCommand<WC extends WebConfiguration> extends EnvironmentCommand<WC> {
private static final Logger LOGGER = LoggerFactory.getLogger(ArgsServerCommand.class);
private final Class<WC> configurationClass;
private Namespace _namespace;
public static String COMMAND_NAME = "args-server";
public ArgsServerCommand(Application<WC> application) {
super(application, "args-server", "Runs the Dropwizard application as an HTTP server specific to my settings");
this.configurationClass = application.getConfigurationClass();
}
/*
* Since we don't subclass ServerCommand, we need a concrete reference to the configuration
* class.
*/
@Override
protected Class<WC> getConfigurationClass() {
return configurationClass;
}
public Namespace getNamespace() {
return _namespace;
}
@Override
protected void run(Environment environment, Namespace namespace, WC configuration) throws Exception {
_namespace = namespace;
final Server server = configuration.getServerFactory().build(environment);
try {
server.addLifeCycleListener(new LifeCycleListener());
cleanupAsynchronously();
server.start();
} catch (Exception e) {
LOGGER.error("Unable to start server, shutting down", e);
server.stop();
cleanup();
throw e;
}
}
private class LifeCycleListener extends AbstractLifeCycle.AbstractLifeCycleListener {
@Override
public void lifeCycleStopped(LifeCycle event) {
cleanup();
}
}
}
在应用程序中重新加载的方法 -
_ymlFilePath = null; //class variable
public static boolean reloadConfiguration() throws IOException, ConfigurationException {
boolean reloaded = false;
if (_ymlFilePath == null) {
List<Command> commands = _configurationBootstrap.getCommands();
for (Command command : commands) {
String commandName = command.getName();
if (commandName.equals(ArgsServerCommand.COMMAND_NAME)) {
Namespace namespace = ((ArgsServerCommand) command).getNamespace();
if (namespace != null) {
_ymlFilePath = namespace.getString("file");
}
}
}
}
ConfigurationFactoryFactory configurationFactoryFactory = _configurationBootstrap.getConfigurationFactoryFactory();
ValidatorFactory validatorFactory = _configurationBootstrap.getValidatorFactory();
Validator validator = validatorFactory.getValidator();
ObjectMapper objectMapper = _configurationBootstrap.getObjectMapper();
ConfigurationSourceProvider provider = _configurationBootstrap.getConfigurationSourceProvider();
final ConfigurationFactory<CustomWebConfiguration> configurationFactory = configurationFactoryFactory.create(CustomWebConfiguration.class, validator, objectMapper, "dw");
if (_ymlFilePath != null) {
// Refresh logging level.
CustomWebConfiguration webConfiguration = configurationFactory.build(provider, _ymlFilePath);
LoggingFactory loggingFactory = webConfiguration.getLoggingFactory();
loggingFactory.configure(_configurationBootstrap.getMetricRegistry(), _configurationBootstrap.getApplication().getName());
// Get my defined custom settings
CustomSettings customSettings = webConfiguration.getCustomSettings();
reloaded = true;
}
return reloaded;
}
尽管dropwizard不支持此功能,但您可以使用他们为您提供的工具相当轻松地完成此操作。
在我开始之前,请注意,这不是所提问题的完整解决方案,因为它不会将更新的配置值保存到config.yml
。但是,只需从应用程序写入配置文件即可轻松实现。如果有人想编写此实现,请随时在我在下面链接的示例项目上打开 PR。
法典
从最小配置开始:
配置.yml
myConfigValue: "hello"
它是相应的配置文件:
示例配置.java
public class ExampleConfiguration extends Configuration {
private String myConfigValue;
public String getMyConfigValue() {
return myConfigValue;
}
public void setMyConfigValue(String value) {
myConfigValue = value;
}
}
然后创建一个更新配置的任务:
更新配置任务.java
public class UpdateConfigTask extends Task {
ExampleConfiguration config;
public UpdateConfigTask(ExampleConfiguration config) {
super("updateconfig");
this.config = config;
}
@Override
public void execute(Map<String, List<String>> parameters, PrintWriter output) {
config.setMyConfigValue("goodbye");
}
}
同样出于演示目的,创建一个允许您获取配置值的资源:
配置资源.java
@Path("/config")
public class ConfigResource {
private final ExampleConfiguration config;
public ConfigResource(ExampleConfiguration config) {
this.config = config;
}
@GET
public Response handleGet() {
return Response.ok().entity(config.getMyConfigValue()).build();
}
}
最后,连接应用程序中的所有内容:
示例应用程序.java(摘录(
environment.jersey().register(new ConfigResource(configuration));
environment.admin().addTask(new UpdateConfigTask(configuration));
用法
启动应用程序,然后运行:
$ curl 'http://localhost:8080/config'
hello
$ curl -X POST 'http://localhost:8081/tasks/updateconfig'
$ curl 'http://localhost:8080/config'
goodbye
工作原理
这只需将相同的引用传递给 ConfigResource.java
和 UpdateConfigTask.java
的构造函数即可工作。如果您不熟悉该概念,请参阅此处:Java是"按引用传递"还是"按值传递"?
上面的链接类是我创建的一个项目,该项目将其演示为一个完整的解决方案。这是该项目的链接:
scottg489/dropwizard-runtime-config-example
脚注:我尚未验证这是否适用于内置配置。但是,您需要为自己的配置扩展的 dropwizard 配置类确实具有用于内部配置的各种"setter",但是在 run()
之外更新这些"setter"可能不安全。
免责声明:我在这里链接的项目是我创建的。