使用Spring AOP注解实现Redis缓存 适合复杂业务场合
来源:互联网 发布:数据标准方案 编辑:程序博客网 时间:2024/06/07 21:53
之前转载过一篇博客
http://blog.csdn.net/massivestars/article/details/50548006
里面有个缺点,切入的方法参数类型要完全一致,若方法的参数定义为Map,传值为HashMap则会报错
为了灵活配置拦截的方法,aop使用xml配置.
定义时间片段的枚举
package org.massive.redis.constant;/** * Created by Massive on 2016/1/9. */public enum DateUnit { SECONDS,MINUTES,HOURS,DAYS,MONTHS,YEARS}
Cacheable的注解
@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface Cacheable { // 类目 用于定义是以什么开头的 String category() default ""; // 要用来解释的key值 String key(); // 过期时间数值,默认-1为永久 int expire() default -1; // 时间单位,默认为秒 DateUnit dateUnit() default DateUnit.SECONDS;}
Aop拦截类
package org.massive.redis.aop;import org.apache.log4j.Logger;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.reflect.MethodSignature;import org.massive.redis.annotation.Cacheable;import org.massive.redis.constant.DateUnit;import org.massive.redis.constant.SystemCacheProperties;import org.massive.redis.util.AopUtils;import org.massive.redis.util.RedisAccess;import org.massive.redis.util.SpringExpressionUtils;import org.springframework.util.StringUtils;import java.lang.reflect.Method;/** * Created by Massive on 2016/1/9. */public class CacheAopAspect { private final static Logger log = Logger.getLogger(CacheAopAspect.class); RedisAccess redisAccess; public RedisAccess getRedisAccess() { return redisAccess; } public void setRedisAccess(RedisAccess redisAccess) { this.redisAccess = redisAccess; } public Object doCacheable(ProceedingJoinPoint pjp) throws Throwable { Object result=null; Method method = AopUtils.getMethod(pjp); Cacheable cacheable = method.getAnnotation(Cacheable.class); Boolean isCacheEnable = "enable".equals(SystemCacheProperties.getProperty("system.cache.enable")); if(cacheable != null && !isCacheEnable) { log.debug("没有开启缓存"); } //----------------------------------------------------------------------- // 如果拦截的方法中没有Cacheable注解 // 或者system.cache.enable的开关没打开 // 则直接执行方法并返回结果 //----------------------------------------------------------------------- if (cacheable == null || !isCacheEnable) { try { result = pjp.proceed(); } catch (Throwable e) { e.printStackTrace(); } return result; } String key = cacheable.key(); //---------------------------------------------------------- // 用SpEL解释key值 //---------------------------------------------------------- String keyVal = SpringExpressionUtils.parseKey(key, method, pjp.getArgs()); if (!StringUtils.isEmpty(cacheable.category())){ keyVal = cacheable.category() + "_" + keyVal; } else { //---------------------------------------------------------- // 如果cacheable的注解中category为空取 类名+方法名 //---------------------------------------------------------- keyVal = pjp.getTarget().getClass().getSimpleName() + "_" + method.getName() + "_" + keyVal; } Class returnType = ((MethodSignature)pjp.getSignature()).getReturnType(); //----------------------------------------------------------------------- // 从redis读取keyVal,并且转换成returnType的类型 //----------------------------------------------------------------------- result = redisAccess.get(keyVal, returnType); if (result == null) { try { //----------------------------------------------------------------------- // 如果redis没有数据则执行拦截的方法体 //----------------------------------------------------------------------- result = pjp.proceed(); int expireSeconds = 0; //----------------------------------------------------------------------- // 如果Cacheable注解中的expire为默认(默认值为-1) // 并且systemCache.properties中的system.cache.expire.default.enable开关为true // 则取system.cache.expire.default.seconds的值为缓存的数据 //----------------------------------------------------------------------- if (cacheable.expire() == -1 && "enable".equals(SystemCacheProperties.getProperty("system.cache.expire.default.enable"))) { expireSeconds = new Integer(SystemCacheProperties.getProperty("system.cache.expire.default.seconds")); } else { expireSeconds = getExpireSeconds(cacheable); } //----------------------------------------------------------------------- // 把拦截的方法体得到的数据设置进redis,过期时间为计算出来的expireSeconds //----------------------------------------------------------------------- redisAccess.set(keyVal, result, expireSeconds); log.debug("已缓存缓存:key=" + keyVal); } catch (Throwable e) { e.printStackTrace(); } return result; } log.debug("========从缓存中读取"); log.debug("=======:key = " + key); log.debug("=======:keyVal= " + keyVal); log.debug("=======:val = " + result); return result; } /** * 计算根据Cacheable注解的expire和DateUnit计算要缓存的秒数 * @param cacheable * @return */ public int getExpireSeconds(Cacheable cacheable) { int expire = cacheable.expire(); DateUnit unit = cacheable.dateUnit(); if (expire <= 0) { return 0; } if (unit == DateUnit.MINUTES) { return expire * 60; } else if(unit == DateUnit.HOURS) { return expire * 60 * 60; } else if(unit == DateUnit.DAYS) { return expire * 60 * 60 * 24; } else if(unit == DateUnit.MONTHS) { return expire * 60 * 60 * 24 * 30; } else if(unit == DateUnit.YEARS) { return expire * 60 * 60 * 24 * 365; } return expire; }}
redis的访问类
package org.massive.redis.util;import com.alibaba.fastjson.JSONObject;import org.springframework.stereotype.Repository;import redis.clients.jedis.Jedis;import redis.clients.jedis.JedisPool;/** * Created by Massive on 2016/1/9. */public class RedisAccess { private JedisPool jedisPool; public JedisPool getJedisPool() { return jedisPool; } public void setJedisPool(JedisPool jedisPool) { this.jedisPool = jedisPool; } public void set(String key,Object o,Integer seconds){ Jedis jedis = jedisPool.getResource(); jedis.set(key, JSONObject.toJSONString(o)); if (seconds != null && seconds > 0) { jedis.expire(key,seconds); } jedis.close(); } public String get(String key) { Jedis jedis = jedisPool.getResource(); String text = jedis.get(key); jedis.close(); return text; } public <T> T get(String key,Class<T> clazz){ String text = get(key); T result = JSONObject.parseObject(text, clazz); return result; } public void del(String key) { Jedis jedis = jedisPool.getResource(); jedis.del(key); jedis.close(); } /** * 清空某个DB的数据 */ public void flushDB() { Jedis jedis = jedisPool.getResource(); jedis.flushDB(); jedis.close(); }}
AopUtils类
package org.massive.redis.util;import org.aspectj.lang.ProceedingJoinPoint;import java.lang.reflect.Method;/** * Created by Massive on 2016/8/11. */public class AopUtils { /** * 获取被拦截方法对象 * MethodSignature.getMethod() 获取的是顶层接口或者父类的方法对象 * 而缓存的注解在实现类的方法上 * 所以应该使用反射获取当前对象的方法对象 * @param pjp * @return * @throws NoSuchMethodException */ public static Method getMethod(ProceedingJoinPoint pjp) throws NoSuchMethodException { //-------------------------------------------------------------------------- // 获取参数的类型 //-------------------------------------------------------------------------- Object[] args = pjp.getArgs(); Class[] argTypes = new Class[pjp.getArgs().length]; for (int i = 0; i < args.length; i++) { argTypes[i] = args[i].getClass(); } String methodName = pjp.getSignature().getName(); Class<?> targetClass = pjp.getTarget().getClass(); Method[] methods = targetClass.getMethods(); //-------------------------------------------------------------------------- // 查找Class<?>里函数名称、参数数量、参数类型(相同或子类)都和拦截的method相同的Method //-------------------------------------------------------------------------- Method method = null; for (int i = 0; i < methods.length; i++){ if (methods[i].getName() == methodName){ Class<?>[] parameterTypes = methods[i].getParameterTypes(); boolean isSameMethod = true; // 如果相比较的两个method的参数长度不一样,则结束本次循环,与下一个method比较 if (args.length != parameterTypes.length) { continue; } //-------------------------------------------------------------------------- // 比较两个method的每个参数,是不是同一类型或者传入对象的类型是形参的子类 //-------------------------------------------------------------------------- for (int j = 0;parameterTypes != null && j < parameterTypes.length ;j++) { if (parameterTypes[j] != argTypes[j] && !parameterTypes[j].isAssignableFrom(argTypes[j])) { isSameMethod = false; break; } } if (isSameMethod) { method = methods[i]; break; } } } return method; }}
SPEL表达解释工具类
package org.massive.redis.util;import org.springframework.core.LocalVariableTableParameterNameDiscoverer;import org.springframework.expression.ExpressionParser;import org.springframework.expression.ParserContext;import org.springframework.expression.common.TemplateParserContext;import org.springframework.expression.spel.standard.SpelExpressionParser;import org.springframework.expression.spel.support.StandardEvaluationContext;import java.lang.reflect.Method;/** * Created by Massive on 2016/8/11. */public class SpringExpressionUtils { /** * 获取缓存的key * key 定义在注解上,支持SPEL表达式 * 注: method的参数支持Javabean和Map * method的基本类型要定义为对象,否则没法读取到名称 * * example1: * Phone phone = new Phone(); * "#{phone.cpu}" 为对象的取值 * example2: * Map apple = new HashMap(); apple.put("name","good apple"); * "#{apple[name]}" 为map的取值 * example3: * "#{phone.cpu}_#{apple[name]}" * * @param key * @param method * @param args * @return */ public static String parseKey(String key, Method method, Object[] args) { //获取被拦截方法参数名列表(使用Spring支持类库) LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer(); String[] paraNameArr = u.getParameterNames(method); //使用SPEL进行key的解析 ExpressionParser parser = new SpelExpressionParser(); //SPEL上下文 StandardEvaluationContext context = new StandardEvaluationContext(); //把方法参数放入SPEL上下文中 for (int i = 0; i < paraNameArr.length; i++) { context.setVariable(paraNameArr[i], args[i]); } ParserContext parserContext = new TemplateParserContext(); //---------------------------------------------------------- // 把 #{ 替换成 #{# ,以适配SpEl模板的格式 //---------------------------------------------------------- Object returnVal = parser.parseExpression(key.replace("#{","#{#"), parserContext).getValue(context, Object.class); return returnVal == null ? null: returnVal.toString(); }}
systemCache.properties
# ==============================================# =============== REDIS CONFIG ===============# ==============================================# parameters for redis.clients.jedis.JedisPoolConfigredis.pool.maxTotal=50redis.pool.maxIdle=10redis.pool.maxWaitMillis=1000redis.pool.testOnBorrow=true# parameters from redis.clients.jedis.JedisPoolredis.server.host=121.40.211.170redis.server.port=6379redis.server.timeout=2000redis.server.auth=lxm_binf_mmm_asd#===============================================# ==============================================# =============== CACHE OPTIONS ===============# ==============================================# determine the cache is opened or not# options: enable,disablesystem.cache.enable=enable# redis database range is 1-16system.cache.database=3# determine the cache expire is opened or not# options: enable,disablesystem.cache.expire.default.enable=enable# The time cache existssystem.cache.expire.default.seconds=3600#===============================================
applicationContext.xml
<?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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd" default-autowire="byType"><context:property-placeholder file-encoding="utf-8" location="classpath*:systemCache.properties"/><!-- 开启使用注解注入bean --><context:annotation-config/><!-- 扫描base-package定义的目录,注解注入bean --><context:component-scan base-package="org.massive"/><bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"><property name="maxTotal" value="${redis.pool.maxTotal}" /><property name="maxIdle" value="${redis.pool.maxIdle}" /><property name="maxWaitMillis" value="${redis.pool.maxWaitMillis}" /><property name="testOnBorrow" value="${redis.pool.testOnBorrow}" /></bean><bean id="jedisPool" class="redis.clients.jedis.JedisPool"><constructor-arg index="0" ref="jedisPoolConfig"/><constructor-arg index="1" value="${redis.server.host}" /><constructor-arg index="2" value="${redis.server.port}"/><constructor-arg index="3" value="${redis.server.timeout}"/><constructor-arg index="4" value="${redis.server.auth}"/><constructor-arg index="5" value="${system.cache.database}"/></bean><!-- redis访问类 --><bean id="redisAccess" class="org.massive.redis.util.RedisAccess"><property name="jedisPool" ref="jedisPool"/></bean><bean id="cacheAopAspect" class="org.massive.redis.aop.CacheAopAspect"><property name="redisAccess" ref="redisAccess"/></bean><!-- 这里使用不使用注解是为了灵活配置要拦截的方法,使用注解的话要修改源代码 --><!-- 强烈推荐使用xml配置的方式 --><!-- 拦截所有org.massive.*.service 和 org.massive.*.cache 包下所有的方法 --><aop:config proxy-target-class="true"><aop:aspect ref="cacheAopAspect"><aop:pointcut id="doCacheAopPointcut" expression="(execution(* org.massive.*.service.*.*(..))or execution(* org.massive.*.cache.*.*(..)))"/><aop:around pointcut-ref="doCacheAopPointcut" method="doCacheable"/></aop:aspect></aop:config><bean id="cacheAopEvict" class="org.massive.redis.aop.CacheAopEvict"><property name="redisAccess" ref="redisAccess"/></bean><!-- 拦截所有org.massive.*.service 和 org.massive.*.cache 包下所有的方法 --><aop:config proxy-target-class="true"><aop:aspect ref="cacheAopEvict"><aop:pointcut id="doCacheEvictPointcut" expression="(execution(* org.massive.*.service.*.*(..))or execution(* org.massive.*.cache.*.*(..)))"/><aop:around pointcut-ref="doCacheEvictPointcut" method="doCacheEvict"/></aop:aspect></aop:config></beans>
下面编写测试用例进行测试
定义一个测试用的Javabean
package org.massive.redis.samples;/** * Created by Massive on 2016/1/10. */public class Player { public String userName; public int age; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Player{" + "userName='" + userName + '\'' + ", age=" + age + '}'; }}
service类,被AopCacheAspect拦截
package org.massive.redis.service;import org.massive.redis.annotation.CacheEvict;import org.massive.redis.annotation.Cacheable;import org.massive.redis.constant.DateUnit;import org.massive.redis.samples.Player;import org.springframework.stereotype.Service;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;/** * Created by Massive on 2016/1/9. */@Servicepublic class CacheAopTestService { /** * 参数为基本类型 * @param pageNo * @param pageSize * @return */ @Cacheable(key = "#{pageNo}_#{pageSize}") public List getPrimitiveData(Integer pageNo, Integer pageSize) { Player player = new Player(); player.setUserName("massive"); List list = new ArrayList(); list.add(player); return list; } /** * 方法参数为Javabean,缓存200秒 * @param player * @return */ @Cacheable(category="player",key="#{player.userName}",expire = 200) public Player getBeanData(Player player) { System.out.println("this is redis bean test..."); return player; } /** * 方法参数为Map;expire = 1,dateUnit = DateUnit.HOURS 缓存一小时 * @param phone * @return */ @Cacheable(category = "forMapTest",key = "#{phone[cpu]}_#{phone[ram]}",expire = 1,dateUnit = DateUnit.HOURS) public Map getMapData(Map phone) { System.out.println("this is redis map test..."); return phone; } /** * 方法参数为复合类型,包括Javabean,Map,Integer等,缓存永存时间 * @param player * @param phone * @param pageNo * @param pageSize * @return */ @Cacheable(category = "mix",key = "#{player.userName}_#{phone[cpu]}_#{phone[ram]}_#{pageNo}_#{pageSize}") public List<Map> getMixData(Player player,Map phone,Integer pageNo,Integer pageSize) { Map map = new HashMap(); map.put("type","mix"); List<Map> list = new ArrayList<Map>(); list.add(map); list.add(phone); return list; } @CacheEvict(category = "forTest",key = "#{map[userName]}") public Map updateMapData(Map map) { System.out.println("this is evict map test..."); return map; }}
测试类
package org.massive.redis.test;import org.junit.Test;import org.junit.runner.RunWith;import org.massive.redis.samples.Player;import org.massive.redis.service.CacheAopTestService;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import redis.clients.jedis.Jedis;import redis.clients.jedis.JedisPool;import javax.annotation.Resource;import java.util.HashMap;import java.util.Map;/** * Created by Massive on 2016/1/9. */@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations={"classpath*:applicationContext.xml"})public class CacheAopTest { @Resource private JedisPool jedisPool; @Resource private CacheAopTestService cacheAopTestService; @Test public void testConnection() { Jedis jedis =jedisPool.getResource(); jedis.set("___mobile", "1388888888"); System.out.println(jedis.get("mobile")); } /** * 测试method的参数为基本类型 */ @Test public void testPrimitive() { cacheAopTestService.getPrimitiveData(1,10); } //第一次输出 //已缓存缓存:key=CacheAopTestService_getPrimitiveData_1_10 //第二次输出 //<========从缓存中读取> //<=======:key = #{pageNo}_#{pageSize}> //<=======:keyVal= CacheAopTestService_getPrimitiveData_1_10> //<=======:val = [{"age":0,"userName":"massive"}]> /** * 测试method的参数为Javabean */ @Test public void testJavaBean() { Player player = new Player(); player.setUserName("Stephen Curry"); player.setAge(27); cacheAopTestService.getBeanData(player); System.out.println(player); } //第一次输出 //<已缓存缓存:key=player_Stephen Curry //第二次输出 //<========从缓存中读取> //<=======:key = #{player.userName}> //<=======:keyVal= player_Stephen Curry> //<=======:val = Player{userName='Stephen Curry', age=27}> /** * 测试method的参数为Map */ @Test public void testMap() { Map phone = new HashMap(); phone.put("cpu","Intel"); phone.put("ram","4GB"); cacheAopTestService.getMapData(phone); } //第一次输出 //已缓存缓存:key=forMapTest_Intel_4GB //第二次输出 //<========从缓存中读取> //<=======:key = #{phone[cpu]}_#{phone[ram]}> //<=======:keyVal= forMapTest_Intel_4GB> //<=======:val = {ram=4GB, cpu=Intel}> /** * 测试method的参数和返回都是混合且复杂 */ @Test public void testMix() { Map phone = new HashMap(); phone.put("cpu","Intel"); phone.put("ram","4GB"); Player player = new Player(); player.setUserName("Curry"); player.setAge(27); cacheAopTestService.getMixData(player,phone,1,100); } //第一次输出 //已缓存缓存:key=mix_Curry_Intel_4GB_1_100 //第二次输出 //<========从缓存中读取> //<=======:key = #{player.userName}_#{phone[cpu]}_#{phone[ram]}_#{pageNo}_#{pageSize}> //<=======:keyVal= mix_Curry_Intel_4GB_1_100> //<=======:val = [{"type":"mix"}, {"ram":"4GB","cpu":"Intel"}]>}
1 0
- 使用Spring AOP注解实现Redis缓存 适合复杂业务场合
- 深入理解Spring Redis的使用 (六)、用Spring Aop 实现注解Dao层的自动Spring Redis缓存
- 使用AOP 实现Redis缓存注解,支持SPEL(转)
- 使用AOP 实现Redis缓存注解,支持SPEL
- 使用AOP 实现Redis缓存注解,支持SPEL
- Spring AOP+自定义注解实现缓存
- spring aop实现注解式缓存
- 使用注解实现Spring aop
- spring使用注解实现AOP
- Redis与spring整合缓存的业务场景使用方法二(使用注解@Cacheable@CacheEvict)
- spring aop结合redis实现数据缓存
- 使用 AOP 和注解实现方法缓存
- Spring AOP实现复杂的日志记录(自定义注解)
- Spring Cache+Redis实现自定义注解缓存
- spring集成redis缓存的注解实现
- 深入理解Spring Redis的使用 (八)、Spring Redis实现 注解 自动缓存
- aop切面和redis实现自定义缓存注解
- java注解使用redis缓存,@Aspect aop @interface
- 03.Sublime Text 3 快捷键
- iOS-申请邓白氏编码的超详细流程介绍--申请苹果公司开发者账号流程所需
- Android图片框架对比Universal-Image-Loader,android-Volley,Picasso、Fresco和Glide五大Android开源组件加载网络图片的优缺点比较
- iOS中APP加急上线加急审核
- 高可用性hadoop集群问题
- 使用Spring AOP注解实现Redis缓存 适合复杂业务场合
- javascript数组的知识点讲解
- Spring+CXF 实现类无法注入属性的问题
- jenkins学习
- 欢迎使用CSDN-markdown编辑器
- mysql日期搜索的边界问题
- 通过项目逐步深入了解Mybatis<三>
- Node服务一键离线部署
- test code