我有一段使用反射工作的现有代码,但如果可能的话,我想开始使用依赖注入和Guice创建对象。
现在是这样的:
- 加载
- Configuration (
.properties
)文件,格式为字符串-
objects=Foo,^ab..$;Bar,^.bc.$;Baz,i*
- 注:
Foo
、Bar
、Baz
是实现MyInterface
的类 - 每对都有一个正则表达式与之配对。
-
- 从另一个来源输入数据。想象一下,对于这个例子,数据是:
-
String[]{ "abab", "abcd", "dbca", "fghi", "jklm" }
-
- 然后我想创建由Guice创建的
Foo
,Bar
和Baz
的新实例。- 在这种情况下,创建的实例将是:
-
new Foo("abab");
-
new Foo("abcd");
-
new Bar("abcd");
-
new Bar("dbca");
-
new Baz("fghi");
-
"jklm"
将不会创建任何新的实例,因为它没有匹配的模式。
-
- 在这种情况下,创建的实例将是:
这是它目前如何工作的(这是我能做的最好的sscce明智),使用反射:
public class MyInterfaceBuilder {
private Classloader tcl = Thread.currentThread().getContextClassLoader();
private Pattern p;
private Class<? extends MyInterface> klass;
public InterfaceBuilder(String className, String pattern) {
this.pattern = Pattern.compile(pattern);
this.klass = makeClass(className);
}
private static Class<? extends Interface> makeClass(String className) {
String fullClassName = classPrefix + className;
Class<?> myClass;
try {
myClass = tcl.loadClass(fullClassName);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Class not found: " + fullClassName, e);
}
if(MyInterface.class.isAssignableFrom(myClass)) {
return (Class<? extends MyInterface>) myClass;
} else {
throw new IllegalArgumentException(fullClassName + " is not a MyInterface!");
}
}
public MyInterface makeInstance(String type) {
if (pattern == null || pattern.matcher(type).find()) {
MyInterface newInstance = null;
try {
newInstance = klass.getConstructor(String.class).newInstance(type);
} catch (Exception e) {
// Handle exceptions
}
return newInstance;
} else {
return null;
}
}
}
我如何使用Guice来复制这个功能(在运行时动态加载类,并创建完全匹配的实例)?
我很确定,如果没有任何反射和仅使用Guice,您无法做到这一点。这是因为Guice不是为这样的事情而生的。Guice的任务是帮助进行依赖关系管理,而不是使用创建对象的不同策略(在某种程度上是这样的,但没有达到您需要的程度)。
但是,如果您需要使用使用来自file的信息创建的对象作为其他对象的依赖项,您可以这样做。把你的对象预加载到某种映射中,我想应该是这样的:
Map<String, MyInterface> myInterfaceMap;
// You fill it with pairs "abcd" -> new Foo("abcd"), "abab" -> new Foo("abab") etc
那么就有两种可能。如果你的字符串键的集合是静态已知的,你想利用它(例如注入一些键的对象到一些类,与其他键的对象到不同的类),那么你可以传递映射到模块和创建一组动态绑定,使用@Named
绑定注释:
for (Map.Entry<String, MyInterface> entry : myInterfaceMap) {
bind(MyInterface.class)
.annotatedWith(Names.named(entry.getKey()))
.toInstance(entry.getValue());
}
之后,你可以注入这些对象如下:
class SomeOtherClass {
// previous 'new Foo("abcd")' object will be injected here
@Inject
SomeOtherClass(@Named("abcd") MyInterface interface) {
// whatever
}
}
如果您的字符串键集是动态的,那么您可能希望在运行时将这些对象作为一个集合进行检查。在这种情况下,你可以像往常一样绑定它:
bind(new TypeLiteral<Map<String, MyInterface>>() {}).toInstance(myInterfaceMap);
然后你可以注射:
@Inject
SomeOtherClass(Map<String, MyInterface> interface) {
// whatever
}
注意,很明显,即使你的键集是静态的,你也可以绑定一个映射,反之亦然,也就是说,即使键集是动态的,你也可以创建多个@Named
绑定。但是我认为这些用例是不太可能的。
注意,只有当你想把你的对象注入到其他对象时,上面的规则才成立。可以很容易地修改上面的示例,以支持对象自己的依赖项的注入。但是,如果两者都不是您的情况,也就是说,您不想将对象作为依赖项注入,并且它们本身没有依赖项,那么您可能根本不需要Guice来完成此任务。
UPDATE(考虑注释)
好的,你想要注入对象的依赖项。
如果你的关键字串必须通过构造函数提供给对象,那么最简单的方法,我想,将是使用方法/字段注入。这样整个过程看起来就像这样。首先像往常一样创建对象,然后在循环中使用Injector.injectMembers()
方法,像这样:
Map<String, MyInterface> myInterfaceMap = ...;
Injector injector = ...; // create the injector
for (MyInterface myInterface : myInterfaceMap.values()) {
injector.injectMembers(myInterface);
}
这是可能的最简单的解决方案,但它要求对象的所有依赖都通过方法提供,而不是通过构造函数。
如果依赖项必须通过构造函数提供,那么事情会变得更复杂。您必须为您的类手动编写一个工厂,并将其与Guice集成。工厂可以是这样的:
public interface MyInterfaceFactory {
MyInterface create(String name);
}
public class ReflectiveFromFileMyInterfaceFactory implements MyInterfaceFactory {
// You have to inject providers for all dependencies you classes need
private final Provider<Dependency1> provider1;
private final Provider<Dependency2> provider2;
private final Provider<Dependency3> provider3;
@Inject
ReflectiveFromFileMyInterfaceFactory(Provider<Dependency1> provider1,
Provider<Dependency2> provider2,
Provider<Dependency3> provider3) {
this.provider1 = provider1;
this.provider2 = provider2;
this.provider3 = provider3;
}
@Override
public MyInterface create(String name) {
// Here you query the file and create an instance of your classes
// reflectively using the information from file and using providers
// to get required dependencies
// You can inject the information from file in this factory too,
// I have omitted it for simplicity
}
}
然后将工厂绑定到模块中:
bind(MyInterfaceFactory.class).to(ReflectiveFromFileMyInterfaceFactory.class);
,然后照常注射。
然而,这种方法要求你事先知道你的类有哪些依赖关系。
如果你事先不知道你的类有哪些依赖关系,那么我认为你可以使用私有模块和上面的一些东西来实现你想要的,但在你的情况下,这很快就会变得笨拙。但是,如果您使用私有模块,则可能不需要使用反射。
经过进一步的思考,我开始怀疑我是否应该少关心将运行时参数传递给构造函数,而更多地关注使用这个答案中提到的创建和配置概念。下面的示例没有错误检查,但是实际的实现版本会抛出大量的NullPointerException
和IllegalArgumentException
来处理坏数据。但这里有一个想法:
基本上是这样的:
// This could be done a number of different ways
public static void main() {
Injector inj = Guice.createInjector(new MyOuterModule());
Injector child = inj.createChildInjector(new MyPluginModule(/* interfaceFileName? */));
MyApp app = child.getInstance(MyApp.class);
app.run();
}
public class MyPluginModule extends AbstractModule {
@Override
protected void configure() {
MapBinder<String, MyInterface> mapBinder
= newMapBinder(binder(), String.class, MyInterface.class);
// These could probably be read from a file with reflection
mapBinder.addBinding("Foo").to(Foo.class);
mapBinder.addBinding("Bar").to(Bar.class);
}
}
public class InterfaceFactory {
private Pattern p;
@Inject private Map<Provider<MyInterface>> providerMap;
private Provider<MyInterface> selectedProvider;
public void configure(String type, String pattern) {
p = Pattern.compile(pattern);
selectedProvider = providerMap.get(type);
}
public MyInterface create(String data) {
if(pattern.matcher(data).find()) {
MyInterface intf = selectedProvider.get();
intf.configure(data);
}
}
}
这看起来比我现在用的干净多了。
优点:
- 使用Guice创建对象
- 反射被最小化和划分
- 我不需要任何依赖项的知识
缺点:
- 我必须写我的类,以便能够知道如何做,如果他们被创建没有配置
- 要么我需要能够在添加插件绑定之前读取我的配置文件,要么在代码中定义它们。
我在添加另一个答案,因为第一个已经太大了。
我能够实现看似你需要使用多binder和私有模块。
首先,这些链接帮助了我:
https://groups.google.com/forum/!主题/google-guice h70a9pwD6_g
https://groups.google.com/forum/!主题/google-guice yhEBKIHpNqY
用Multibinding泛化guide 's机器人腿示例
基本思想如下。首先,我们创建一个从名称到类的映射。这应该通过手动反射来完成,因为你的类名是由配置文件中的字符串定义的,但是Guice需要Class
对象(至少)来建立绑定。
接下来,我们遍历这个映射,并为每个通信name -> class
安装一个私有模块,该模块将带有一些绑定注释的String
绑定到name
。它还用一些独特的注释将MyInterface
绑定到类class
。然后它公开class
的绑定,该绑定通过外部模块中的Multibinder
添加到集合中。
此方法允许自动解析类的依赖关系,并提供设置每个对象名称的通用方法。
更新:这里是代码:https://github.com/dpx-infinity/guice-multibindings-private-modules