AOP+自定义注解+memcached

来源:互联网 发布:嵌入式软件测试工具 编辑:程序博客网 时间:2024/05/18 03:53

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)@Documentedpublic @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@Aspectpublic 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在获取缓存的时候会报错;








0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 吃羊肉后上火了怎么办 羊肉吃多了上火怎么办 小孩吃糖牙齿坏怎么办 一岁宝宝吐奶怎么办 一个月宝宝吐奶怎么办 2个月里小孩好哭怎么办 两个月宝宝闹觉怎么办 6岁儿童视力0.5怎么办 单一的三系减少怎么办 血小板低到50该怎么办 放化疗后白细胞低怎么办 化疗后白细胞低发烧怎么办 全程c反应蛋白高怎么办 儿童c反应蛋白高怎么办 c反应蛋白高是怎么办 新生儿c反蛋白高怎么办 c反应蛋白高发烧怎么办 血沉高到50了怎么办啊 血沉和超敏偏高怎么办 孕37周血糖偏高怎么办 孕37周血糖7.0多怎么办 孕妇超敏crp偏高怎么办 高敏c反应蛋白高怎么办 孕17周尿蛋白高怎么办 血小板低到20该怎么办 血象高发烧39度怎么办 新生儿血象3万多怎么办 血象高发烧不退怎么办 半岁宝宝血象高怎么办 5-6小孩免疫力差怎么办 快速c反应蛋白高怎么办 15个月宝宝发烧怎么办 小孩发烧到40度怎么办 孩子发烧到39度怎么办 宝宝抵抗力差总生病怎么办 献血前没休息好怎么办 拔了牙齿一直流血怎么办 拔牙后血块掉了怎么办 生血功能不强怎么办 孕妇白球比偏低怎么办 凝血因子Ⅷ很高怎么办