1原理图
2代码实现
1)
package cn.zhou.common.web.aop;
import java.io.StringWriter;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion;
import org.springframework.beans.factory.annotation.Autowired;
import com.danga.MemCached.MemCachedClient;
import cn.itcast.common.web.aop.MemCachedUtil;
import cn.zhou.common.encode.Md5Pwd;
import sun.org.mozilla.javascript.internal.IdFunctionCall;
/**
* 缓存memcached数据的切面对象
*
* @author Administrator around after
*/
public class CacheInterceptor {
@Autowired
private MemCachedClient memCachedClient;
@Autowired
private Md5Pwd md5Pwd;
// 缓存服务器时间
private final int TIMEOUT = 3600;
private Integer expiry = TIMEOUT;
// 配置环绕方法,调用service get*方法前,调用doAround方法,之后回到service get* 方法
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
// 去cemCachedClient查看有没有数据 key=包名+类名+方法名+参数(多个)
String cacheKey = getCacheKey(pjp);
// 如果缓存服务器连接不上,就去service层走数据库查数据
if (memCachedClient.stats().isEmpty()) {
return pjp.proceed();// 回service层,继续service层的方法
}
// 缓存服务器memcached没有对应键的数据
Object object = memCachedClient.get(cacheKey);
if (object == null) {
// 去service层查到对应数据,并写到 memcached中
Object proceed = pjp.proceed();
memCachedClient.set(cacheKey, proceed,expiry);
return proceed;
}else {
// 如果有数据,返回服务器数据并去controller层
return object;// 去controller层
}
}
/**
* aop 后置
* 数据同步
* 当数据有变动的时候,需要同步memcached 中 key 对应的value
* 实现方式:删除对应的key(用户下次查询需要访问doAround方法将变化的数据写到memcached中
*
*/
public void doAfter(JoinPoint jp) {
// 包名+类名 是同一个类下的数据更新的方法,那么get *保存在缓存的数据就要更新,
//因为都是同一个类,所以key值前面的包名+类名是相同的
// com.danga.MemCached.MemCachedClient
String PackageName =jp.getTarget().getClass().getName();
PackageName=md5Pwd.encode(PackageName);
//获取memcached的所有key
Map<String, Object> keySet = MemCachedUtil.getKeySet(memCachedClient);
Set<Entry<String, Object>> entrySet = keySet.entrySet();
for (Entry<String, Object> entry : entrySet) {
//遍历吗每个key,
if(entry.getKey().startsWith(PackageName)){
memCachedClient.delete(entry.getKey());
}
}
}
/**
* MemCached key 的命名规则 key=包名+类名+方法名+参数(多个)
*
* @param pjp
* @return
*/
public String getCacheKey(ProceedingJoinPoint pjp) {
StringBuilder sb = new StringBuilder();
StringBuilder sb2 = new StringBuilder();
// 包名
// com.danga.MemCached.MemCachedClient
String PackageName = pjp.getTarget().getClass().getName();
// 方法名
String methodName = pjp.getSignature().getName();
if (methodName != null) {
sb.append("." + methodName);
}
// 参数(多个)
ObjectMapper om = new ObjectMapper();
// 去掉json中value=null的key 和value
om.setSerializationInclusion(Inclusion.NON_NULL);
Object[] args = pjp.getArgs();
for (Object arg : args) {
// 对象转json,写的过程,json 是只字符串流
StringWriter out = new StringWriter();
try {
om.writeValue(out, arg);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
sb.append("." + out.toString());
}
//memcached key 的长度有限制--255,所以为了防止超过长度,这里用md5加密==》32位长度
//加密报名+类名===》32
sb2.append(md5Pwd.encode(PackageName));
//方法名+参数多个===》32
sb2.append(md5Pwd.encode(sb.toString()));
//返回的是64位的加密数字
return sb2.toString();
}
public void setExpiry(Integer expiry) {
this.expiry = expiry;
}
}
2)配置
<?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" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!-- memcached配置 -->
<!-- 配置客户端 -->
<bean id="memCachedClient" class="com.danga.MemCached.MemCachedClient">
<!-- 设置连接池 -->
<constructor-arg>
<value>sockIOPool</value>
</constructor-arg>
</bean>
<!--memcached连接池,帮助其实例化 -->
<!-- factory-method="getInstance" 实例化 -->
<!-- init-method="initialize" 初始化 -->
<!-- destroy-method="shutDown" 销毁 -->
<bean id="sockIOPool" class="com.danga.MemCached.SockIOPool"
factory-method="getInstance" init-method="initialize" destroy-method="shutDown">
<!-- 将自己的引用以构造的形式注入自己类 -->
<constructor-arg>
<value>sockIOPool</value>
</constructor-arg>
<property name="servers">
<list>
<!-- 配置memcached服务器的地址,如果有多个服务器,重复写value -->
<value>192.168.44.128:11211</value>
</list>
</property>
<!-- 设置权重 ,权重与上面memcached服务器地址一一对应 -->
<property name="weights">
<list>
<value>1</value>
</list>
</property>
</bean>
<!-- memcached切面对象 -->
<bean id="cacheInterceptor" class="cn.zhou.common.web.aop.CacheInterceptor">
<property name="expiry" value="1800"></property>
</bean>
<!-- spring aop 配置 get* 环绕 -->
<aop:config>
<!--面 -->
<aop:aspect ref="cacheInterceptor">
<!-- 点 -->
<!-- 切点为service层的get* 方法 -->
<!-- *cn.zhou.core.servi 会报错误 Caused by: java.lang.IllegalArgumentException:
Pointcut is not well-formed,要改为* cn.zhou.c -->
<!-- 数据查询的缓存 -->
<aop:around method="doAround" pointcut="execution(* cn.zhou.core.service.*.*.get*(..))" />
<!-- 数据更新,更新memcached ,方法是删掉查询的时候保存的key -->
<aop:after method="doAfter" pointcut="execution(* cn.zhou.core.service.*.*.update*(..))" />
<aop:after method="doAfter" pointcut="execution(* cn.zhou.core.service.*.*.add*(..))" />
<aop:after method="doAfter" pointcut="execution(* cn.zhou.core.service.*.*.delete*(..))" />
</aop:aspect>
</aop:config>
</beans>