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

新手上路,请多包涵

使用 Spring 的 Java Config,我需要获取/实例化一个原型范围的 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 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 配置支持的如此微不足道的事情,这是大量的工作(相对而言)。

原文由 Les Hazlewood 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 505
2 个回答

@Configuration 类中,一个 @Bean 方法就像这样

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

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

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

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

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

参数:

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

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

或者,您可以使用 typed BeanFactory#getBean(Class requiredType, Object... args) 按类型查找 bean 的方法。

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

请注意,如果您不想从 ApplicationContextBeanFactory 开始用于您的 bean 检索,您可以注入 ObjectProvider )。

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

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

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

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

例如,

 @Autowired
private ObjectProvider<Thing> things;

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

原文由 Sotirios Delimanolis 发布,翻译遵循 CC BY-SA 4.0 许可协议

使用 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 以传递两个以上的参数)。

原文由 Roman Golyshev 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题