我正在使用Guice进行DI,并尝试以插件样式加载模块。
想象一下,我有一个UserProvider
,它需要一个IDataSource
。 IDataSource
可以是DatabaseDataSource
或LdapDataSource
,但不能同时包含两者(同时拥有2个真值来源是否有意义,是吗?)。然后我将使用从文件中的配置加载的类名字符串来选择IDataSource
(例如,部署人员可以配置要使用的数据源)。
我已经列出了这样的代码
guice
|- + user
|- + datasource
|- + database
|- DatabaseDataSourceConfig.java
|- DatabaseDataSource.java
|- DatabaseDataSourceModule.java
|- + ldap
|- LdapDataSourceConfig.java
|- LdapDataSource.java
|- LdapDataSourceModule.java
|- IDataSource
|- UserProvider.java
|- UserProviderConfig.java
|- UserProviderModule.java
|- Factory.java
|- FactoryConfig.java
Factory.java
public class Factory {
public static UserProvider create(FactoryConfig settings) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Injector injector = Guice.createInjector(
new AbstractModule() {
@Override
public void configure() {
bind(FactoryConfig.class).toInstance(settings);
}
},
(Module)Class.forName(settings.getUserProviderModuleClassName()).newInstance());
return injector.getInstance(UserProvider.class);
}
public static void main(String[] args) throws Exception {
// simiulate building config from file
String modClass = "guice.main.user.UserProviderModule"; // or a stub that doesn't hit the data source
String subModClass = "guice.main.user.datasource.ldap.LdapDataSourceModule";
Config config = ConfigFactory.empty()
.withValue("user.module", ConfigValueFactory.fromAnyRef(modClass))
.withValue("user.dataSource.module", ConfigValueFactory.fromAnyRef(subModClass));
FactoryConfig factoryConfig = new FactoryConfig(config);
IUserProvider provider = Factory.create(factoryConfig);
provider.work();
}
}
FactoryConfig.java
public class FactoryConfig {
private interface Keys {
String USER_PROVIDER_MODULE = "user.module";
String USER_PROVIDER_CONFIG = "user";
}
private Config config;
public FactoryConfig(Config config) {
this.config = config;
}
public String getUserProviderModuleClassName() {
return config.getString(Keys.USER_PROVIDER_MODULE);
}
public Config getUserProviderConfig() {
return config.getConfig(Keys.USER_PROVIDER_CONFIG);
}
}
UserProvider.java
public class UserProvider implements IUserProvider {
private UserProviderConfig config;
private IDataSource dataSource;
public UserProvider(UserProviderConfig config, IDataSource dataSource) {
this.config = config;
this.dataSource = dataSource;
}
@Override
public void work() {
System.out.println("UserProvider");
dataSource.work();
}
}
UserProviderConfig.java
public class UserProviderConfig {
private interface Keys {
String DATA_SOURCE_PROVIDER_MODULE = "dataSource.module";
String DATA_SOURCE_PROVIDER_CONFIG = "dataSource";
}
private Config config;
public UserProviderConfig(Config config) {
this.config = config;
}
public String getDataSourceModuleClassName() {
return config.getString(Keys.DATA_SOURCE_PROVIDER_MODULE);
}
public Config getDataSourceConfig() {
return config.getConfig(Keys.DATA_SOURCE_PROVIDER_CONFIG);
}
}
UserProviderModule.java
public class UserProviderModule extends PrivateModule {
@Override
protected void configure() {
}
@Provides
public UserProviderConfig userProviderConfig(FactoryConfig settings) {
return new UserProviderConfig(settings.getUserProviderConfig());
}
@Provides
@Exposed
public IUserProvider userProvider(UserProviderConfig config, Injector injector) throws Exception {
Injector childInjector = injector.createChildInjector((Module)Class.forName(config.getDataSourceModuleClassName()).newInstance());
return new UserProvider(config, childInjector.getInstance(IDataSource.class));
}
}
然后我认为DatabaseDataSource
和LdapDataSource
在这里并不重要。
在实践中,我测试过子模块注射是否有效。但是,我想知道这是否是在子模块中加载插件的推荐方法。
我看到过只需在IDataSource
方法中加载Factory.create
的建议,但这意味着现在Factory
必须知道此IDataSource
存在并且必须对其进行管理。从理论上讲,Factory
应该只知道它所关心的顶级提供者,例如只有UserProvider
并让UserProvider
使用DI来提供自己。