如何只在构造函数中初始化一次变量?

时间:2017-02-07 23:03:43

标签: java caching constructor initialization guava

我有一个构建器模式,其中我从客户那里获取了一些参数,并基于我构建我的构建器类,然后将该构建器类传递给我们的底层库,然后我的库将使用它。

public final class KeyHolder {
  private final String clientId;
  private final String deviceId;
  private final int processId;
  private final Cache<String, List<Response>> userCache;
  private static final long MAXIMUM_CACHE_SIZE = 5000000;
  private static final long EXPIRE_AFTER_WRITE = 120; // this is in seconds

  private KeyHolder(Builder builder) {
    this.clientId = builder.clientId;
    this.deviceId = builder.deviceId;
    this.processId = builder.processId;
    this.maximumCacheSize = builder.maximumCacheSize;
    this.expireAfterWrite = builder.expireAfterWrite;

    // how to execute this line only once
    this.userCache =
        CacheBuilder
            .newBuilder()
            .maximumSize(maximumCacheSize)
            .expireAfterWrite(expireAfterWrite, TimeUnit.SECONDS)
            .removalListener(
                RemovalListeners.asynchronous(new CustomListener(),
                    Executors.newSingleThreadScheduledExecutor())).build();

  }

  public static class Builder {
    protected final int processId;
    protected String clientId = null;
    protected String deviceId = null;
    protected long maximumCacheSize = MAXIMUM_CACHE_SIZE;
    protected long expireAfterWrite = EXPIRE_AFTER_WRITE;


    public Builder(int processId) {
      this.processId = processId;
    }

    public Builder setClientId(String clientId) {
      this.clientId = clientId;
      return this;
    }

    public Builder setDeviceId(String deviceId) {
      this.deviceId = deviceId;
      return this;
    }

    public Builder setMaximumCacheSize(long size) {
      this.maximumCacheSize = size;
      return this;
    }

    public Builder setExpiryTimeAfterWrite(long duration) {
      this.expireAfterWrite = duration;
      return this;
    }

    public KeyHolder build() {
      return new KeyHolder(this);
    }
  }

 // getters here
}

对于我们库的每次调用,他们每次都会创建一个新的KeyHolder构建器类并将其传递给我们的库。 processIdclientIddeviceId会随着每次通话而改变,但maximumCacheSizeexpireAfterWrite将与每次通话保持一致。正如您在上面所看到的,我在这里使用guava缓存,因为他们每次都在创建KeyHolder构建器类,如何确保下面的行只在构造函数中执行一次?

    this.userCache =
        CacheBuilder
            .newBuilder()
            .maximumSize(maximumCacheSize)
            .expireAfterWrite(expireAfterWrite, TimeUnit.SECONDS)
            .removalListener(
                RemovalListeners.asynchronous(new CustomListener(),
                    Executors.newSingleThreadScheduledExecutor())).build();

由于现在使用当前代码,它将在每次调用时执行,我每次都会在我的库中获得一个新的guava缓存,因此使用此guava缓存在我的库中先前缓存的任何条目都将丢失。< / p>

如何仅将一个特定变量初始化一次,之后它应该忽略传递给它的值?

更新

public class DataClient implements Client {
    private final ExecutorService executor = Executors.newFixedThreadPool(10);

    // for synchronous call
    @Override
    public List<Response> executeSync(KeyHolder key) {
        Cache<String, List<Response>> userCache = key.getUserCache();
        List<Response> response = userCache.getIfPresent(key.getUUID());
        if (CollectionUtils.isNotEmpty(response)) {
          return response;
        }
        // if not in cache, then normally call the flow and populate the cache
        List<Response> dataResponse = null;
        Future<List<Response>> future = null;
        try {
            future = executeAsync(key);
            dataResponse = future.get(key.getTimeout(), TimeUnit.MILLISECONDS);
            userCache.put(key.getUUID(), dataResponse);
        } catch (TimeoutException ex) {
            // log error and return DataResponse
        } catch (Exception ex) {
            // log error and return DataResponse
        }

        return dataResponse;
    }
}

3 个答案:

答案 0 :(得分:1)

如果您只想设置一次缓存,为什么每个KeuHolder对象都会尝试构建它?事实上,即使KeyHolder#Builder公开了方法来帮助构建缓存,这只会有用一次。

这非常值得怀疑。如果第一个KeyHolder未指定缓存详细信息,该怎么办?我的意思是,它没有被强迫(你没有正确使用构建器模式,最后更多)。

解决此问题的第一步是确保在开始创建KeyHolder对象之前设置缓存。您可以通过创建静态工厂并使userCache静态:

来完成此操作
class KeyHolder {
    private static Map<String, List<Response>> userCache;

    public static KeyHolder.Builder newBuilder(int id) {
        if(userCache == null) {
            userCache = ...;
        }

        return new Builder(id);
    }
}

但是你可能已经从我的评论中读到,这只是这个问题的创可贴。每当我们想要创建一个新的KeyHolder时,这将检查userCache,这不应该发生。

相反,您应该将缓存与KeyHolder一起解耦。为什么还需要了解缓存?

您的缓存属于DataClient

class DataClient {
    private Map<String, List<Response>> userCache;

    public List<Response> executeSync(KeyHolder key) {
        List<Response> response = userCache.getIfPresent(key.getUUID());
        //...
    }
}

您可以通过DataClient构造函数接受设置,或使用已指定的设置将缓存传递到DataClient

至于你对构建器模式的使用,请记住我们使用它的原因:Java缺少可选参数。

这就是构建器很常见的原因:它们允许我们通过方法指定可选数据。

您将关键信息(例如缓存设置)指定为可选参数(构建器方法)。如果您不需要信息,则应该只使用构建器方法,并且缓存信息肯定是必需的。我会询问可选的deviceIdclientId是如何进行的,看看唯一需要的数据是productId

答案 1 :(得分:0)

首先,make it static

private static Cache<String, List<Response>> userCache;

然后,只有在尚未初始化时才初始化

private KeyHolder(Builder builder) {
    ...

    if (userCache == null) {
        userCache = CacheBuilder
            .newBuilder()
            .maximumSize(maximumCacheSize)
            .expireAfterWrite(expireAfterWrite, TimeUnit.SECONDS)
            .removalListener(
                RemovalListeners.asynchronous(new CustomListener(), Executors.newSingleThreadScheduledExecutor())
            ).build();
    }
}

如果你真的不需要在运行时自定义缓存(通过使用传入的参数,我建议使用类似

的内容)
// initialize the cache while defining it
// replace maximumCacheSize and expireAfterWrite with constants
private static final Cache... = CacheBuilder.newBuilder()...;

并将其从构造函数中删除。

答案 2 :(得分:0)

您可以将变量设为静态,只在第一次调用时初始化它,如果它为null。像这样:

public final class KeyHolder {
  private final String clientId;
  private final String deviceId;
  private final int processId;

  //this var is now static, so it is shared across all instances
  private static Cache<String, List<Response>> userCache = null;  

  private static final long MAXIMUM_CACHE_SIZE = 5000000;
  private static final long EXPIRE_AFTER_WRITE = 120; // this is in seconds

  private KeyHolder(Builder builder) {
    this.clientId = builder.clientId;
    this.deviceId = builder.deviceId;
    this.processId = builder.processId;
    this.maximumCacheSize = builder.maximumCacheSize;
    this.expireAfterWrite = builder.expireAfterWrite;

    //this will be executed only the first time, when the var is null
    if (userCache == null) {
      userCache =
        CacheBuilder
            .newBuilder()
            .maximumSize(maximumCacheSize)
            .expireAfterWrite(expireAfterWrite, TimeUnit.SECONDS)
            .removalListener(
                RemovalListeners.asynchronous(new CustomListener(),
                    Executors.newSingleThreadScheduledExecutor())).build();

  }

  //rest of your class below