AOP+自定义注解+memcached

版权声明:本文为博主原创文章,未经博主允许不得转载。 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在获取缓存的时候会报错;








猜你喜欢

转载自blog.csdn.net/PYXLY1314/article/details/50427427