我们如何在应用程序范围内存储数据以用于缓存目的?

时间:2011-04-06 06:46:42

标签: java jsp caching web-applications

我在应用程序范围内存储数据。我想在每小时后清除这些数据。实际上使用它作为缓存一小时。实现这个的最佳方法是什么?之前我们使用会话范围来存储这些数据,并且在会话到期后它过去会过期。由于此数据在整个应用程序中是唯一的,因此我们希望将其存储在应用程序范围内。

4 个答案:

答案 0 :(得分:5)

对于非常简单的缓存,您可以使用Google番石榴MapMaker: 以下是从javadoc中获取的示例:

   ConcurrentMap<Key, Graph> graphs = new MapMaker()
       .concurrencyLevel(4)
       .softKeys()
       .weakValues()
       .maximumSize(10000)
       .expireAfterWrite(10, TimeUnit.MINUTES)
       .makeComputingMap(
           new Function<Key, Graph>() {
             public Graph apply(Key key) {
               return createExpensiveGraph(key);
             }
           });

有两种方法 expireAfterWrite expireAfterRead 来做你想要的事情。而且如果你想要/需要它,你可以免费获得一个线程安全的Map,包含weakValue,软键和延迟评估:)

答案 1 :(得分:1)

根据您是要积极清除过期数据(恢复内存)还是只想在到期时间后重新计算,这种方法会有很大不同。

如果您只是想重新计算,我会扩展SoftReference,例如:

public class ExpiringSoftReference<T> extends SoftReference<T> implements Serializable {
  private final long _expirationMoment;

  public ExpiringSoftReference(Object referent, long duration, TimeUnit unit) {
    this(referent, System.currentTimeMillis() + unit.toMillis(duration);
  }

  private ExpiringSoftReference(Object referent, long expirationMoment) {
    super(referent);
    _expirationMoment = expirationMoment;
  }

  public T get() {
    if (System.currentTimeMillis() >= _expirationMoment) {
      clear();
    }
    return super.get();
  }

  private Object writeReplace() throws ObjectStreamException {
    return new SerializedForm<T>(get(), _expirationMoment);
  }

  private static class SerializedForm<T> implements Serializable {
    private final T _referent;
    private final T _expirationMoment;

    SerializedForm(T referent, long expirationMoment) {
      _referent = referent;
      _expirationMoment = expirationMoment;
    }

    private Object readResolve() throws ObjectStreamException {
      return new ExpiringSoftReference<T>(_referent, _expirationMoment);
    }
  }
}

如果要积极回收内存,则需要实现一种垃圾回收。采取的方法是首先将所有引用放在线程安全优先级队列中,然后偶尔查看第一个元素,以查看是否至少有一个引用已过期:

public class ExpiringSoftReference<T> 
extends SoftReference<T> 
implements Comparable<ExpiringSoftReference>, Serializable {
  // same as above, plus
  public int compareTo(ExpiringSoftReference other) {
    if (this._expirationMoment < other._expirationMoment) {
      return -1;
    } else if (this._expirationMoment > other._expirationMoment) {
      return 1;
    } else {
      return 0;
    }
  }

  final long expiration() {
    return _expirationMoment;
  }
}

public class ExpirationEnforcer {
  private final PriorityBlockingQueue<ExpiringSoftReference> _byExpiration = new ...();

  public void add(ExpiringSoftReference reference) {
    _byExpiration.put(reference);
  }

  public synchronized void tick() {
    long now = System.currentTimeMillis();
    while (true) {
      ExpiringSoftReference candidate = _byExpiration.peek();
      if (candidate == null || candidate.expiration() > now) {
        return;
      }
      ExpirationSoftReference toClear = _byExpiration.peek();
      toClear.clear();
    }
  }
}

您需要每隔几秒或其他任何时间调用队列上的tick()。要在Java EE中执行此操作,您需要使用计时器服务或类似的东西。

答案 2 :(得分:0)

这些数据来自哪里?如果来自数据库,则应考虑使用支持Hibernate / JPA等一级和二级缓存的ORM。这样,您不需要更改代码以便从缓存中进行依赖(这将使其难以维护/可重用)。您只需按常规方式触发SQL查询,Hibernate / JPA将在适当的时候从Java的内存缓存中返回对象。

另见:

答案 3 :(得分:0)

我们找到了一种使用ServletContextListener和Timer清除应用程序范围变量的方法。

public class SampleListener implements ServletContextListener {

public static final long _startTimerDelayMin = 10; // In minutes
public static final long _startTimerPeriodMin = 60; // In minutes

Timer timer;
ServletContext context =null;       

public void contextInitialized(ServletContextEvent contextEvent) {      
    context = contextEvent.getServletContext();     
    scheduleTimer();        
}   

public void scheduleTimer() {
    long delay = _startTimerDelayMin * 60000;   //initial delay set to _startTimerDelayMin minutes as per msecs
    long period = _startTimerPeriodMin * 60000;   //subsequent rate set to _startTimerPeriodMin minutes as per msecs
    timer = new Timer();
    timer.scheduleAtFixedRate(new RemindTask(), delay, period); 
}

public void contextDestroyed(ServletContextEvent arg0) {
    //Stopping Timer Thread once context destroyed
    timer.cancel();
}

private static String getPropertyValue(String key){
    String value = "";
    try{
        value = Util.getPropertyValueOfKey(key).trim();
    }catch(IOException e){

    }
    return value;
}

/**
 * This class is invoked at given interval to clear the application scope variable for browse call which have not been used for given time
 *
 */
class RemindTask extends TimerTask {

    public void run() {
        clearScopeVariables();
    } 

    /**
     * This function has logic to clear the application scope variable for browse call which have not been used for given time
     */
    public void clearScopeVariables() {
        Date dt = new Date();
        Enumeration<String> applicationScopeVarNames = (Enumeration<String>)context.getAttributeNames();
                    // TODO: clear the scope variables
    }       
}   

在web.xml中添加监听器