春季动态依赖注入

时间:2018-07-01 22:17:50

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

我一直在寻找一种创建可以“部分”自动接线的非单簧豆的方法。

@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)使得使用依赖注入变得更加困难。 我想知道是否有“春天的方式”来做这些事情。

谢谢。

  

更新:   我发现一个不错的帖子对我有很大帮助:   Spring Java Config: how do you create a prototype-scoped @Bean with runtime arguments?

     

Update2:   在Spring 4.3.0中引入的显式解决方案:ObjectProvider   https://docs.spring.io/autorepo/docs/spring-framework/5.0.8.RELEASE/javadoc-api/org/springframework/beans/factory/ObjectProvider.html

1 个答案:

答案 0 :(得分:2)

您将使用Spring注入的工厂的基本方法,然后使用该方法公开创建Example实例的方法,因此,它基本上是正确的。如果您希望Spring使用其现代功能透明地进行此操作,则可以将@Configuration classlookup method injection结合使用,以按单例需求创建Example的实例。范围豆。


首先,配置类:

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

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

除了name的{​​{1}}参数以外,这里没有什么令人惊讶的。您可以像我上面那样自动装配容器可以满足的依赖关系(newExamplefooBean,但是由于Spring像其他任何bean一样创建了配置类的实例,因此您还可以使用任何其他机制:将barBeanObjectFactory注入配置,使其实现ObjectProvider,甚至对其使用查找方法注入。如果您需要避免ApplicationContextAwarefooBean像被自动装配到配置bean中一样被初始化,这将很有用。

不要忘记将factory方法的范围设置为barBean,否则即使您为"prototype"传递了一个不同的值,Spring也会返回您使用创建的第一个bean。


name本身的实现类似于您所提出的问题:

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的虚拟实现; Spring将使用CGLIB将其替换为对以上getExample()中定义的factory方法的调用。 Spring会将参数正确地从查找方法传递给工厂方法。

出于测试目的,我只用DemoConfiguration的不同值两次调用getExample(),以证明我们得到的是另一个实例,每次都注入了正确的东西。


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

name

给出以下输出:

@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
  • 使用上述两个bean和BarBean创建一个Example
  • name返回到Example
  • 创建了一个不同的UseExample,具有相同的ExampleFooBean,并且这次将BarBean设置为name

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

如果要从其他原型作用域的bean创建"bbb",可能有一种方法可以通过作用域传递仅用于运行时的依赖项,但是我不知道从哪里开始回答如何为此,尤其是在不知道bean的实际范围以及它们如何相互关联的情况下。无论哪种方式,上述解决方案似乎都更加直接和易于理解。