0

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

以下是它目前的工作方式:

  1. .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 复制此功能(在运行时动态加载类,并创建完全匹配的实例)?

4

3 回答 3

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

然后像往常一样注入它。

但是,这种方法需要您提前知道您的类有哪些依赖项。

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

于 2013-06-18T15:59:53.233 回答
0

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

使用 multibinder 和私有模块,我似乎能够实现您所需要的。

首先,这些是帮助我的链接:
https://groups.google.com/forum/#!topic/google-guice/h70a9pwD6_g
https://groups.google.com/forum/#!topic/google -guice/yhEBKIHpNqY 使用 Multibinding
泛化 guice 的机器人腿示例

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

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

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

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

于 2013-06-18T19:44:16.210 回答
0

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

基本上,它会是这样的:

// 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. 在添加插件绑定之前,我需要能够读取我的配置文件,或者在代码中定义它们。
于 2013-06-18T17:19:52.343 回答