Guice,Settings和Boilerplate代码

时间:2013-02-28 21:57:58

标签: java guice named-parameters

我有一个使用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
}

这似乎不会让我太多!更糟糕的是,如果我为每个设置参数创建了自定义注释类。必须有一个更好的方法,对吗?

一种解决方案可能是直接传递“设置”对象,但这违反了best practices: Inject only direct dependencies ...

2 个答案:

答案 0 :(得分:2)

如果您有大量设置,并且想要避免为每个设置创建新的绑定注释,您可以尝试将它们放在枚举中并在公共绑定注释中使用该枚举。这可能是解决方案的一个复杂问题,但它也可能会保存您试图避免的样板。

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

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是以编程方式管理它的最佳方式。

答案 1 :(得分:0)

查看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个文件。

您可以使用apache Settings,JDK ConfigurationPropertiesinvestigate Tadeon source替换Map<String, String>课程,为您的{{1}创建类似的解决方案}。class。