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

使用Spring的Java配置,我需要获取/实例化一个原型范围的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;
    }
}

注意是:它只能通过构造函数提供,并保证不可变性。其他依赖项是类的特定于实现的依赖项,不应为请求处理程序实现所知(紧密耦合)。namefinalThing

此代码与Spring XML配置配合得很好,例如:

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

我如何用Java配置实现同样的事情?以下操作在使用 Spring 3.x 时不起作用:

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

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

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

但这违背了使用Spring取代ServiceLocator和Factory设计模式的全部意义,而这种模式非常适合这个用例。

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

  • 定义工厂接口
  • 定义工厂实现
  • 为工厂实现编写测试

对于Spring已经通过XML配置支持的东西来说,这是一项艰巨的工作(相对而言)。


答案 1

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

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

用于注册 Bean 定义并提供用于创建 Bean 的工厂。它定义的 Bean 仅在请求时使用直接或通过扫描确定的参数进行实例化。ApplicationContext

对于 Bean,每次都会创建一个新对象,因此也会执行相应的方法。prototype@Bean

你可以通过它的BeanFactory#getBean(字符串名称,对象...args) 方法,其中状态ApplicationContext

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

参数:

在使用静态工厂方法的显式参数创建原型时要使用的 args 参数。在任何其他情况下使用非空参数值都是无效的。

换句话说,对于此作用域 Bean,您提供的参数不是在 Bean 类的构造函数中,而是在方法调用中。(此方法具有非常弱的类型保证,因为它使用 Bean 的名称查找。prototype@Bean

或者,您可以使用类型化的BeanFactory#getBean(类必需类型,对象...args) 方法,按类型查找 Bean。

这至少适用于春季版本4 +。

请注意,如果您不想从 或 开始进行 Bean 检索,则可以注入 ObjectProvider(从 Spring 4.3 开始)。ApplicationContextBeanFactory

专为注入点设计的变体,允许编程可选性和宽松的非唯一处理。ObjectFactory

并使用其 getObject(Object...args) 方法

返回此工厂管理的对象的实例(可能共享或独立)。

允许指定显式构造参数,如 行。BeanFactory.getBean(String, Object)

例如

@Autowired
private ObjectProvider<Thing> things;

[...]
Thing newThing = things.getObject(name);
[...]

答案 2

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

@Configuration    
public class ServiceConfig {

    @Bean
    public Function<String, Thing> thingFactory() {
        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> thingFactory;

public void onRequest(Request request) {
    //request is already validated
    String name = request.getParameter("name");
    Thing thing = thingFactory.apply(name);

    // ...
}

所以现在你可以在运行时得到你的bean。这当然是一个工厂模式,但是你可以节省一些时间来编写特定的类(但是你必须编写自定义来传递两个以上的参数)。ThingFactory@FunctionalInterface