spring 中的动态依赖注入
Dynamic dependency injection in spring
我正在寻找一种创建非单例 spring bean 的方法,它可以 "partially" 自动装配。
@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;
}
}
我想要这样的东西,因为有时依赖关系只在运行时才知道。
我想到了一种方法,就是创建一种创建静态成员 class 的工厂,这样我就可以测试静态成员 class:
@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 way"做这样的事情
谢谢。
Update:
I have found another solution and post an answer at another post:
你的基本方法是使用由 Spring 注入的工厂,然后公开一个创建 Example
实例的方法,这就是我的做法,所以它基本上是正确的。如果你想让 Spring 透明地处理这个问题,使用它的现代特性,你可以使用 @Configuration
class in combination with lookup method injection 从单例范围的 beans 按需创建 Example
的实例。
一、配置class:
@Configuration
public class DemoConfiguration {
@Autowired IFooBean fooBean;
@Autowired IBarBean barBean;
@Bean()
@Scope("prototype")
Example newExample(String name) {
return new Example(fooBean, barBean, name);
}
}
除了 newExample
的 name
参数外,这里应该没有什么太令人惊讶的了。您可以像我上面所做的那样自动装配容器可以满足的依赖项(fooBean
和 barBean
),但是由于配置实例 classes 像任何其他 bean 一样由 Spring 创建,您还可以使用任何其他机制:将 ObjectFactory
或 ObjectProvider
注入配置,让它实现 ApplicationContextAware
,甚至为它们使用查找方法注入。如果您需要避免 fooBean
和 barBean
像自动装配到配置 bean 中那样被提前初始化,这将很有用。
不要忘记将工厂方法的范围设置为 "prototype"
,否则 Spring 将只是 return 您创建的第一个 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
和扫描,这必须是具体的 class,这意味着我们需要 getExample()
的虚拟实现; Spring 将使用 CGLIB 将其替换为对上面 DemoConfiguration
中定义的工厂方法的调用。 Spring 将正确地将参数从查找方法传递到工厂方法。
出于测试目的,我只是用 name
的不同值调用 getExample()
两次,以证明我们得到了一个不同的实例,每次都注入了正确的东西。
使用以下小型 Spring 启动应用程序进行测试:
@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$b994e8@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$b994e8@6c345c5f.doThing(getExample() = com.example.demo.Example@6b5966e1)
即:
- 创建了一个
FooBean
- 创建了一个
BarBean
Example
是用上面的两个 bean 和 name
创建的
- 此
Example
return 编辑为 UseExample
- 创建了一个不同的
Example
,具有相同的 FooBean
和 BarBean
,这次 name
设置为 "bbb"
。
我假设您熟悉如何设置基于 java 的配置和组件扫描以及上述示例所依赖的所有其他管道。我使用 Spring Boot 以一种简单的方式获得整个 shebang。
如果您从其他原型范围的 bean 创建 Example
s,可能有一种方法可以通过范围传递仅运行时依赖项的值,但我什至不知道从哪里开始回答如何做到这一点,尤其是在不知道 bean 的实际范围以及它们如何相互关联的情况下。无论哪种方式,上述解决方案似乎都更简单易懂。
我正在寻找一种创建非单例 spring bean 的方法,它可以 "partially" 自动装配。
@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;
}
}
我想要这样的东西,因为有时依赖关系只在运行时才知道。 我想到了一种方法,就是创建一种创建静态成员 class 的工厂,这样我就可以测试静态成员 class:
@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 way"做这样的事情
谢谢。
Update: I have found another solution and post an answer at another post:
你的基本方法是使用由 Spring 注入的工厂,然后公开一个创建 Example
实例的方法,这就是我的做法,所以它基本上是正确的。如果你想让 Spring 透明地处理这个问题,使用它的现代特性,你可以使用 @Configuration
class in combination with lookup method injection 从单例范围的 beans 按需创建 Example
的实例。
一、配置class:
@Configuration
public class DemoConfiguration {
@Autowired IFooBean fooBean;
@Autowired IBarBean barBean;
@Bean()
@Scope("prototype")
Example newExample(String name) {
return new Example(fooBean, barBean, name);
}
}
除了 newExample
的 name
参数外,这里应该没有什么太令人惊讶的了。您可以像我上面所做的那样自动装配容器可以满足的依赖项(fooBean
和 barBean
),但是由于配置实例 classes 像任何其他 bean 一样由 Spring 创建,您还可以使用任何其他机制:将 ObjectFactory
或 ObjectProvider
注入配置,让它实现 ApplicationContextAware
,甚至为它们使用查找方法注入。如果您需要避免 fooBean
和 barBean
像自动装配到配置 bean 中那样被提前初始化,这将很有用。
不要忘记将工厂方法的范围设置为 "prototype"
,否则 Spring 将只是 return 您创建的第一个 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
和扫描,这必须是具体的 class,这意味着我们需要 getExample()
的虚拟实现; Spring 将使用 CGLIB 将其替换为对上面 DemoConfiguration
中定义的工厂方法的调用。 Spring 将正确地将参数从查找方法传递到工厂方法。
出于测试目的,我只是用 name
的不同值调用 getExample()
两次,以证明我们得到了一个不同的实例,每次都注入了正确的东西。
使用以下小型 Spring 启动应用程序进行测试:
@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$b994e8@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$b994e8@6c345c5f.doThing(getExample() = com.example.demo.Example@6b5966e1)
即:
- 创建了一个
FooBean
- 创建了一个
BarBean
Example
是用上面的两个 bean 和name
创建的
- 此
Example
return 编辑为UseExample
- 创建了一个不同的
Example
,具有相同的FooBean
和BarBean
,这次name
设置为"bbb"
。
我假设您熟悉如何设置基于 java 的配置和组件扫描以及上述示例所依赖的所有其他管道。我使用 Spring Boot 以一种简单的方式获得整个 shebang。
如果您从其他原型范围的 bean 创建 Example
s,可能有一种方法可以通过范围传递仅运行时依赖项的值,但我什至不知道从哪里开始回答如何做到这一点,尤其是在不知道 bean 的实际范围以及它们如何相互关联的情况下。无论哪种方式,上述解决方案似乎都更简单易懂。