在运行时更新 Dropwizard 配置



是否可以让我的应用程序在运行时更新配置设置?我可以轻松地在我的 UI 中公开我想要的设置,但是有没有办法允许用户更新设置并使它们永久化,即将它们保存到 config.yaml 文件中?我可以看到它手动更新文件然后重新启动服务器的唯一方法,这似乎有点限制。

是的。可以在运行时重新加载服务类。

Dropwizard本身没有办法重新加载应用程序,但泽西有。

Jersey 在内部使用容器对象来维护正在运行的应用程序。Dropwizard 使用 Jersey 的 ServletContainer 类来运行应用程序。

如何在不重新启动的情况下重新加载应用程序 -

  1. 获得球衣内部使用的容器的手柄

    您可以通过在启动应用程序之前在 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;
        }
    });  
  1. 向应用添加方法以重新加载应用。它将接收字符串列表,这些字符串是您要重新加载的服务类的名称。此方法将使用新的自定义 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.javaUpdateConfigTask.java 的构造函数即可工作。如果您不熟悉该概念,请参阅此处:Java是"按引用传递"还是"按值传递"?

上面的链接类是我创建的一个项目,该项目将其演示为一个完整的解决方案。这是该项目的链接:

scottg489/dropwizard-runtime-config-example

脚注:我尚未验证这是否适用于内置配置。但是,您需要为自己的配置扩展的 dropwizard 配置类确实具有用于内部配置的各种"setter",但是在 run() 之外更新这些"setter"可能不安全。

免责声明:我在这里链接的项目是我创建的。

最新更新