在项目中使用缓存我OSCache,今天有时间将所有的EHCache的缓存的应用关注一下。首先我们看看Spring和EHCache采用AOP实现的缓存实现。
1.首先使用EHCache编写EHCache的配置文件。
<ehcache>
<diskStore path="java.io.tmpdir" />
<defaultCache maxElementsInMemory="10000" eternal="false"
timeToIdleSeconds="2" timeToLiveSeconds="5" overflowToDisk="true"
maxElementsOnDisk="10000000" diskPersistent="false"
diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" />
<cache name="testCache" maxElementsInMemory="10000"
maxElementsOnDisk="1000" eternal="false" overflowToDisk="false"
diskSpoolBufferSizeMB="20" timeToIdleSeconds="60" timeToLiveSeconds="3600"
memoryStoreEvictionPolicy="LFU" />
</ehcache>
2.编写AOP的方法拦截器,此处采用环绕通知的方式实现方法拦截。
package com.easyway.ecache.service;
import java.io.Serializable;
import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
/**
* AOP方法拦截实现缓存的更新的
*
* MethodInterceptor:在方法调用会自动调用
* InitializingBean:系统初始化时会自动初始化此类的特定的方法afterPropertiesSet()
* @author longgangbai
*
*/
public class MethodCacheInterceptor implements MethodInterceptor, InitializingBean{
private static final Log logger = LogFactory.getLog(MethodCacheInterceptor.class);
private Cache cache;
public void setCache(Cache cache) {
this.cache = cache;
}
//invoke方法会在spring配置文件里的,指明的cache拦截的方法的调用时,自动触发它,如这个项目里,
//当运行HelloEhcacheSpring.java类时,在showPersonsInfo方法里调用到personManager.getList()方法时,
//它就会先调到这里来执行,执行完才行下执行它的业务
public Object invoke(MethodInvocation invocation) throws Throwable {
//这个表示哪个类调用(或触发)了这个MethodCacheInterceptor,
String targetName = invocation.getThis().getClass().getName();
//这个表示哪个方法触发了这个类(MethodCacheInterceptor)方法(invoke)的调用,
String methodName = invocation.getMethod().getName();
//调用的参数,这里没有参数
Object[] arguments = invocation.getArguments();
Object result;
//这里得出的是:manager.PersonManagerImpl.getList
String cacheKey = getCacheKey(targetName, methodName, arguments);
Element element = cache.get(cacheKey);
if (element == null) {
// call target/sub-interceptor
//这个就是调用数据访问方法,
result = invocation.proceed();
//如这里调用了getList()方法,会先打印出"get Person from DB" ,
//然后将结果集放入到result里面去,这里由于使用的是自己配置只能放入10个元素的ehcache,
//所以这里的result是ArrayList<E> ,它里面存放的是elementData[10],并将getList得到的结果放入到elementData里面去了
System.out.println("set into cache");
// cache method result
//下面方法执行后,将cacheKey与数据集连起来,cacheKey是用来标识这个element的标志,我们可以有多个element(各自是来自不同的数据访问方法而形成的),区分它们就是用cacheKey,
//这里的新生成后的element,含有cacheKey,还在element创建时间,访问时间,还有命令次数等cache的属性,我觉得它就像是一个小cache一样,下次要不要更新它就要看它的这些属性来决定。
element = new Element(cacheKey, (Serializable) result);
//放入cache中
cache.put(element);
}else{
logger.debug("come from cache ...!");
}
//完成cache操作
System.out.println("out cache");
return element.getValue();
}
/**
* 缓存特定的类:
* @param targetName
* @param methodName
* @param arguments
* @return
*/
private String getCacheKey(String targetName, String methodName,
Object[] arguments) {
StringBuffer sb = new StringBuffer();
sb.append(targetName).append(".").append(methodName);
if ((arguments != null) && (arguments.length != 0)) {
for (int i = 0; i < arguments.length; i++) {
sb.append(".").append(arguments[i]);
}
}
return sb.toString();
}
/**
* 初始化时调用
*/
public void afterPropertiesSet() throws Exception {
if(null == cache) {
throw new IllegalArgumentException("Cache should not be null.");
}
}
}
3.Spring的关于缓存的配置类似事物的配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"
default-lazy-init="true">
<!--配置缓存管理器 -->
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation">
<value>ehcache.xml</value>
</property>
</bean>
<!-- 创建缓存的工厂的应用 -->
<bean id="methodCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheManager">
<ref local="cacheManager"/>
</property>
<property name="cacheName">
<value>com.easyway.MethodCache</value>
</property>
</bean>
<!-- 自定义缓存拦截器 -->
<bean id="methodCacheInterceptor" class="com.easyway.ecache.service.MethodCacheInterceptor">
<property name="cache">
<ref local="methodCache"/>
</property>
</bean>
<!-- 自定义拦截器 -->
<bean id="methodCachePointCut" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref local="methodCacheInterceptor"/>
</property>
<!-- 下面的配置就使得在数据访问时,cache将拦截从数据库获取的数据,与cache数据比较,如有就不放入cache,没有就放入,更新到数据库去,也是先存入cache,再更新到数据库中去 -->
<property name="patterns">
<list>
<value>.*getServiceName</value>
<value>.*testMethod</value>
</list>
</property>
</bean>
<!-- 声明一个服务 -->
<bean id = "ticketServiceTarget" class="com.easyway.ecache.service.TicketService" />
<!-- 相关的服务 -->
<bean id="ticketService"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref local="ticketServiceTarget"/>
</property>
<property name="interceptorNames">
<list>
<value>methodCachePointCut</value>
</list>
</property>
</bean>
</beans>
4.测试服务类:
package com.easyway.ecache.service;
import java.util.List;
/**
* 对其所有的以testMethod* ,getServiceName方式命令的方法,
* 进行缓存处理。当调用其他命令时,不进行缓存
* @author longgangbai
*
*/
@SuppressWarnings("unchecked")
public class TicketService {
public String testMethod(){
System.out.println("没走缓存,直接调用TestService.testMethod()");
return "china";
}
public void updateMethod(){
System.out.println("updateMethod");
}
public void insertMethod(){
System.out.println("insertMethod");
}
public void deleteMethod(){
System.out.println("deleteMethod");
}
/**
* 需要缓存的集合
*/
private List ticketList;
/**
* 需要缓存的服务名称
*/
private String serviceName;
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public List getTicketList() {
return ticketList;
}
public void setTicketList(List ticketList) {
this.ticketList = ticketList;
}
/**
* 修改的服务端名称备注但是不缓存
* @param serviceName
*/
public void changesetServiceName(String serviceName) {
this.serviceName = serviceName;
}
}
5.测试用例
package com.easyway.ecache.service;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* 这里使用了ehcache与spring结合,这里并没用用到数据库,用spring只是用来管理bean,
* 这里用ehcache就相当于数据库,存放对象信息
* @author longgangbai
*/
@SuppressWarnings({"unchecked"})
public class HelloEhcacheSpring{
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath*:applicationContext.xml");
TicketService ticketSrv = (TicketService) context.getBean("ticketService");
//配置了spring就可以从配置文件里找到对应的接口实现类,再生成实例对象,以完成业务处理
String srvName0=ticketSrv.testMethod();
//获取初始化服务端名称
System.out.println("srvName0="+srvName0);
//设置存储的名称
ticketSrv.setServiceName("ticketService");
String srvName1=ticketSrv.testMethod();
//获取服务端名称
System.out.println("srvName1="+srvName1);
//修改服务名称但是不缓存
ticketSrv.updateMethod();
String srvName2=ticketSrv.testMethod();
//获取服务端名称来源自缓存注意观察
System.out.println("srvName2="+srvName2);
}
}
6.测试结果:
没走缓存,直接调用TestService.testMethod()
打印信息如下:
set into cache
out cache
srvName0=china
out cache
srvName1=china
updateMethod
out cache
srvName2=china