本次我们使用springAOP+ehcache结合来实现数据的缓存,我们可以 Cache 系统中 Service 或则 DAO 层的 get/find 等方法返回结果,如果数据更新( 使用Create/update/delete 方法), 则刷新 cache 中相应的内容。
Aop中最常见的就是拦截器,那么我们首先需要创建的就是一个拦截器:
? MethodCacheInterceptor
<span style="font-family:Microsoft YaHei;font-size:14px;">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; } public MethodCacheInterceptor() { super(); } /** * public Object invoke(MethodInvocation invocation) 中, 完成了搜索 Cache/新建 cache 的功能。 * @param invocation * @return * @throws Throwable */ @Override public Object invoke( MethodInvocation invocation ) throws Throwable { String targetName = invocation.getThis().getClass().getName(); String methodName = invocation.getMethod().getName(); Object[] arguments = invocation.getArguments(); Object result; logger.debug("Find object from cache is " + cache.getName()); String cacheKey = getCacheKey(targetName, methodName, arguments); // 搜索cache功能,如果存在直接返回,否则的话将创建之后进行存储 Element element = cache.get(cacheKey); if (element == null) { logger.debug("Hold up method , Get method result and create cache........!"); result = invocation.proceed(); //处理结果,这句代码的作用是获取所拦截方法的返回值 element = new Element(cacheKey, (Serializable) result); cache.put(element); } return element.getValue(); // return null; } /** * implement InitializingBean, 检查 cache 是否为空 * @throws Exception */ @Override public void afterPropertiesSet() throws Exception { Assert.notNull(cache, "Need a cache. Please use setCache(Cache) create it."); } /** * 获得 cache key 的方法, cache key 是 Cache 中一个 Element 的唯一标识 * cache key 包括 包名+类名+方法名, 如 com.co.cache.service.UserServiceImpl.getAllUser */ 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(); } }</span>
? MethodCacheAfterAdvice
<span style="font-family:Microsoft YaHei;font-size:14px;">public class MethodCacheAfterAdvice implements AfterReturningAdvice, InitializingBean { private static final Log logger = LogFactory.getLog(MethodCacheAfterAdvice.class); private Cache cache; public void setCache(Cache cache) { this.cache = cache; } public MethodCacheAfterAdvice() { super(); } @Override public void afterReturning( Object arg0, Method arg1, Object[] arg2, Object arg3 ) throws Throwable { String className = arg3.getClass().getName(); List list = cache.getKeys(); for(int i = 0;i<list.size();i++){ String cacheKey = String.valueOf(list.get(i)); if(cacheKey.startsWith(className)){ cache.remove(cacheKey); logger.debug("remove cache " + cacheKey); } } } @Override public void afterPropertiesSet() throws Exception { Assert.notNull(cache, "Need a cache. Please use setCache(Cache) create it."); } }</span>
随后,再建立一个拦截器MethodCacheAfterAdvice,作用是在用户进行create/update/delete操作时来刷新/remove 相关 cache 内容, 这个拦截器实现了AfterReturningAdvice 接口,将会在所拦截的方法执行后执行在
public void afterReturning(Object arg0, Method arg1,Object[] arg2, Object arg3)方法中所预定的操作
? 配置文件:
Application_spring_cache.xml
<span style="font-family:Microsoft YaHei;font-size:14px;"><?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:context="http://www.springframework.org/schema/context" xmlns:cache="http://www.springframework.org/schema/cache" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-3.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!--支持缓存注解--> <cache:annotation-driven cache-manager="cacheManager"/> <context:component-scan base-package="ehcache.*" /> <!-- 一些@RequestMapping 请求和一些转换 --> <mvc:annotation-driven /> <!-- 前后缀 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/"/> <property name="suffix" value=".jsp"/> </bean> <!-- 静态资源访问 的两种方式 --> <!-- <mvc:default-servlet-handler/> --> <!--<mvc:resources location="/*" mapping="/**" />--> <mvc:view-controller path="/" view-name="forward:/index.jsp"/> <!-- Spring自己的基于java.util.concurrent.ConcurrentHashMap实现的缓存管理器(该功能是从Spring3.1开始提供的) --> <!-- <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager"> <property name="caches"> <set> <bean name="myCache" class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"/> </set> </property> </bean> --> <!-- 若只想使用Spring自身提供的缓存器,则注释掉下面的两个关于Ehcache配置的bean,并启用上面的SimpleCacheManager即可 --> <!-- Spring提供的基于的Ehcache实现的缓存管理器 --> <!-- 缓存 属性--> <bean id="cacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"> <property name="configLocation" value="classpath:/ehcache.xml" /> </bean> <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"> <property name="cacheManager" ref="cacheManagerFactory"/> </bean> <!--为了测试拦截器缓存--> <!-- 定义 ehCache 的工厂, 并设置所使用的 Cache name --> <bean id="ehCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean"> <property name="cacheManager"> <ref local="cacheManagerFactory"/> </property> <!--如果 cacheName 属性内设置的 name 在 ehCache.xml 中无法找到, 那么将使用默认的--> <!--cache(defaultCache 标签定义)--> <property name="cacheName"> <value>DEFAULT_CACHE</value> </property> </bean> <!-- find/create cache 拦截器 --> <bean id="methodCacheInterceptor" class="ehcache.CacheInterceptor.MethodCacheInterceptor"> <property name="cache"> <ref local="ehCache" /> </property> </bean> <!-- flush cache 拦截器 --> <bean id="methodCacheAfterAdvice" class="ehcache.CacheInterceptor.MethodCacheAfterAdvice"> <property name="cache"> <ref local="ehCache" /> </property> </bean> <!--上面的代码最终创建了两个"切入点", methodCachePointCut 和--> <!--methodCachePointCutAdvice, 分别用于拦截不同方法名的方法, 可以根据需要任意增加--> <!--所需要拦截方法的名称。--> <bean id="methodCachePointCut" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="advice"> <ref local="methodCacheInterceptor"/> </property> <property name="patterns"> <list> <value>.*find.*</value> <value>.*get.*</value> </list> </property> </bean> <bean id="methodCachePointCutAdvice" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="advice"> <ref local="methodCacheAfterAdvice"/> </property> <property name="patterns"> <list> <value>.*create.*</value> <value>.*update.*</value> <value>.*delete.*</value> </list> </property> </bean> <!--方法测试--> <!--如果缓存的配置与spring拦截在一个方法中就可以直接使用不用引入,否则的话需要引入--> <!--<import resource="cacheContext.xml"/>--> <bean id="testServiceTarget" class="ehcache.Service.TestServiceImpl"/> <bean id="testService" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target"> <ref local="testServiceTarget"/> </property> <property name="interceptorNames"> <list> <value>methodCachePointCut</value> <value>methodCachePointCutAdvice</value> </list> </property> </bean> </beans></span>
Ehcache.xml
<span style="font-family:Microsoft YaHei;font-size:14px;"><ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> <!--当内存缓存中对象数量超过maxElementsInMemory时,将缓存对象写到磁盘缓存中(需对象实现序列化接口)。--> <diskStore path="java.io.tmpdir"/> <!--:用来配置磁盘缓存使用的物理路径,Ehcache磁盘缓存使用的文件后缀名是*.data和*.index。--> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" maxElementsOnDisk="10000000" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> <persistence strategy="localTempSwap"/> </defaultCache> <cache name="DEFAULT_CACHE" maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="300000" timeToLiveSeconds="600000" overflowToDisk="true" /> </ehcache></span>
? 测试用例
接口TestService
<span style="font-family:Microsoft YaHei;font-size:14px;">package ehcache.Iservice; import java.util.List; /** * Created by xiaona on 2016/6/14. */ public interface TestService { public List getAllObject(); public void updateObject(Object Object); } </span>
实现TestServiceImpl
<span style="font-family:Microsoft YaHei;font-size:14px;">package ehcache.Service; import ehcache.Iservice.TestService; import org.springframework.stereotype.Service; import java.util.List; /** * Created by xiaona on 2016/6/14. */ @Service public class TestServiceImpl implements TestService { @Override public List getAllObject() { System.out.println("---TestService: Cache 内不存在该 element, 查找并放入 Cache! "); return null; } @Override public void updateObject( Object Object ) { System.out.println("---TestService:更新了对象,这个 Class 产生的 cache 都将被 remove"); } } </span>
方法调用
两种方式:getBean方式和使用注解读取
第一种方式:
<span style="font-family:Microsoft YaHei;font-size:14px;">package ehcache.controller; import ehcache.Iservice.TestService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * Created by xiaona on 2016/6/14. */ public class TestMain { public static void main( String[] args ) { ApplicationContext context = new ClassPathXmlApplicationContext("application_spring_cache.xml"); TestService testService=(TestService)context.getBean("testService"); System.out.println("1--第一次查找并创建 cache"); testService.getAllObject(); System.out.println("2--在 cache 中查找"); testService.getAllObject(); System.out.println("3--remove cache"); testService.updateObject(null); System.out.println("4--需要重新查找并创建 cache"); testService.getAllObject(); } } </span>
第二种方式:
<span style="font-family:Microsoft YaHei;font-size:14px;">@Resource private TestService testService; @RequestMapping(value = "/testInterceptor", method = RequestMethod.GET) public String testInterceptor() { System.out.println("1--第一次查找并创建 cache"); testService.getAllObject(); System.out.println("2--在 cache 中查找"); testService.getAllObject(); System.out.println("3--remove cache"); testService.updateObject(null); System.out.println("4--需要重新查找并创建 cache"); testService.getAllObject(); return root + "/" + "testInterceptor"; } </span>
前提是TestService的实现TestServiceImpl必须加上@Service注解,这样可以把其交给容器进行管理。什么样的结果表示我们的是成功的从缓存中获取到的呢?可以看到,
第一步执行getAllObject(),执行 TestServiceImpl 内的方法,并创建了 cache, 在第二次执行getAllObject()方法时,由于 cache 有该方法的缓存,直接从cache 中 get 出方法的结果, 所以没有打印出 TestServiceImpl中的内容, 而第三步, 调用了 updateObject 方法,和TestServiceImpl 相关的
cache 被 remove,所以在第四步执行时,又执行TestServiceImpl 中的方法, 创建 Cache。
? 小结
我们看出结合SpringAop的实例,可以实现我们对于请求方法的拦截,其实很多时候我们用到的页面都是一定时间内不会发生变化的,此时我们需要的是拦截经常被调用的方法从而进行缓存,这样的话可以提高用户对于页面请求的响应速度。