cache服务的实现(在spring中)
扩展Spring:实现Cache服务- -
TargetSource的概念
Spring Framework引入了TargetSource的概念,使用org.springframework.aop.TargetSource接口来表示。它负责返回实现了JointPoint的"target object"。当AOP proxy处理一个方法调用时,TargetSource的实现返回target对象的实例。
使用Spring AOP的开发人员通常并不需要直接和TargetSource打交道,但TargetSource提供了支持pooling,hot swappable等高级特性。例如,pooling TargetSource使用一个pool管理对象实例,为每次调用返回不同的target实例。
如果你不指定TargetSource,Spring AOP将使用一个缺省的实现来包装对象,并且每次调用都会返回同一个对象实例。
我的目标
Spring Framework使用TargetSource的概念实现了pooling的功能,其中使用了Jakarta的Commons Pool。通过编写配置文件声明,Spring可以pooling任何一个POJO对象。我的目标是模仿pooling的实现方式,实现一个简单的缓存机制。用于探索Spring AOP的实现方式,以便扩展目前Spring Framework没有实现的功能。
选择对象缓存类库
目前,开源的对象缓存类库有很多,如Jakarta Commons Cache,OSCache,Java Caching System和Java Object Cache等。其中OSCache的比较成熟,应用较广(如Hibernate,iBatis等),功能也很强大(支持Cluster)。因此,我选择了OSCache作为实现Cache TargetSource的类库。
准备测试用例
考虑一下这样的应用情景,我的应用需要一个管理数据的对象,该对象每隔一定的时间从数据库中读取数据,并缓存在内存中。在缓存期间,如果客户程序读取数据,该数据对象将返回内存中的数据,如果数据超过指定的缓存时间,则重新从数据库中读取并更新数据。
首先,我模仿CommonsPoolTargetSource编写了以下的testcache.xml配置文件:
<beans>
<bean id="businessObjectTarget" class="unittest.MyBusinessObject" singleton="false"/>
<bean id="cacheTargetSource" class="unittest.OSCacheTargetSource">
<property name="target"><ref local="businessObjectTarget"/></property>
<property name="refreshPeriod"><value>10</value></property>
</bean>
<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces"><value>unittest.Business</value></property>
<property name="targetSource"><ref local="cacheTargetSource"/></property>
</bean>
</beans>
然后,编写测试用例CacheTargetSourceTest:
public class CacheTargetSourceTest extends TestCase {
ApplicationContext ctx = null;
BizManager manager;
protected void setUp() throws Exception {
super.setUp();
ctx = new FileSystemXmlApplicationContext("testcache.xml");
manager = new BizManager(200);
manager.start();
}
public void testGetId() throws Exception {
for (int i=0; i<6; i++) {
Thread.sleep(2000);
Business biz = (Business)ctx.getBean("businessObject");
assertNotNull(biz);
System.out.println("get id from BizObjec=" + biz.getId());
}
}
}
接下来,编写业务测试代码Business,MyBusinessObject:
public interface Business {
public int getId();
}
public class MyBusinessObject implements Business {
final Log logger = LogFactory.getLog(this.getClass());
private int id = 0;
public MyBusinessObject() {
logger.debug("create new MyBusnissObject");
id = BizManager.getId();
}
public int getId() {
return id;
}
}
其中MyBusinessObject通过一个BizManager线程类来更新数据库,模拟数据库中的数据更新。
public class BizManager extends Thread {
private int period = 1000;
private static int id = 0;
public BizManager(int l) {
this.period = l;
}
public void run() {
System.out.println("BizManager started.");
try {
while (true) {
sleep(period);
id++;
}
} catch(Exception ex) {}
}
public static int getId() {
return id;
}
}
最后,实现一个简单OSCacheTargetSource类。我在这里试探性地实现了TargetSource接口,并通过setRefreshPeriod方法指定了更新数据的时间间隔。在这里有一些地方需要说明。为了能让FactoryBean生成新的TargetSource实例,TargetSource的isStatic方法返回false;在releaseTarget方法中调用CacheAdministrator的destroy方法;每次更新内存的数据时,都生成新的数据管理对象,以调用构造器中读取更新数据的方法,这种做法当然很不优雅,留待以后重构。代码如下:
public class OSCacheTargetSource implements TargetSource {
private int refreshPeriod = 2000;
private Object target;
private final static String MY_KEY = "mykey";
private GeneralCacheAdministrator admin;
private final Log logger = LogFactory.getLog(OSCacheTargetSource.class);
public OSCacheTargetSource() {
admin = new GeneralCacheAdministrator();
}
public boolean isStatic() {
return false;
}
public void setTarget(Object obj) {
this.target = obj;
}
public void releaseTarget(Object target) throws Exception {
admin.destroy();
}
public int getRefreshPeriod() {
return refreshPeriod;
}
public void setRefreshPeriod(int i) {
refreshPeriod = i;
}
private Object getCached() {
try {
//CacheEntry.INDEFINITE_EXPIRY = -1
target = (Object) admin.getFromCache(MY_KEY, refreshPeriod);
} catch (NeedsRefreshException nre) {
try {
target = target.getClass().newInstance();
admin.putInCache(MY_KEY, target);
} catch (Exception ex) {
target = nre.getCacheContent();
admin.cancelUpdate(MY_KEY);
}
}
return target;
}
public Object getTarget() throws Exception {
return getCached();
}