上一篇简单服务端缓存API设计设计并实现了一套缓存API,适应不一样的缓存产品,本文重点是基于Spring框架集成应用开发。html
以普通Web应用开发常见的搭配Spring+Spring mvc+Mybatis为例,在与DB集成时一般会出如今Mybatis数据访问层作缓存,在以前的文章Mybatis缓存结构一文中有关于Mybatis缓存的简要概述。java
随着业务的发展,缓存的范围远不止针对数据库,缓存的产品也会有多种,这种情形下,咱们很但愿可以使用相同的代码或者基于相同的结构去设计并实现缓存逻辑。redis
最常规的情形:spring
备注:Spring提供了丰富的切入点表达式逻辑,若是你认为注解会影响代码的动态部署,能够考虑所有采用xml文件配置的方式。数据库
定义Cache注解json
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Cache { }
定义切面数组
package org.wit.ff.aspect; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.wit.ff.cache.CacheKey; import org.wit.ff.cache.IAppCache; import org.wit.ff.util.JsonUtil; /** * Created by F.Fang on 2015/9/15. * Version :2015/9/15 */ @Aspect public class BusinessCacheAspect { private static final Logger LOGGER = LoggerFactory.getLogger(BusinessCacheAspect.class); /** * 实际的数据缓存服务提供者. */ private IAppCache appCache; @Pointcut("@annotation(org.wit.ff.cache.Cache)") public void methodCachePointcut() { } @Around("methodCachePointcut()") public Object record(ProceedingJoinPoint pjp) throws Throwable { CacheKey cacheKey = buildCacheKey(pjp); // 只要两个CacheKey对象的json值相等,就认为一致. // 数组是对象,内容相同的数组执行equals比较时并不想等. // 若是先转换成json, 将json字符串转换成bytes数组,做为值比较更合理. //appCache.get(); MethodSignature ms = (MethodSignature) pjp.getSignature(); // 获取方法返回类型 Class<?> returnType = ms.getMethod().getReturnType(); // 返回类型为空,不会应用缓存策略 if (Void.TYPE.equals(returnType)) { // 实际上, 在你并不想改变业务模型的条件下, pjp.proceed()和pjp.proceed(params) 无差异. return pjp.proceed(); } // Json化能够避免掉许多的问题, 没必要经过重写CacheKey的equals方法来比较, 由于实现会比较的复杂, 而且不见得能作好. String key = JsonUtil.objectToJson(cacheKey); // 查询缓存,即便缓存失败,也不能影响正常业务逻辑执行. Object result = null; try { result = appCache.get(key, returnType); } catch (Exception e) { LOGGER.error("get cache catch exception!", e); } // 若缓存为空, 则处理实际业务. if (result == null) { // 正常业务处理不要作任何拦截. result = pjp.proceed(); // 暂时不记录是否缓存成功,虽然有boolean返回. try { appCache.put(key, result); }catch (Exception e){ LOGGER.error("put cache catch exception!",e); } } return result; } private CacheKey buildCacheKey(ProceedingJoinPoint pjp) { CacheKey key = new CacheKey(); key.setMethod(pjp.getSignature().getName()); if (pjp.getArgs() != null && pjp.getArgs().length > 0) { key.setParams(pjp.getArgs()); } return key; } public void setAppCache(IAppCache appCache) { this.appCache = appCache; } }
业务服务缓存
package org.wit.ff.business; import org.springframework.stereotype.Service; import org.wit.ff.cache.Cache; import org.wit.ff.model.User; import java.util.ArrayList; import java.util.List; /** * Created by F.Fang on 2015/10/22. * Version :2015/10/22 */ @Service public class UserBusiness { @Cache public List<User> getUser(int appId, List<Integer> userIds){ System.out.println("do business, getUser, appId="+appId); User user1 = new User(1, "f.fang@adchina.com", "fangfan"); User user2 = new User(2,"mm@adchina.com","mm"); List<User> list = new ArrayList<>(); list.add(user1); list.add(user2); return list; } @Cache public User findUnique(int appId, int id){ System.out.println("do business, findUnique, appId="+appId); User user = new User(100, "am@gmail.com", "am"); return user; } @Cache public void saveUser(int appId, User user){ System.out.println("do business, saveUser"); } }
spring.xmlmvc
<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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <import resource="classpath:spring-memcached.xml" /> <!-- Aspect扫描 Aspect配置的顺序决定了谁先执行.--> <bean id="cacheAspect" class="org.wit.ff.aspect.BusinessCacheAspect" > <property name="appCache" ref="xmemAppCache"/> </bean> <aop:aspectj-autoproxy proxy-target-class="true"/> <!-- 启动service扫描 --> <context:component-scan base-package="org.wit.ff.business"/> </beans>
备注:spring-memcached.xml请参考上一篇简单服务端缓存API设计app
package org.wit.ff.cache; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; import org.wit.ff.business.UserBusiness; import org.wit.ff.model.User; /** * Created by F.Fang on 2015/10/26. * Version :2015/10/26 */ @ContextConfiguration("classpath:spring.xml") public class CacheIntegrationTest extends AbstractJUnit4SpringContextTests { @Autowired private UserBusiness userBusiness; @Test public void demo(){ User user1 = userBusiness.findUnique(3,1000); System.out.println(user1); userBusiness.saveUser(1, new User()); User user2 = userBusiness.findUnique(1,1000); System.out.println(user2); userBusiness.saveUser(1, new User()); } }
依据目前的简单业务情形分析,一套简单的缓存支持方案(包括对缓存产品的封装和无侵入式的应用接入)。
目前为止,我的认为如redis支持的集合操做,并不能做为通用的缓存处理场景,可考虑做为其它的抽象方案的具体实现。
有任何问题,请留言联系。