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
- AOP+自定义注解+memcached
- spring AOP 自定义注解
- Spring AOP自定义注解
- Spring AOP自定义注解 身份验证
- Spring Aop解析自定义注解
- Spring AOP自定义注解 身份验证
- 自定义AOP注解的应用
- spring自定义注解AOP实现
- aop注解 自定义切面的注解写法
- aop注解 自定义切面的注解写法
- java 自定义注解 spring aop 实现注解
- aop注解 自定义切面的注解写法
- aop+自定义注解实现操作日志记录
- aop+自定义注解实现操作日志记录
- 利用Spring AOP处理自定义注解
- Spring AOP+自定义注解实现缓存
- Spring AOP 自定义注解的实现
- Spring AOP与IOC以及自定义注解
- leetcode dynamic programming
- Android Studio 调试进阶用法
- node.js 使用 UglifyJS2 高效率压缩 javascript 文件
- excel数据生成txt逗号分隔文本及末尾添加新列
- 阅读《Android 从入门到精通》(8)——编辑框
- AOP+自定义注解+memcached
- Objective-C Runtime<1>
- android Canvas画布
- 【d3.js教程02】d3入门
- 【LVL1_7_c】【上机题】【1】使用动态数组,存放10个int类型数据,赋值,再遍历
- nginx rewrite
- [ 笔记 ] git 基础命令在学习
- jQuery源码分析11--插件接口的设计,可拓展性
- HTML5+CSS3的响应式网页设计:自动适应屏幕宽度