Spring Java Config:如何用运行时参数创build一个原型范围的@Bean?

使用Spring的Javaconfiguration,我需要获取/实例化一个原型范围的bean,它具有只能在运行时获得的构造函数参数。 考虑下面的代码示例(简洁起见):

@Autowired private ApplicationContext appCtx; public void onRequest(Request request) { //request is already validated String name = request.getParameter("name"); Thing thing = appCtx.getBean(Thing.class, name); //System.out.println(thing.getName()); //prints name } 

Thing类定义如下:

 public class Thing { private final String name; @Autowired private SomeComponent someComponent; @Autowired private AnotherComponent anotherComponent; public Thing(String name) { this.name = name; } public String getName() { return this.name; } } 

注意namefinal :只能通过构造函数提供,并保证不变性。 其他依赖项是Thing类的特定于实现的依赖关系,不应该被认为(紧密耦合到)请求处理程序实现。

这段代码完全适用于Spring XMLconfiguration,例如:

 <bean id="thing", class="com.whatever.Thing" scope="prototype"> <!-- other post-instantiation properties omitted --> </bean> 

如何用Javaconfiguration实现同样的function? 以下不起作用:

 @Bean @Scope("prototype") public Thing thing(String name) { return new Thing(name); } 

现在,我可以创build一个工厂,例如:

 public interface ThingFactory { public Thing createThing(String name); } 

但是,这违背了使用Spring来取代ServiceLocator和Factorydevise模式的全部观点 ,这对于这个用例来说是非常理想的。

如果Spring Java Config可以做到这一点,我将能够避免:

  • 定义一个工厂界面
  • 定义一个工厂实现
  • 为工厂实施编写testing

对于那些微不足道的东西,Spring已经通过XMLconfiguration支持了大量的工作(相对而言)。

@Configuration类中,像这样的@Bean方法

 @Bean @Scope("prototype") public Thing thing(String name) { return new Thing(name); } 

用于注册一个bean定义并提供创buildbean的工厂 。 它定义的bean仅在请求时使用直接或通过扫描ApplicationContext确定的参数来实例化。

prototype bean的情况下,每次创build一个新的对象,因此相应的@Bean方法也被执行。

你可以通过它的BeanFactory#getBean(String name, Object... args)方法从ApplicationContext检索一个bean,

允许指定显式构造函数参数/工厂方法参数,覆盖bean定义中指定的缺省参数(如果有的话)。

参数:

如果使用静态工厂方法的显式参数创build原型,则使用args参数。 在其他情况下使用非空args值是无效的。

换句话说,对于这个prototype scoped bean,你提供了将被使用的参数,而不是在bean类的构造函数中,而是在@Bean方法调用中。

对于Spring版本4+,这至less是正确的。

每个评论更新

首先,我不确定为什么你说“这不起作用”,在Spring 3.x中可以正常工作。 我怀疑你的configuration某处肯定有问题。

这工作:

– configuration文件:

 @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(); } } 

– testing文件执行:

 @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,给出了这个输出:

 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被请求两次。 但是,正如我们所期望的那样,Spring只创build一次。 第二次看到它有这个bean,只是返回现有的对象。 构造函数(@Bean方法)不会被第二次调用。 在这种情况下,当两次从同一个上下文对象请求“Prototype”Bean时,我们看到输出中的引用更改以及构造函数(@Bean方法)被调用两次。

那么问题是如何将单例注入到原型中。 上面的configuration类显示了如何做到这一点! 您应该将所有这样的引用传递给构造函数。 这将允许创build的类成为一个纯粹的POJO,并使得包含的引用对象不可变。 所以传输服务可能如下所示:

 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; } .... } 

如果你写unit testing,你将永远很高兴你创build类没有所有的@Autowired。 如果你确实需要自动assembly的组件,保持那些本地的javaconfiguration文件。

这将在BeanFactory中调用下面的方法。 在说明中注意这是如何用于你的确切用例。

 /** * 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; 

使用Spring> 4.0和Java 8,您可以更安全地执行此操作:

 @Configuration public class ServiceConfig { @Bean public Function<String, Thing> thingFabric() { return name -> thing(name); // or this::thing } @Bean @Scope(value = "prototype") public Thing thing(String name) { return new Thing(name); } } 

用法:

 @Autowired private Function<String, Thing> thingFabric; public void onRequest(Request request) { //request is already validated String name = request.getParameter("name"); Thing thing = thingFabric.apply(name); // ... } 

所以现在你可以在运行时获得你的bean。 这当然是一种结构模式,但是您可以节省一些时间来编写像ThingFabric这样的特定类(但是您必须编写自定义的@FunctionalInterface来传递两个以上的参数)。