【问题标题】:Dynamically typed objects created with dependency injection使用依赖注入创建的动态类型对象
【发布时间】:2013-06-18 13:46:51
【问题描述】:

我有一段使用反射工作的现有代码,但如果可能的话,我想开始使用依赖注入和 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" 不会创建任何新实例,因为它没有匹配的模式。

这是它目前的工作方式(这是我能做的最好的sscce-wise),使用反射:

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

【问题讨论】:

    标签: java reflection guice


    【解决方案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 和上述内容来实现您想要的,但在您的情况下,这很快就会变得笨拙。但是,如果您将使用私有模块,则可能不需要使用反射。

    【讨论】:

    • 具有讽刺意味的是,在将我的场景转换为 sscce 时,我消除了我遇到的主要问题,即我希望能够将 其他不相关的依赖项注入FooBar 等的构造函数,理论上如果 Guice 正在创建对象,我什至不需要知道它们在数据馈送步骤中是什么。换句话说,我不需要FooBar注入东西,但我确实需要能够向它们注入东西。跨度>
    • @durron597 好的,这很简单。我会在几分钟后扩展我的答案。
    • 嗯,在某种程度上简单。现在我不确定我是否能想出最通用的解决方案,但仍然可以。
    • 我赞成你的努力,但这仍然不理想(需要依赖知识),并直接使用反射创建类。但是,您的帖子和我的一些研究激发了我创建自己的答案,让我知道您的想法。
    【解决方案2】:

    经过进一步思考,我开始怀疑我是否应该少关心将运行时参数传递给构造函数,而多关心使用 create and configure concept mentioned in this answer. 下面的示例没有错误检查,但实际的实现版本会抛出很多 @ 987654322@s 和 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. 要么我需要能够在添加插件绑定之前读取我的配置文件,要么在代码中定义它们。

    【讨论】:

    • 在这种情况下如何使用InterfaceFactory?从最初的问题中,我认为您想以某种方式从类的真实名称中抽象出来,而在这段代码中,您似乎必须通过 configure() 方法显式命名它们。
    • 顺便说一句,我看不到您如何将 String 参数提供给 Foo/Bar 构造函数。如果他们在构造函数中接受String,您的代码将失败,因为您的模块中没有String 类的绑定。我仍然认为私有模块将是合适的解决方案;我现在正在处理这个例子。
    • @VladimirMatveev 这就是我的意思,我将更改 FooBar 以从构造函数中删除 String 并改用 configure 方法。这并不理想,这就是 Con #1 所说的。对于objects=name,pattern;name,pattern;... 等中的每个条目,我都会有多个InterfaceFactory 实例。
    【解决方案3】:

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

    我似乎能够使用多绑定器和私有模块实现您所需要的。

    首先,这些是帮助我的链接:
    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 -&gt; class 安装一个私有模块,该模块将带有一些绑定注释注释的 String 绑定到 name。它还将MyInterface 与一些独特的注释绑定到类class。然后它暴露了class 的这个绑定,它通过外部模块中的Multibinder 添加到集合中。

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

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

    【讨论】:

    • 我没有完全关注你,但如果我理解正确,你应该知道输入数据在应用程序启动时不可用;它只有在应用程序完成建立网络连接后才可用。
    • @durron597,如果这发生在注入器创建之前,那没关系。
    • 如果没有,您可以随时创建子注射器 :)
    猜你喜欢
    • 2015-01-22
    • 2017-10-18
    • 2020-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多