有状态依赖和线程安全

时间:2011-12-16 13:29:16

标签: java unit-testing thread-safety factory

我有Struts 1动作类(动作是struts 1中设计的单例),需要收集一些数据,然后将它们全部合并为单个响应。 我想将所有响应生成逻辑提取到单独的类

ResponseBuilder

通常我会将ResponseBuilder作为一个字段并为其设置(例如用于测试)。 我的响应构建器如下所示

class JsonResponseBuilder implements ResponseBuilder {
    public void addElement(String key, Object value) {
        ...
    }

    public String buildResponse() {
        // build response from data collected
    }
}

由于这里的线程安全问题,我不能这样做。

如何更改此设计?这里的工厂模式是否适用? 我的意思是使用

ResponseBuilderFactory

作为依赖并以这种方式调用:

ResponseBuilder builder = factory.getBuilder();
builder.addElement(...);
...
String response = builder.build();
从设计和可测试性的角度来看,

是否正常? 如果还可以。如何为此编写测试代码?模拟工厂?模拟建造者?

4 个答案:

答案 0 :(得分:2)

工厂会工作。你实际上是在做一个带工厂的依赖注入形式。当您实例化或初始化您的操作时,您将设置工厂:

public void setResponseFactory(ResponseBuilderFactory factory) {
    this.responseFactory = factory;
}

并且在工厂中,只需在需要时返回JsonResponseBuilder的新实例。确保您不将JsonResponseBuilder的实例存储为操作中的实例变量;它必须保持在您正在使用的方法的本地,或作为方法参数传递。

对于测试,使用返回模拟ResponseBuilder的模拟工厂替换工厂变得容易。有许多库可以执行此操作,例如MockitoJMock。所有这些都适用于JUnit和TestNG。

修改

您需要将ResponseBuilderFactory作为接口,例如:

public interface ResponseBuilderFactory {
    public ResponseBuilder getResponseBuilder();
}

进行测试时,只需创建一个返回ResponseBuilder模拟的类:

@Test
public void testMyAction() throws Exception {
    ResponseBuilderFactory mockFactory = new ResponseBuilderFactory() {
        public ResponseBuilder getResponseBuilder() {
            ResponseBuilder builder = context.mock(ResponseBuilder.class);
            // set up mock behaviour
            return builder;
        }
    }
}

所以你不是注入一个模拟工厂,只是一个返回模拟的工厂。

另请参阅Dependency Injection vs Factory Pattern

编辑2:

如果你能以某种方式设法重新编码你的JsonResponseBuilder以便它不维持状态,那么你可以潜在地避免整个工厂混乱,只使用你原来的方法。不维护状态的对象本质上是线程安全的。

答案 1 :(得分:2)

只需在您的操作服务方法中实例化您的ResponseBuilder即可。这样它将是一个局部变量(而不是单例类成员),每个线程将有一个独特的ResponseBuilder新实例。

您可以单独对ResponseBuilder进行单元测试,也可以像其他任何操作一样正常测试您的操作(如果需要,可以模拟ResponseBuilder)。

使用工厂模式构建您的ResponseBuilder是另一回事,与您的问题无关,imo。如果实例化ResponseBuilders是昂贵的并且它们是可重用的,那么您可能希望使用ResponseBuilders的

答案 2 :(得分:1)

您将ResponseBuilder作为Action中的成员变量的那一刻,它将在所有请求中共享。这意味着您必须同步它以确保其可变状态(如果有)是线程安全的。

另一种方法是在为每个操作调用的方法中创建一个新的ResponseBuilder。您可以为每个请求创建一个实例。

答案 3 :(得分:1)

您可以在会话中存储“响应”构建器。通过为其设置getter / setter使其显示为Action类的“属性”,但不定义实际字段。而是做类似的事情:

protected ResponseBuilder getResponseBuilder() {
    ResponseBuilder builder = (ResponseBuilder) session.getAttribute("ATTR_RESPONSE_BUILDER");
    if(builder == null) {
        builder = new ResponseBuilder();
        session.setAttribute("ATTR_RESPONSE_BUILDER", builder);
    }

    return builder;
}

protected void setResponseBuilder(ResponseBuilder builder) {
    session.setAttribute("ATTR_RESPONSE_BUILDER", builder);
}

您需要在对Action类的调用之间清除构建器。