【问题标题】:Dynamic dependency injection in springSpring中的动态依赖注入
【发布时间】:2018-07-01 22:17:50
【问题描述】:

我一直在寻找一种方法来创建可以“部分”自动装配的非单件 spring bean。

@Component
class Example {
    private SpringBean1 bean1;
    private SpringBean2 bean2;
    private String dynamicDependancy;

    @Autowired
    public Example(SpringBean1 bean1, SpringBean2 bean2, String dynamicDepedency) {
        this.bean1 = bean1;
        this.bean2 = bean2;
        this.dynamicDepedency = dynamicDepedency;
    }
}

我想要这样的东西,因为有时依赖关系仅在运行时才知道。 我想到了一种方法是创建一种创建静态成员类的工厂,这样我就可以测试静态成员类:

@Component
class ExampleFactory {
    private SpringBean1 bean1;
    private SpringBean2 bean2;

    @Autowired
    public ExampleFactory(SpringBean1 bean1, SpringBean2 bean2) {
        this.bean1 = bean1;
        this.bean2 = bean2;
    }

    public Example from(String dynamicDependency) {
        return new Example(bean1, bean2, dynamicDependency);
    }

    static class Example {
        private SpringBean1 bean1;
        private SpringBean2 bean2;
        private String dynamicDependancy;

        public Example(SpringBean1 bean1, SpringBean2 bean2, String 
            dynamicDependancy) {
            this.bean1 = bean1;
            this.bean2 = bean2;
            this.dynamicDependancy = dynamicDependancy;
        }
    }
}

我正在寻找有关 Prototype 范围的信息,并且使用 getBean(java.lang.String, java.lang.Object) 使得使用依赖注入变得更加困难。 我想知道是否有任何“春天的方式”来做这些事情。

谢谢。

更新: 我找到了另一个解决方案并在另一个帖子中发布了答案: https://stackoverflow.com/a/52021965/2580829

【问题讨论】:

  • ExampleFactory 可以是 ApplicationContextAware,然后您可以从应用上下文中获取您的 bean,而不是将字符串传递给 Example ctor。
  • 谢谢。在那些情况下,将这种依赖注入其他组件的正确方法是什么?单例呢(可能需要确定它的具体值)
  • 我不确定这样的动态应用上下文是否有标准模式。如果您无法在初始化时解决它的依赖关系,那么将 Example 注入到单例中并没有真正的意义。我的猜测是“很多工厂”来隐藏/本地化动态初始化(工厂可以是单例)就可以了。如果可能,枚举示例的可能实例,将它们全部放在您的应用程序上下文中可能会更简单/更干净。
  • 在我看来,通过创建注入对象的自动装配工厂来提出的建议基本上是正确的。您让 Spring 处理“连接时”依赖项,将它们隐藏在接口后面,并且只需要创建 Examples 的运行时代码来处理字符串参数。
  • 您也可以使用lookup-method/@Lookup 让Spring 自动更改依赖类中的方法以调用该工厂。稍后我将对此进行讨论以使其成为一个完整的答案,自从我学习 Java 以来,这是一个热门的时刻。

标签: java spring dependency-injection autowired spring-framework-beans


【解决方案1】:

您使用 Spring 注入的工厂的基本方法,然后公开创建 Example 实例的方法是我的做法,所以它基本上是正确的。如果你想让 Spring 透明地处理这件事,使用它的现代特性,你可以使用 @Configuration class 结合 lookup method injection 从单例范围的 bean 按需创建 Example 的实例。


一、配置类:

@Configuration
public class DemoConfiguration {
    @Autowired IFooBean fooBean;
    @Autowired IBarBean barBean;

    @Bean()
    @Scope("prototype")
    Example newExample(String name) {
        return new Example(fooBean, barBean, name);
    }
}

除了newExamplename 参数之外,这里应该没有什么太令人惊讶的地方。您可以像我上面所做的那样自动装配容器可以满足的依赖项(fooBeanbarBean),但是由于配置类的实例是像 Spring 的任何其他 bean 一样创建的,因此您还可以使用任何其他机制:注入 @987654331 @ 或ObjectProvider 到配置中,让它实现ApplicationContextAware,甚至为它们使用查找方法注入。如果您需要避免 fooBeanbarBean 像自动装配到配置 bean 中那样被提前初始化,这将非常有用。

不要忘记将工厂方法的范围设置为"prototype",否则即使您为name 传入不同的值,Spring 也只会返回您创建的第一个bean。


Example 本身的实现类似于您的问题:

public class Example {
    IFooBean fooBean;
    IBarBean barBean;
    String name;

    public Example(IFooBean fooBean, IBarBean barBean, String name) {
        System.out.printf("%s(fooBean=%s, barBean=%s, name=%s)\n", this, fooBean, barBean, name);
        this.fooBean = fooBean;
        this.barBean = barBean;
        this.name = name;
    }
}

然后,在您实际需要Example 的实例时,您使用@Lookup 注入工厂方法:

public interface IUsesExample {
    void doThing();
}

@Component
public class UsesExample implements IUsesExample {
    @Lookup
    protected Example getExample(String name) {return null;};

    public void doThing() {
        System.out.printf("%s.doThing(getExample() = %s)\n", this, getExample("aaa"));
        System.out.printf("%s.doThing(getExample() = %s)\n", this, getExample("bbb"));
    }
}

要使用@Component 和扫描,这必须是一个具体的类,这意味着我们需要一个getExample() 的虚拟实现; Spring 将使用 CGLIB 将其替换为对上面DemoConfiguration 中定义的工厂方法的调用。 Spring 会正确地将参数从查找方法传递到工厂方法。

出于测试目的,我只需调用 getExample() 两次,并为 name 提供不同的值,以证明我们每次都获得了一个不同的实例,其中注入了正确的东西。


使用以下小型 Spring Boot 应用程序对此进行测试:

@SpringBootApplication
public class DemoApplication {
    @Autowired IUsesExample usesExample;

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @PostConstruct
    void run() {
        usesExample.doThing();
    }
}

给出以下输出:

com.example.demo.FooBean@fd46303
com.example.demo.BarBean@6a62689d
com.example.demo.Example@66629f63(fooBean=com.example.demo.FooBean@fd46303, barBean=com.example.demo.BarBean@6a62689d, name=aaa)
com.example.demo.UsesExample$$EnhancerBySpringCGLIB$$68b994e8@6c345c5f.doThing(getExample() = com.example.demo.Example@66629f63)
com.example.demo.Example@6b5966e1(fooBean=com.example.demo.FooBean@fd46303, barBean=com.example.demo.BarBean@6a62689d, name=bbb)
com.example.demo.UsesExample$$EnhancerBySpringCGLIB$$68b994e8@6c345c5f.doThing(getExample() = com.example.demo.Example@6b5966e1)

即:

  • FooBean 被创建
  • BarBean 被创建
  • Example 使用上述两个 bean 和 name 创建
  • 这个Example被返回给UseExample
  • 创建了一个不同的Example,同时将相同的FooBeanBarBean 设置为name,这次设置为"bbb"

我假设您熟悉如何设置基于 java 的配置和组件扫描以及上述示例所依赖的所有其他管道。我使用 Spring Boot 以一种简单的方式获得了整个 shebang。

如果您从其他原型范围的 bean 创建 Examples 可能有一种方法可以通过范围传递仅运行时依赖项的值,但我什至不知道从哪里开始回答如何做尤其是在不知道 bean 的实际范围以及它们如何相互关联的情况下。无论哪种方式,上述解决方案似乎都更简单易懂。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-04-08
    • 1970-01-01
    • 1970-01-01
    • 2015-10-04
    • 1970-01-01
    • 2017-01-04
    • 1970-01-01
    相关资源
    最近更新 更多