Spring Java Config: how do you create a prototype-scoped @Bean with runtime arguments?
使用Spring的Java配置,我需要获取或实例化一个原型范围的bean,其中只有在运行时才可获得的构造函数参数。考虑下面的代码示例(为了简洁起见简化了代码示例):
1 2 3 4 5 6 7 8 9 10 |
其中,thing类定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
注意:
此代码与SpringXML配置完美配合,例如:
1 2 3 | <bean id="thing", class="com.whatever.Thing" scope="prototype"> <!-- other post-instantiation properties omitted --> </bean> |
如何用Java配置实现相同的事情?使用弹簧3.x时,以下各项不起作用:
1 2 3 4 5 |
现在,我可以创建一个工厂,例如:
1 2 3 |
但是,这就破坏了使用Spring来替换服务定位器和工厂设计模式的整个观点,这对于这个用例来说是理想的。
如果Spring JavaCONFIG能够做到这一点,我将能够避免:
- 定义工厂接口
- 定义工厂实现
- 为工厂实现编写测试
这是一项繁重的工作(相对而言),因为Spring已经通过XML配置支持了这些琐碎的工作。
在
1 2 3 4 5 |
用于注册bean定义并提供用于创建bean的工厂。它定义的bean仅在请求时使用直接确定的参数或通过扫描
对于
您可以通过它的
Allows for specifying explicit constructor arguments / factory method
arguments, overriding the specified default arguments (if any) in the
bean definition.Parameters:
args arguments to use if creating a prototype using explicit arguments
to a static factory method. It is invalid to use a non-null args value
in any other case.
换句话说,对于这个
对于Spring4+版本,这至少是正确的。
使用Spring>4和Java 8,您可以更安全地进行这类操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
用途:
1 2 3 4 5 6 7 8 9 10 |
所以现在您可以在运行时获取bean。当然,这是一个工厂模式,但是您可以节省一些时间来编写特定的类,如
按注释更新
首先,我不知道为什么你说"这不起作用",因为它在Spring3.x中工作得很好。我怀疑你在某个地方的配置肯定有问题。
这工作:
-配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | @Configuration public class ServiceConfig { // only here to demo execution order private int count = 1; @Bean @Scope(value ="prototype") public TransferService myFirstService(String param) { System.out.println("value of count:" + count++); return new TransferServiceImpl(aSingletonBean(), param); } @Bean public AccountRepository aSingletonBean() { System.out.println("value of count:" + count++); return new InMemoryAccountRepository(); } } |
--要执行的测试文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Test public void prototypeTest() { // create the spring container using the ServiceConfig @Configuration class ApplicationContext ctx = new AnnotationConfigApplicationContext(ServiceConfig.class); Object singleton = ctx.getBean("aSingletonBean"); System.out.println(singleton.toString()); singleton = ctx.getBean("aSingletonBean"); System.out.println(singleton.toString()); TransferService transferService = ctx.getBean("myFirstService","simulated Dynamic Parameter One"); System.out.println(transferService.toString()); transferService = ctx.getBean("myFirstService","simulated Dynamic Parameter Two"); System.out.println(transferService.toString()); } |
使用Spring 3.2.8和Java 7,给出了这个输出:
1 2 3 4 5 6 7 8 9 | value of count:1 com.spring3demo.account.repository.InMemoryAccountRepository@4da8692d com.spring3demo.account.repository.InMemoryAccountRepository@4da8692d value of count:2 Using name value of: simulated Dynamic Parameter One com.spring3demo.account.service.TransferServiceImpl@634d6f2c value of count:3 Using name value of: simulated Dynamic Parameter Two com.spring3demo.account.service.TransferServiceImpl@70bde4a2 |
所以"singleton"bean被请求两次。然而,正如我们所期望的,春天只创造了一次。第二次它看到它有那个bean并返回现有的对象。第二次未调用构造函数(@bean方法)。为此,当两次从同一个上下文对象请求"原型"bean时,我们会看到引用在输出中发生了更改,并且两次调用了构造函数(@bean method)。
那么问题是如何在原型中注入一个单体。上面的配置类也展示了如何做到这一点!您应该将所有这些引用传递到构造函数中。这将允许所创建的类是纯POJO,并使包含的引用对象保持不变。因此,传输服务可能看起来像:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class TransferServiceImpl implements TransferService { private final String name; private final AccountRepository accountRepository; public TransferServiceImpl(AccountRepository accountRepository, String name) { this.name = name; // system out here is only because this is a dumb test usage System.out.println("Using name value of:" + this.name); this.accountRepository = accountRepository; } .... } |
如果您编写单元测试,那么您将非常高兴在没有@autowired的情况下创建了这些类。如果您确实需要自动组件,请将这些本地组件保留到Java配置文件中。
这将在BeanFactory中调用下面的方法。在描述中注意这是如何用于您的确切用例的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /** * Return an instance, which may be shared or independent, of the specified bean. * <p> Allows for specifying explicit constructor arguments / factory method arguments, * overriding the specified default arguments (if any) in the bean definition. * @param name the name of the bean to retrieve * @param args arguments to use if creating a prototype using explicit arguments to a * static factory method. It is invalid to use a non-null args value in any other case. * @return an instance of the bean * @throws NoSuchBeanDefinitionException if there is no such bean definition * @throws BeanDefinitionStoreException if arguments have been given but * the affected bean isn't a prototype * @throws BeansException if the bean could not be created * @since 2.5 */ Object getBean(String name, Object... args) throws BeansException; |
从4.3年春开始,就有了新的方法来解决这个问题。
ObjectProvider-它允许您将其作为依赖项添加到"argumented"原型范围bean中,并使用参数实例化它。
下面是一个如何使用它的简单示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | @Configuration public class MyConf { @Bean @Scope(BeanDefinition.SCOPE_PROTOTYPE) public MyPrototype createPrototype(String arg) { return new MyPrototype(arg); } } public class MyPrototype { private String arg; public MyPrototype(String arg) { this.arg = arg; } public void action() { System.out.println(arg); } } @Component public class UsingMyPrototype { private ObjectProvider<MyPrototype> myPrototypeProvider; @Autowired public UsingMyPrototype(ObjectProvider<MyPrototype> myPrototypeProvider) { this.myPrototypeProvider = myPrototypeProvider; } public void usePrototype() { final MyPrototype myPrototype = myPrototypeProvider.getObject("hello"); myPrototype.action(); } } |
这当然会在调用usePrototype时打印hello字符串。
您可以通过使用内部类来实现类似的效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | @Component class ThingFactory { private final SomeBean someBean; ThingFactory(SomeBean someBean) { this.someBean = someBean; } Thing getInstance(String name) { return new Thing(name); } class Thing { private final String name; Thing(String name) { this.name = name; } void foo() { System.out.format("My name is %s and I can" + "access bean from outer class %s", name, someBean); } } } |