【问题标题】:Guice, Settings, and Boilerplate codeGuice、设置和样板代码
【发布时间】:2013-02-15 06:56:30
【问题描述】:

我有一个使用 Guice 并从配置文件中读取一些配置设置的应用程序。我是这样加载的:

@Provides @Singleton
Settings provideSettings() {
    // code that loads the settings
}

某些对象需要某些设置,其他对象需要其他设置。在我看来,在构造函数中传递这些东西是有意义的,但最终我得到了很多样板代码,比如:

@Provides @Named("integerValueSetting1")
int provideIntegerValueSetting1(Settings settings) {
    return settings.getInteger("integerValueSetting1");
}

而且我必须为每一种设置类型创建这样一个@Provides 方法,而且我必须在构造函数中注释适当的设置。像这样:

@Inject
public MyClass(@Assisted String objectName, @Named("integerValueSetting1") myValue) {
    // blah blah constructor
}

这似乎并没有为我节省多少!更糟糕的是,如果我为每个设置参数创建了一个自定义注释类。一定有更好的方法吧?

一种解决方案是直接传递 Settings 对象,但这违反了best practices: Inject only direct dependencies...

【问题讨论】:

  • 设置直接依赖。仅仅因为您不调用 Settings 对象的每个方法并不意味着它不是直接依赖项。
  • @JBNizet 好吧,我确实有一个与设置对象关联的接口......现在它只是从一个平面文件中读取,但我想稍后将它变成一个数据库。但我想无论哪种方式它都会有相同的界面......
  • 所以,直接注入设置。要进行单元测试,您只需注入一个模拟设置对象并存根被测对象使用的方法。依赖注入的经典用法。

标签: java guice named-parameters


【解决方案1】:

如果您有大量设置,并且希望避免为每个设置创建新的绑定注释,您可以尝试将它们放在一个枚举中,并在一个通用绑定注释中使用该枚举。这可能是一个有点复杂的解决方案,但它也可以节省您试图避免的样板。

通过这种方式,您可以匹配对象引用(IDE 友好)而不是字符串(缓慢而脆弱),并且仍然只创建一个绑定注释。

public enum Config {
  DB_NAME("db_name"),
  DB_HOST("db_host_name_specified_in_file"),
  SOME_NUMBER("some_number"),
  ;

  private final String propertyName;

  private Config(String propertyName) {
    this.propertyName = propertyName;
  }

  public String getPropertyName() {
    return propertyName;
  }

  public InjectConfig annotation() {
    // Create an implementation of InjectConfig for ease of binding.
    return new InjectConfig() {
      @Override public Class<? extends Annotation> annotationType() {
        return InjectConfig.class;
      }

      @Override public Config value() {
        return Config.this;
      }

      @Override public boolean equals(Object obj) {
        if (obj == this) {
          return true;
        } else if (!(obj instanceof InjectConfig)) {
          return false;
        }
        return value() == ((InjectConfig) obj).value();
      }

      /** @see Annotation#hashCode */
      @Override public int hashCode() {
        return (127 * "value".hashCode()) ^ value().hashCode();
      }
    };
  }

  @Retention(RetentionPolicy.RUNTIME)
  @BindingAnnotation
  public static @interface InjectConfig {
    Config value();
  }
}

现在您可以循环遍历并绑定每一个:

public class YourModule extend AbstractModule {
  @Override public void configure() {
    // You can get a Provider in a Module as long as you
    // don't call get() before the injector exists.
    Provider<Settings> settingsProvider = binder().getProvider(Settings.class);
    for (Config config : Config.values()) {
      String propertyName = config.getPropertyName();
      // Guice's TypeConverter will convert Strings to the right type.
      bind(String.class).annotatedWith(config.annotation()).toProvider(
          new GetValueFromSettingsProvider(settingsProvider, propertyName));
    }
  }
}

并且只注入你需要的东西,直接:

/** Your constructor */
YourClass(@InjectConfig(DB_USER) String user,
    @InjectConfig(SOME_NUMBER) int number) { }

我没有机会对此进行测试,但据我所知 应该 工作。鉴于您的特定设置用例,您可能需要修改您编写的GetValueFromSettingsProvider,或者在枚举中编写一个可覆盖的getConfigValueFromSettings 方法。但请记住,您仍然需要以一种或另一种方式存储(枚举键、文件中的属性名称、属性类型)元组,而 Enum 似乎是以编程方式管理它的最佳方式。

【讨论】:

  • 这太棒了。我已经有了枚举,只是没有用它来做注释
  • 我还没试过,但是使用注解会不会是@Config.InjectConfig?另外,我不确定您是否可以像这样匿名声明InjectConfig
  • 嵌套以便于阅读; Config.InjectConfig 有效,或将其排除在外(在 IDE 中可能效果更好)。我认为匿名实现确实有效,但无论如何我不会责怪您将其分解出来。
  • 如果我在设置类中使用@Provides 而不是Provider,这会起作用吗?
  • Guice 擅长于提供程序...您可以绑定XProvider&lt;X&gt;(包括使用@Provides 创建的隐式提供程序),并请求注入XProvider&lt;X&gt; ,并且任何组合都有效。不过,您仍然需要一些 GetValueFromSettingsProvider 或类似的实现,因为 Guice 需要能够从您的 Settings 中获取绑定值。
【解决方案2】:

查看Tadeon 项目,尤其是它的配置 功能。

使用以下.properties 文件:

foo=foo
list=1, 2, 3

和组件:

public class PropertyInjectedComponent {

    private final String foo;
    private final List<String> list;

    @Inject
    public PropertyInjectedComponent(
        @Named("foo") String foo,
        @Named("list") List<String> list) {
    this.foo = foo;
    this.list = list;
    }
    ...
}

您可以简单地配置 guice 模块来扫描命名值的属性:

Injector injector = Guice.createInjector(new AbstractModule() {
        protected void configure() {
            GuiceConfigurations.bindProperties(binder(), new File("src/test/data"), "conf1.properties");
        }
    });

这里有test 文件。

您可以将 Settings 类替换为 apache Configuration、JDK Properties 或只是 Map&lt;String, String&gt;investigate Tadeon source 为您的 Settings 类创建类似的解决方案。

【讨论】:

  • FWIW 我的 Settings 类由 JDK 属性支持,但我还有其他功能(缓存、多种类型的 get 方法等)。另外,重要的是我让自己可以选择更改它所支持的内容,因为我知道在开发周期的后期我打算切换到数据库。
  • 如果你不想提交额外的库,你可以使用内置的 Names.bindProperties 来获得大部分的方式。不过,Tadeon 可能还有一些您会喜欢的附加功能。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-05-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-31
  • 1970-01-01
相关资源
最近更新 更多