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

时间:2013-06-18 13:46:52

标签: java reflection guice

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

以下是目前的工作原理:

  1. 加载配置(.properties)文件,其中包含类似的字符串
    • objects=Foo,^ab..$;Bar,^.bc.$;Baz,i*
    • 注意:FooBarBaz是实施MyInterface
    • 的类
    • 每对都有一个与之配对的正则表达式。
  2. 输入数据来自其他来源。想象一下这个例子,数据是:
    • String[]{ "abab", "abcd", "dbca", "fghi", "jklm" }
  3. 然后,我想要创建由Guice创建的FooBarBaz的新实例。
    • 在这种情况下,创建的实例将是:
      • new Foo("abab");
      • new Foo("abcd");
      • new Bar("abcd");
      • new Bar("dbca");
      • new Baz("fghi");
      • "jklm"不会创建任何新实例,因为它没有匹配的模式。
  4. 以下是它当前的工作原理(这是我能做的最好的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复制此功能(在运行时动态加载类,并创建完全匹配的实例)?

3 个答案:

答案 0 :(得分:1)

我很确定你不能在没有任何反思的情况下做到这一点,只使用Guice。这是因为Guice不是为这些东西而制作的。 Guice的任务是帮助进行依赖关系管理,而不是创建对象的不同策略(好吧,在某种程度上它是,但不是你需要的那么多)。

但是,如果您需要使用使用文件中的信息创建的对象作为其他对象的依赖项,则可以执行此操作。只需将您的对象预加载到某种地图中,我想这样的事情就可以了:

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来完成这项任务。

更新(考虑评论)

好的,您想要注入对象的依赖项。

如果必须通过构造函数将关键字符串提供给对象,那么最简单的方法是使用方法/字段注入。这样整个过程看起来就像这样。首先,您像往常一样创建对象,然后在循环中使用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);

然后像往常一样注射它。

但是,这种方法要求您事先知道您的类具有哪些依赖关系。

如果您事先并不知道您的班级有哪些依赖关系,那么我认为您可以使用private modules以及上述内容实现您想要的目标,但在您的情况下,这很快就会变得难以处理。但是,如果您使用私有模块,则可能不需要使用反射。

答案 1 :(得分:0)

经过进一步反思,我开始怀疑我是否应该更少关心将运行时参数传递给构造函数以及更多关于使用create and configure concept mentioned in this answer.的问题。下面的示例没有错误检查,但实际的实现版本会抛出很多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. 我不需要任何依赖性知识
  4. <强>缺点:

    1. 我必须编写我的类才能知道如果在没有配置的情况下创建它们该怎么做
    2. 我需要能够在添加插件绑定之前读取我的配置文件,或者在代码中定义它们。

答案 2 :(得分:0)

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

我能够使用multibinder和私有模块实现您所需的功能。

首先,这些是帮助我的链接:
https://groups.google.com/forum/#!topic/google-guice/h70a9pwD6_g
https://groups.google.com/forum/#!topic/google-guice/yhEBKIHpNqY
Generalize guice's robot-legs example with Multibinding

基本思路如下。首先,我们创建从名称到类的映射。这应该通过手动反射来完成,因为你的类名是由配置文件中的字符串定义的,但是Guice需要Class个对象(至少)来建立绑定。

接下来,我们遍历此映射,并为每个对应关系name -> class安装一个私有模块,该模块将带有注释的String绑定到name。它还将MyInterface与类class的一些唯一注释绑定在一起。然后它公开了class的这个绑定,它通过外部模块中的Multibinder添加到集合中。

此方法允许自动解决类依赖关系,并提供设置每个对象名称的通用方法。

更新:这里是代码:https://github.com/dpx-infinity/guice-multibindings-private-modules