使用依赖注入创建的动态类型对象



我有一段使用反射工作的现有代码,但如果可能的话,我想开始使用依赖注入和Guice创建对象。

现在是这样的:

    加载
  1. Configuration (.properties)文件,格式为字符串
    • objects=Foo,^ab..$;Bar,^.bc.$;Baz,i*
    • 注:FooBarBaz是实现MyInterface
    • 的类
    • 每对都有一个正则表达式与之配对。
  2. 从另一个来源输入数据。想象一下,对于这个例子,数据是:
    • String[]{ "abab", "abcd", "dbca", "fghi", "jklm" }
  3. 然后我想创建由Guice创建的Foo, BarBaz的新实例。
    • 在这种情况下,创建的实例将是:
      • 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);

,然后照常注射。

然而,这种方法要求你事先知道你的类有哪些依赖关系。

如果你事先不知道你的类有哪些依赖关系,那么我认为你可以使用私有模块和上面的一些东西来实现你想要的,但在你的情况下,这很快就会变得笨拙。但是,如果您使用私有模块,则可能不需要使用反射。

经过进一步的思考,我开始怀疑我是否应该少关心将运行时参数传递给构造函数,而更多地关注使用这个答案中提到的创建和配置概念。下面的示例没有错误检查,但是实际的实现版本会抛出大量的NullPointerExceptionIllegalArgumentException来处理坏数据。但这里有一个想法:

基本上是这样的:

// 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);
    }
  }
}

这看起来比我现在用的干净多了。

优点:

  1. 使用Guice创建对象
  2. 反射被最小化和划分
  3. 我不需要任何依赖项的知识

缺点:

  1. 我必须写我的类,以便能够知道如何做,如果他们被创建没有配置
  2. 要么我需要能够在添加插件绑定之前读取我的配置文件,要么在代码中定义它们。

我在添加另一个答案,因为第一个已经太大了。

我能够实现看似你需要使用多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

相关内容

  • 没有找到相关文章

最新更新