在项目中使用缓存我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