使用Spring注入资源的Builder模式

时间:2013-10-01 16:36:59

标签: java multithreading spring dependency-injection builder

我有一个 Builder ,它使用通过Spring注入的多个资源。它看起来与此相似:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class SandBoxBuilder {
    private final SandUtil sandUtil;
    private Sand sand;
    private Box box;

    @Autowired
    public SandBoxBuilder(SandUtil sandUtil) {
        this.sandUtil = sandUtil;
    }

    public SandBoxBuilder setSand(Sand sand) {
        this.sand = sand;
        return this;
    }

    public SandBoxBuilder setBox(Box box) {
        this.box = box;
        return this;
    }

    public SandBox build() {
        SandBox sandBox = new SandBox(sand);
        sandUtil.changeBox(sandBox, box);
        return sandBox;
    }
}

我遇到的问题是它不是线程安全的。我知道这个构建器不应该是单例,但我不知道如何使用弹簧注入资源(SandUtil)而不连接构建器并将其注入我使用它的位置。

如何实现利用spring注入的单例的线程安全构建器?

解决方案

由于一些架构限制,我无法将实用程序注入我的调用类。我最终实现了一个工厂构建器bean,它返回一个构建器的新实例,该构建器引用了spring资源。

解决方案实施

@Component
public class SandBoxBuilderFactory {
    private final SandUtil sandUtil;

    @Autowired
    public SandBoxBuilderFactory(SandUtil sandUtil) {
        this.sandUtil = sandUtil;
    }

    public Builder newBuilder(){
        return new Builder(sandUtil);
    }

    public static class Builder {
        private final SandUtil sandUtil;
        private Sand sand;
        private Box box;

        private Builder(SandUtil sandUtil) {
            this.sandUtil = sandUtil;
        }

        public Builder setSand(Sand sand) {
            this.sand = sand;
            return this;
        }

        public Builder setBox(Box box) {
            this.box = box;
            return this;
        }

        public SandBox build() {
            SandBox sandBox = new SandBox(sand);
            sandUtil.changeBox(sandBox, box);
            return sandBox;
        }

    }
}

用法

newBuilder().setBox(box).setSand(sand).build();

3 个答案:

答案 0 :(得分:2)

由于SandBoxBuilder,您正在使用@Component作为bean。无论您需要什么,您都必须能够访问ApplicationContext。我建议,而不是注入SandBoxBuilder bean,注入SandUtil bean并使用它来创建SandBoxBuilder实例

@Service
public class MyService {
    private final SandUtil sandUtil;

    @Autowired
    public MyService (SandUtil sandUtil) {
        this.sandUtil = sandUtil;
    }

    public void someMethod() {
        SandBoxBuilder builder = new SandBoxBuilder(sandUtil);
        ... // use it
    }
}

SandUtil需要是一个bean吗?它可能适合static实用程序类。

答案 1 :(得分:0)

我最近对Spring IOC了解不多。我使用Tapestry IOC很多应该提供类似的内部工作。

首先,单个按照定义应该是线程安全的。因此,如果每次使用它时都创建构建器,则构建器不需要是线程安全的。 SandUtil本身必须是线程安全的。

这就像合同:如果你是单身人士服务,你会被注入多个线程。因此,单例服务必须是线程安全的(同步方法,共享锁,同步对象等)。如果您的服务是PerThread意味着相同的服务仅在单个线程中使用,则它不必是线程安全的。

因此,确保SandUtil是线程安全的,如果Sandbox是PerThread或PerOccurence(每次注入时都会创建新实例),就可以了。

如果你想使构建器线程安全,因为你不能确定它的单个实例只在一个线程中使用 - 并且你不关心性能 - 你可以只将synchronized关键字添加到每个非私有方法构建器类。这是穷人并发控制,否则请查看一些有关并发控制的教程,如original Java lesson

答案 2 :(得分:0)

我猜这个非线程安全的部分与sandUtil字段有关吗?

您可以在changeBox方法上使用外部锁定以确保对其进行同步访问。

否则,也许'原型'bean范围会帮助你吗?

http://docs.spring.io/spring/docs/3.0.x/reference/beans.html#beans-factory-scopes

http://docs.spring.io/spring/docs/3.0.x/reference/beans.html#beans-factory-scopes-prototype