上一篇簡單服務端緩存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支持的集合操做,並不能做爲通用的緩存處理場景,可考慮做爲其它的抽象方案的具體實現。
有任何問題,請留言聯繫。