版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/PYXLY1314/article/details/50427427
1、配置Spring AOP 在XML中(具体可以参考我前面的博客内容《SpringMVC+AOP注意点》)
2、自定义拦截注解,并且对缓存有效期可以通过参数来改变(注解配合AOP切面)
package com.memcached;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 拦截service层的缓存注解
* @author luoyi
*
*/
@Target({ElementType.PARAMETER,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ServiceMemcached {
public abstract int effectSecs() default 10*24*3600;//默认有效期10天
}
3、编写memcahed的工具类(依赖java_memcached-release_2.6.6.jar,和其他辅助的jar),用来操作缓存
package com.memcached;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.net.telnet.TelnetClient;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import com.danga.MemCached.MemCachedClient;
import com.danga.MemCached.SockIOPool;
public class MemCachedUtil {
private final static Log logger = LogFactory.getLog(MemCachedUtil.class);
// 是否启用MemCached内存数据库
protected static boolean enUsed = true;
// 创建全局唯一的可实例化对象
protected static MemCachedUtil memCached = new MemCachedUtil();
// 初始化MemCached客户端对象
protected static MemCachedClient memClient = new MemCachedClient();
// 定义MemCached服务器运行环境配置文件名称
private static final String MemCachedConfigFile_NAME = "MemCachedConfig.xml";
// 定义可用的MemCached服务器列表,用于分布式存储
private static String[] serverListArr = new String[1];
// 定义各MemCached服务器的负载权重列表,与服务器列表按先后顺序对应
private static Integer[] weightListArr = new Integer[1];;
// 定义MemCached服务器运行环境表,配置文件中关于参数相关数据将保存到该表
private static Map<String, String> serverConfig;
// 定义MemCached服务器运行状态表,用于保存各状态的中文解释
protected static HashMap<String, String> statsItems;
// 设置全局静态参数,以下代码在整个服务器运行周期内仅运行一次!
static {
// 初始化MemCached运行环境配置
// 首先初始化各参数默认值,然后加载配置文件,遍历其中的参数值并进行覆盖。
initConfig();
if (enUsed) { // 如果已启用memcached缓存服务
// 获取socke连接池的实例对象
SockIOPool pool = SockIOPool.getInstance();
// 设置可用的MemCached服务器信息,实现分布式存储
pool.setServers(serverListArr);
// 设置各MemCached服务器的负载权重,根据可支配内存实现负载均衡
pool.setWeights(weightListArr);
// 设置初始连接数
pool.setInitConn(Integer.parseInt(serverConfig.get("initConn")
.toString()));
// 设置最小连接数
pool.setMinConn(Integer.parseInt(serverConfig.get("minConn")
.toString()));
// 设置最大连接数
pool.setMaxConn(Integer.parseInt(serverConfig.get("maxConn")
.toString()));
// 设置连接最大空闲时间
pool.setMaxIdle(Long.parseLong(serverConfig.get("maxIdle")
.toString()));
// 设置主线程的睡眠时间,每隔该时间维护一次各连接线程状态
pool.setMaintSleep(Long.parseLong(serverConfig.get("maintSleep")
.toString()));
// 关闭nagle算法
pool.setNagle(false);
// 读取操作的超时限制
pool.setSocketTO(Integer.parseInt(serverConfig.get("socketTO")
.toString()));
// 连接操作的超时限制,0为不限制
pool.setSocketConnectTO(Integer.parseInt(serverConfig.get(
"socketConnTO").toString()));
// 初始化连接池
pool.initialize();
// 压缩设置,超过指定大小的数据都会被压缩
// 从java_memcached-release_2.6.1开始已经不再支持内置的数据压缩功能
// memClient.setCompressEnable(Boolean.parseBoolean(serverConfig.get("compressEnable").toString()));
// memClient.setCompressThreshold(Long.parseLong(serverConfig.get("compressThreshold").toString()));
}
}
/**
* @category 初始化MemCached运行环境配置
* @category 注:该方法在整个服务器周期内仅运行一次
*/
protected static void initConfig() {
// 日志生成系统
logger.info("初始化MemCached运行环境配置,开始...");
// 初始化可用的MemCached服务器列表默认值(本机)
serverListArr[0] = "127.0.0.1:11211";
weightListArr[0] = 1;
// 初始化MemCached服务器运行环境表(默认值),当某参数未在配置文件中进行定义时,将使用该默认值
serverConfig = new HashMap<String, String>() {
private static final long serialVersionUID = 1L;
{
put("initConn", "5"); // 设置初始连接数
put("minConn", "5"); // 设置最小连接数
put("maxConn", "250"); // 设置最大连接数
put("maxIdle", "21600000"); // 设置连接最大空闲时间(6小时)
put("maintSleep", "30"); // 设置主线程的睡眠时间(30秒)
put("socketTO", "10000"); // 读取操作的超时限制(10秒)
put("socketConnTO", "0"); // 连接操作的超时限制(不限制)
}
};
// 开始读取配置文件,并将其中的参数值向默认环境表中进行覆盖
String filePath = Thread.currentThread().getContextClassLoader()
.getResource(MemCachedConfigFile_NAME).getPath().substring(1);
File file = new File(filePath.replaceAll("%20", " "));
try {
if (file.exists()) { // 如果可以成功加载配置文件
SAXReader sr = new SAXReader();
Document doc = sr.read(file);
Element Root = doc.getRootElement(); // 获得根节点
Element Enabled = (Element) Root.selectSingleNode("Enabled"); // 获得是否启用memcached节点
Element Servers = (Element) Root.selectSingleNode("Servers"); // 获得可用的服务器列表父节点
Element Config = (Element) Root.selectSingleNode("Config"); // 获得运行环境参数列表父节点
enUsed = Boolean.parseBoolean(Enabled.getText()); // 是否启用memcached缓存服务
List<Element> serverDoms = Servers.elements(); // 备用的服务器列表
List<Element> serverUsed = new ArrayList<Element>(); // 经检测,实际可用的服务器列表
TelnetClient telnet = new TelnetClient(); // 初始化Telnet对象,用来检测服务器是否可以成功连接
telnet.setConnectTimeout(5000); // 连接超时:5秒
for (Element serverTmp : serverDoms) {
try {
telnet.connect(serverTmp.attributeValue("host"),
Integer.parseInt(serverTmp
.attributeValue("post"))); // 连接到服务器
telnet.disconnect(); // 断开连接
serverUsed.add(serverTmp); // 连接成功,将服务器添加到实际可用列表
} catch (Exception e) {
}
}
int serverCount = serverUsed.size(); // 经检测,实际可用的服务器个数
if (serverCount == 0) { // 没有发现实际可用的服务器,返回
enUsed = false;
return;
}
serverListArr = new String[serverCount]; // 初始化服务器地址及端口号数组
weightListArr = new Integer[serverCount]; // 初始化服务器负载权重数组
for (int ind = 0; ind < serverCount; ind++) { // 向服务器数组进行赋值
serverListArr[ind] = serverUsed.get(ind).attributeValue(
"host")
+ ":" + serverUsed.get(ind).attributeValue("post");
weightListArr[ind] = Integer.parseInt(serverUsed.get(ind)
.attributeValue("weight").toString());
}
Object[] serverConfigArr = serverConfig.keySet().toArray(); // 返回服务器运行环境参数列表,用于遍历配置文件
for (Object cfgItem : serverConfigArr) {
Node node = Config.selectSingleNode("//property[@name='"
+ cfgItem + "']"); // 查找指定的参数节点
if (node == null)
continue; // 如果该参数节点不存在,则继续查找下一个参数,该参数将采用默认值
Element configNode = (Element) node;
serverConfig.put(cfgItem.toString(),
configNode.getTextTrim()); // 添加配置文件中定义的参数值
}
}
} catch (Exception e) {
logger.error("初始化MemCached运行环境出现异常...");
}
logger.info("初始化MemCached运行环境配置,结束...");
}
/**
* @category 保护型构造方法,不允许实例化!
*/
protected MemCachedUtil() {
}
/**
* @category 操作类入口:获取唯一实例.
*
* @return MemCached对象
*/
public static MemCachedUtil getInstance() {
return memCached;
}
/**
* @category 返回是否已经启用memcached内存服务器
*
* @return boolean
*/
public static boolean used() {
return enUsed;
}
/*
* /** 向缓存添加新的键值对。如果键已经存在,则之前的值将被替换。
*
* @param key 键
*
* @param value 值
*
* @return
*/
public boolean set(String key, Object value) {
return setExp(key, value, null);
}
/**
* 向缓存添加新的键值对。如果键已经存在,则之前的值将被替换。
*
* @param key
* 键
* @param value
* 值
* @param expire
* 过期时间 New Date(1000*10):十秒后过期
* @return
*/
public boolean set(String key, Object value, Date expire) {
return setExp(key, value, expire);
}
/**
* 向缓存添加新的键值对。如果键已经存在,则之前的值将被替换。
*
* @param key
* 键
* @param value
* 值
* @param expire
* 过期时间 New Date(1000*10):十秒后过期
* @return
*/
private boolean setExp(String key, Object value, Date expire) {
boolean flag = false;
if (!enUsed) {
// 未开启缓存或者没有可用缓存服务
return flag;
}
try {
flag = memClient.set(key, value, expire);
} catch (Exception e) {
// 记录Memcached日志
logger.error("set命令向缓存添加数据错误");
}
return flag;
}
/**
* 仅当缓存中不存在键时,add 命令才会向缓存中添加一个键值对。
*
* @param key
* 键
* @param value
* 值
* @return
*/
public boolean add(String key, Object value) {
return addExp(key, value, null);
}
/**
* 仅当缓存中不存在键时,add 命令才会向缓存中添加一个键值对。
*
* @param key
* 键
* @param value
* 值
* @param expire
* 过期时间 New Date(1000*10):十秒后过期
* @return
*/
public boolean add(String key, Object value, Date expire) {
return addExp(key, value, expire);
}
/**
* 仅当缓存中不存在键时,add 命令才会向缓存中添加一个键值对。
*
* @param key
* 键
* @param value
* 值
* @param expire
* 过期时间 New Date(1000*10):十秒后过期
* @return
*/
private boolean addExp(String key, Object value, Date expire) {
boolean flag = false;
if (!enUsed) {
// 未开启缓存或者没有可用缓存服务
return flag;
}
try {
flag = memClient.add(key, value, expire);
} catch (Exception e) {
// 记录Memcached日志
logger.error("add命令向缓存添加数据错误");
}
return flag;
}
/**
* 仅当键已经存在时,replace 命令才会替换缓存中的键。
*
* @param key
* 键
* @param value
* 值
* @return
*/
public boolean replace(String key, Object value) {
return replaceExp(key, value, null);
}
/**
* 仅当键已经存在时,replace 命令才会替换缓存中的键。
*
* @param key
* 键
* @param value
* 值
* @param expire
* 过期时间 New Date(1000*10):十秒后过期
* @return
*/
public boolean replace(String key, Object value, Date expire) {
return replaceExp(key, value, expire);
}
/**
* 仅当键已经存在时,replace 命令才会替换缓存中的键。
*
* @param key
* 键
* @param value
* 值
* @param expire
* 过期时间 New Date(1000*10):十秒后过期
* @return
*/
private boolean replaceExp(String key, Object value, Date expire) {
boolean flag = false;
if (!enUsed) {
// 未开启缓存或者没有可用缓存服务
return flag;
}
try {
flag = memClient.replace(key, value, expire);
} catch (Exception e) {
logger.error("replace 命令向缓存替换数据错误");
}
return flag;
}
/**
* get 命令用于检索与之前添加的键值对相关的值。
*
* @param key
* 键
* @return
*/
public Object get(String key) {
Object obj = null;
try {
obj = memClient.get(key);
} catch (Exception e) {
logger.error("get 命令从缓存获取数据错误");
}
return obj;
}
/**
* 删除 memcached 中的任何现有值。
*
* @param key
* 键
* @return
*/
public boolean delete(String key) {
return deleteExp(key, null);
}
/**
* 删除 memcached 中的任何现有值。
*
* @param key
* 键
* @param expire
* 过期时间 New Date(1000*10):十秒后过期
* @return
*/
public boolean delete(String key, Date expire) {
return deleteExp(key, expire);
}
/**
* 删除 memcached 中的任何现有值。
*
* @param key
* 键
* @param expire
* 过期时间 New Date(1000*10):十秒后过期
* @return
*/
private boolean deleteExp(String key, Date expire) {
boolean flag = false;
if (!enUsed) {
// 未开启缓存或者没有可用缓存服务
return flag;
}
try {
flag = memClient.delete(key, expire);
} catch (Exception e) {
logger.error("delete 命令从缓存删除数据错误");
}
return flag;
}
/**
* 清理缓存中的所有键/值对
*
* @return
*/
public boolean flashAll() {
boolean flag = false;
if (!enUsed) {
// 未开启缓存或者没有可用缓存服务
return flag;
}
try {
flag = memClient.flushAll();
} catch (Exception e) {
logger.error("flashAll 命令从缓存中清空数据错误");
}
return flag;
}
/**
* 使用示例
*/
public static void main(String[] args) {
/*// 初始化memcached操作类对象
MemCachedUtil cache = MemCachedUtil.getInstance();
* for (int i = 0; i < 10; i++) { //2min过期
* cache.set("userIdkey" + i,
* "headerPhoto"+i, new Date(1000*60*2)); }
// 验证memcached服务是否已启用
if (!cache.used()) {
System.out.println("memcached服务未启用!");
return;
}
System.out
.println("读取单条记录(get):\r\n===================================");
System.out.println("keyTest01:" + cache.get("keyTest01"));
System.out.println("keyTest02:" + cache.get("keyTest02"));
System.out.println("读取单条记录操作完成\r\n===================================");
for (int i = 0; i < 10; i++) {
// 2min过期
String key = "userIdkey" + i;
System.out.println(cache.get(key));
}*/
}
}
以上代码读取的配置文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<!--memcached数据库配置文件-->
<MemCachedConfig>
<!-- Enabled : 是否启用memcached内存数据库选项可选值 : true - 启用; false - 停用-->
<Enabled>true</Enabled>
<!-- Servers : 可用的memcached服务器列表,各服务器根据weight(负载权重值)实现分布式任务均衡
注意 : 各memcached服务器负载权重值的最大公约数最好为1,可在一定程度上简化其内部的负载均衡算法
规则 : <Server host="memcached服务器IP或域名" post="memcached服务端口(默认11211)" weight="负载权重值" />
-->
<Servers>
<Server host="127.0.0.1" post="11211" weight="1" />
</Servers>
<!--
Config : memcached数据库配置选项
initConn : 初始连接数
minConn : 最小连接数
maxConn : 最大连接数
maxIdle : 连接最大空闲时间(毫秒)
maintSleep : 主线程的维护周期(每隔多少秒维护一次连接池,0表示不启用主线程)
socketTO : 读取操作的超时限制(毫秒)
socketConnTO : 连接操作的超时限制(毫秒,0表示不限制)
compressEnable : 是否启用自动压缩(该参数从java_memcached-release_2.6.1开始不再支持)
compressThreshold : 超过指定大小(bytes)的数据都会被压缩(该参数从java_memcached-release_2.6.1开始不再支持)
-->
<Config>
<property name="initConn">5</property>
<property name="minConn">5</property>
<property name="maxConn">250</property>
<property name="maxIdle">21600000</property>
<property name="maintSleep">30</property>
<property name="socketTO">10000</property>
<property name="socketConnTO">0</property>
<property name="compressEnable">true</property>
<property name="compressThreshold">65536</property>
</Config>
</MemCachedConfig>
4、编写切面类,做aop的切面逻辑(用 注解代替 织入表达式使用起来更加灵活;只需要在调用方法上加入注解就可以了)
package com.memcached;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Date;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class anotationAspect {
//service层的切入点
@Pointcut("@annotation(com.memcached.ServiceMemcached)")
public void serviceCacheAspect(){}
//日志生成系统
private final Log logger = LogFactory.getLog(getClass());
@Around("serviceCacheAspect()")
public Object aroundService(ProceedingJoinPoint joinPoint) throws Throwable{
Object retVal = null;
StringBuilder memcachKey = new StringBuilder();
//获取参数
Object[] args = joinPoint.getArgs();
//缓存开关0:关闭 1:打开
int flag = Switch.cacheSwith;
if(flag == 1){
MemCachedUtil cache = MemCachedUtil.getInstance();
Signature signature = joinPoint.getSignature();
//全类名 com.xx.xx.javaClass
//String serviceClassName = signature.getDeclaringTypeName();
String serviceMethodName = signature.getName();
memcachKey.append(serviceMethodName);
//拼接完整的 uri+参数 ,获取参数类型数组
Class[] classArr = null;
if (args != null && args.length > 0) {
classArr = new Class[args.length];
int index = 0;
for (Object object : args) {
classArr[index] = object.getClass();
index++;
memcachKey.append("_").append(object.toString());
}
}
//获取方法上的注解
Method method = Class.forName(signature.getDeclaringTypeName()).getDeclaredMethod(serviceMethodName,classArr);
Annotation a = method.getAnnotation(ServiceMemcached.class);
//获取注解中的缓存有效期的秒数
int secends = ((ServiceMemcached)a).effectSecs();
if(memcachKey != null){
//获取缓存数据
//注意返回类型要实现Serializable接口,才能被序列化到缓存中
Object cachedObject = cache.get(memcachKey.toString());
if(cachedObject != null){
//获取到缓存则直接返回缓存数据
retVal = cachedObject;
if(logger.isDebugEnabled()){
logger.debug("缓存拦截:获取缓存数据");
}
}else{
retVal = ((ProceedingJoinPoint) joinPoint).proceed(args);
//设置缓存并且设置有效期(相对时间)
cache.set(memcachKey.toString(), retVal,new Date(secends*1000));
if(logger.isDebugEnabled()){
logger.debug("缓存拦截:未获得缓存数据,查询DB获取并放入缓存");
}
}
}
}else{
logger.info("缓存拦截:缓存开关关闭。");
//必须调用让被拦截的方法正常返回
retVal = ((ProceedingJoinPoint) joinPoint).proceed(args);
}
return retVal;
}
}
以上代码的作用介绍:
总所周知,springaop的经常的作用是在“被切的方法(piontCut)"方法前后做额外的自定义逻辑;在上面代码中,我们通过自定义注解,去引导aop切入(即被加上自定义注解的方法将会被aop拦截);拦截,结合memcahed工具类我们能干什么呢?于是我想到了缓存获取,当我们要获取某些数据的时候会先判断缓存中是否存在,如果存在则返回缓存中的数据,如果不存在则从数据库中获取并放入缓存,下次调用同样的方法,也是如此有缓存就可以直接从缓存获取...这样的场景就适合这种aop来完成(当然,过滤器和struts拦截器)也是可以实现的;
注意事项:memcahed的jar包必须和被序列化并且被缓存的类在相同的classpath下面,不然memcahedClient在获取缓存的时候会报错;