Want#

上一篇简单服务端缓存API设计设计并实现了一套缓存API,适应不同的缓存产品,本文重点是基于Spring框架集成应用开发。

缓存集成#

以普通Web应用开发常见的搭配Spring+Spring mvc+Mybatis为例,在与DB集成时通常会出现在Mybatis数据访问层做缓存,在之前的文章Mybatis缓存结构一文中有关于Mybatis缓存的简要概述。

随着业务的发展,缓存的范围远不止针对数据库,缓存的产品也会有多种,这种情形下,我们很希望能够使用相同的代码或者基于相同的结构去设计并实现缓存逻辑。

缓存集成示例#

最常规的情形:

  • 定义一个基于方法的注解(Cache)
  • 定义基于注解拦截业务逻辑的切面
  • 所有被注解标注的业务逻辑处理结果,都可以被缓存

备注:Spring提供了丰富的切入点表达式逻辑,如果你认为注解会影响代码的动态部署,可以考虑全部采用xml文件配置的方式。

定义Cache注解

@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.xml

<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设计


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支持的集合操作,并不能作为通用的缓存处理场景,可考虑作为其它的抽象方案的具体实现。

QA#

有任何问题,请留言联系。

Spring集成缓存的更多相关文章

  1. 从零开始学 Java - Spring 集成 Memcached 缓存配置(二)

    Memcached 客户端选择 上一篇文章 从零开始学 Java - Spring 集成 Memcached 缓存配置(一)中我们讲到这篇要谈客户端的选择,在 Java 中一般常用的有三个: Memc ...

  2. 从零开始学 Java - Spring 集成 Memcached 缓存配置(一)

    硬盘和内存的作用是什么 硬盘的作用毫无疑问我们大家都清楚,不就是用来存储数据文件的么?如照片.视频.各种文档或等等,肯定也有你喜欢的某位岛国老师的动作片,这个时候无论我们电脑是否关机重启它们永远在那里 ...

  3. spring集成ehcache本地缓存

    1.maven依赖 <!-- ehcache 相关依赖 --> <dependency> <groupId>net.sf.ehcache</groupId&g ...

  4. (转)为Spring集成的Hibernate配置二级缓存

    http://blog.csdn.net/yerenyuan_pku/article/details/52896195 前面我们已经集成了Spring4.2.5+Hibernate4.3.11+Str ...

  5. Spring集成GuavaCache实现本地缓存

    Spring集成GuavaCache实现本地缓存: 一.SimpleCacheManager集成GuavaCache 1 package com.bwdz.sp.comm.util.test; 2 3 ...

  6. 从零开始学 Java - Spring 集成 ActiveMQ 配置(二)

    从上一篇开始说起 上一篇从零开始学 Java - Spring 集成 ActiveMQ 配置(一)文章中讲了我关于消息队列的思考过程,现在这一篇会讲到 ActivMQ 与 Spring 框架的整合配置 ...

  7. spring集成常用技术的配置

    使用spring集成其他技术,最基本的配置都是模板化的,比如配置视图模板引擎.数据库连接池.orm框架.缓存服务.邮件服务.rpc调用等,以spring的xml配置为例,我将这些配置过程整理出来,并不 ...

  8. 注释驱动的 Spring cache 缓存介绍

    概述 Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如 EHCache 或者 OSCache),而是一个对缓存使 ...

  9. [转]注释驱动的 Spring cache 缓存介绍

    原文:http://www.ibm.com/developerworks/cn/opensource/os-cn-spring-cache/ 概述 Spring 3.1 引入了激动人心的基于注释(an ...

随机推荐

  1. 【zznu-2174】

    题目链接 题目描述 给出一个圆C1的圆心和半径x1,y1,r1和另外一个圆C2的圆心为x2,y2(x,y均为整数 r为正整数  且绝对值不超1e8).已知两圆圆心距不超1e8,给出两者相交面积S(大于 ...

  2. 公客网beta阶段发布说明

    项目 公客 公正客观的课程评价网站 功能说明 评价的增删改 对课程发表评价 限制评价次数(3次),删除与增量修改评价 评价下的讨论与点赞 在评价下添加讨论,支持在讨论中使用@与对方交流想法 为评价点赞 ...

  3. Spring整合hibernate:3、使用XML进行声明式的事务管理

    配置applicationContext.xml文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 2 ...

  4. 《Effective C++》第8章 定制new和delete-读书笔记

    章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effecti ...

  5. Linux系统在启动过程中启动级别发生错误的解决办法

    一.系统启动级别一共有六个: 0:系统停机模式,系统不可以正常启动 1:单用户模式, root权限,用于系统的维护,禁止远程登陆 2:多用户模式,没有NFS网络支持 3:完整的多用户文本模式,有NFS ...

  6. SQL Server(MSSQLSERVER)启动失败,提示“请求失败或服务未及时响应

    1.SQL Server(MSSQLSERVER)启动失败,提示“请求失败或服务未及时响应. --------------------------- SQL Server 配置管理器 -------- ...

  7. 《利用Python进行数据分析》笔记---第2章--1880-2010年间全美婴儿姓名

    写在前面的话: 实例中的所有数据都是在GitHub上下载的,打包下载即可. 地址是:http://github.com/pydata/pydata-book 还有一定要说明的: 我使用的是Python ...

  8. TF随笔-7

    求平均值的函数 reduce_mean axis为1表示求行 axis为0表示求列 >>> xxx=tf.constant([[1., 10.],[3.,30.]])>> ...

  9. 使用ElasticSearch完成百万级数据查询附近的人功能

    上一篇文章介绍了ElasticSearch使用Repository和ElasticSearchTemplate完成构建复杂查询条件,简单介绍了ElasticSearch使用地理位置的功能. 这一篇我们 ...

  10. 《机器学习》第三章——LDA

    import numpy as np x=np.zeros((2,17)) y=np.zeros((1,17)) def dataload(filename): f=open(filename) ar ...