Java 配置模拟 XML 配置不起作用

A Java config analog of XML configuration not working

TL/DR:问题归结为创建自定义 Spring 作用域,将类似 prototype 的作用域 bean 注入具有 proxyMode = ScopedProxyMode.TARGET_CLASS 的单例中,但仍然得到一个配置的 Java 配置版本中的单例(而它在 XML 中工作正常)。

更新:问题已解决,查看答案。


我正在使用 jBehave 为我们的 Spring 应用程序编写 BDD 测试场景。我们最近认为我们需要独立执行测试场景(这意味着必须在每个场景之前重置测试上下文)并在网络上找到 this 文章准确地解决了我们正在处理的问题。

文章建议创建自定义 Spring Scenario 范围,将其分配给表示测试上下文的 class 并注入 AOP 代理而不是上下文文件。

我已经根据文章对所有内容进行了编码并且效果很好,但问题是我们在 Java 配置方面需要它,而不是 XML,当我转换所有更改 Java 配置,它停止工作 - 这意味着 StoryContext 中的 Map 在每个测试场景后都没有重置,并且包含前一个场景的值。

我的修改如下:

@Component
public class ScenarioScope implements Scope {

    private final ConcurrentMap<String, Object> cache = new ConcurrentHashMap<>();

    @BeforeScenario
    public void startScenario() {
        cache.clear();
    }

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        return cache.putIfAbsent(name, objectFactory.getObject());
    }

    @Override
    public Object remove(String name) {
        return cache.remove(name);
    }

    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
    }

    @Override
    public Object resolveContextualObject(String key) {
        return null;
    }

    @Override
    public String getConversationId() {
        return "scenario scope";
    }
}
@Configuration
public class SpringConfiguration {

    @Bean
    public static CustomScopeConfigurer scopeConfigurer() {
        CustomScopeConfigurer configurer = new CustomScopeConfigurer();
        configurer.addScope("scenario", new ScenarioScope());
        return configurer;
    }
}
@Component
@Scope(value = "scenario", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class StoryContext {

  private Map<String, Object> storyContext = new HashMap<>();

  public void put(String key, Object value) {
    storyContext.put(key,value);
  }

  public <T> T get(String key, Class<T> tClass) {
    return (T) storyContext.get(key);
  }

  @PostConstruct
  public void clearContext() {
    storyContext.clear();
  }
}

据我所知,上面的代码类似于 XML 配置,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation=" http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <context:annotation-config />
    <context:component-scan base-package="foo"/>

    <bean id="scenarioScope" class="foo.ScenarioScope"/>

    <bean class="foo.CustomScopeConfigurer">
        <property name="scopes">
            <map>
                <entry key="scenario" value-ref="scenarioScope"/>
            </map>
        </property>
    </bean>

    <bean id="storyContext" class="foo.StoryContext" scope="scenario">
        <aop:scoped-proxy/>
    </bean>
</beans>

谁能告诉我为什么 Java 配置没有按预期工作?我花了一些时间研究 Whosebug,但大多数类似的问题都是通过将 proxyMode = ScopedProxyMode.TARGET_CLASS 添加到 @Scope 注释来解决的,我这样做了。

更新: 所以我尝试通过注释/反注释文件中的相应行逐渐从 XML 移动到 Java 配置,并发现问题出在这部分代码中:

    <bean class="foo.CustomScopeConfigurer">
        <property name="scopes">
            <map>
                <entry key="scenario" value-ref="scenarioScope"/>
            </map>
        </property>
    </bean>

当我把它替换成

@Configuration
public class SpringConfiguration {

    @Bean
    public static CustomScopeConfigurer scopeConfigurer() {
        CustomScopeConfigurer configurer = new CustomScopeConfigurer();
        configurer.addScope("scenario", new ScenarioScope());
        return configurer;
    }
}

StoryContext bean 成为单例。我尝试通过注册自定义 BeanFactoryPostProcessor 并使用 here 中描述的 registerScope() 方法来实现它,但它也没有用。

我已经设法解决了问题,解决方案很简单:SpringConfiguration class 中的 ScenarioScope 实例必须由 Spring 管理容器而不是通过 new() 运算符创建:

@Configuration
public class SpringConfiguration {

    @Bean
    public static CustomScopeConfigurer scopeConfigurer(ScenarioScope scenarioScope) {
        CustomScopeConfigurer configurer = new CustomScopeConfigurer();
        configurer.addScope("scenario", scenarioScope);
        return configurer;
    }
}