2013-02-28 17 views
4

我有一個應用程序使用Guice並從配置文件中讀取一些配置設置。我這樣加載:Guice,Settings和Boilerplate代碼

@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 ......

+1

設置*是*直接依賴。僅僅因爲你沒有調用Settings對象的每個方法並不意味着它不是一個直接的依賴關係。 – 2013-02-28 22:04:42

+0

@JBNizet好吧,我確實有一個與設置對象相關的接口...現在它只是從平面文件中讀取數據,但我希望稍後將其作爲數據庫。但我想它會有相同的接口,無論哪種方式... – durron597 2013-02-28 22:14:23

+1

因此,直接注入設置。爲了進行單元測試,你只需要注入一個模擬設置對象,並存儲被測對象使用的方法。經典的依賴注入。 – 2013-02-28 22:30:47

回答

2

如果您有很多設置,並且希望避免爲每個設置創建新的綁定註釋,您可以嘗試將它們放入枚舉中,並在通用綁定註釋中使用該枚舉。這可能是一個解決方案有點複雜,但它也可以節省您試圖避免的樣板文件。通過這種方式,可以匹配對象引用(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是以編程方式管理它的最佳方式。

+0

這太棒了。我已經有枚舉,我只是沒有使用它來做註釋 – durron597 2013-03-05 16:56:46

+0

我還沒有嘗試過,但它會是'@ Config.InjectConfig'來使用註釋嗎?另外,我不確定你可以這樣匿名聲明'InjectConfig'。 – durron597 2013-03-05 17:01:40

+0

它的嵌套爲SO讀取方便; Config.InjectConfig的工作原理,或將其分解(這可能會在IDE中更好地工作)。無論如何,匿名實現的確行得通,但我不會責怪你。 – 2013-03-05 17:19:56

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; 
    } 
    ... 
} 

,你可以簡單地配置吉斯模塊掃描屬性命名值:

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

這裏有test文件。

您可以與Apache Configuration,JDK Properties或者只是Map<String, String>investigate Tadeon source更換您Settings類來創建你的Settings類類似的解決方案。

+0

FWIW我的Settings類由JDK屬性支持,但我還有其他功能(緩存,多種類型的get方法等)。另外,我必須讓自己選擇更改支持的選項,因爲我知道在開發後期我打算切換到數據庫。 – durron597 2013-02-28 23:39:56

+0

如果您不想提交額外的庫,可以使用內置的['Names.bindProperties'](http://google-guice.googlecode.com/git/javadoc/com/google/inject /name/Names.html)來獲取大部分路徑。不過,Tadeon可能會有一些額外的功能,您可以欣賞。 – 2013-03-04 07:04:28