1、引入spring-data-redis依赖的jar 包
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.7.1.RELEASE</version> <exclusions> <exclusion> <artifactId>spring-tx</artifactId> <groupId>org.springframework</groupId> </exclusion> <exclusion> <artifactId>spring-context-support</artifactId> <groupId>org.springframework</groupId> </exclusion> <exclusion> <artifactId>spring-core</artifactId> <groupId>org.springframework</groupId> </exclusion> <exclusion> <artifactId>spring-aop</artifactId> <groupId>org.springframework</groupId> </exclusion> <exclusion> <artifactId>spring-context</artifactId> <groupId>org.springframework</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.8.1</version> </dependency>
2、添加缓存注解
package com.huatech.common.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 添加缓存 * @author lh * */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Cacheable { /** * 缓存key * @return */ public String key() default ""; /** * 缓存时效,默认无限期 * @return */ public long expire() default 0L; }
package com.huatech.common.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 缓存清除 * @author lh * @version 3.0 * @since 2016-8-28 * */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface CacheEvict { /** * 缓存key数组 * @return */ String[] keys() default ""; /** * 操作之间的缓存时间(秒) * @author FangJun * @date 2016年9月9日 * @return 默认0,不做限制 */ long interval() default 0; }
3、添加缓存操作工具类
package com.huatech.common.util; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @SuppressWarnings("rawtypes") public class ReflectionUtils { private static final String SETTER_PREFIX = "set"; private static final String GETTER_PREFIX = "get"; private static final String CGLIB_CLASS_SEPARATOR = "$$"; private static Logger logger = LoggerFactory.getLogger(ReflectionUtils.class); public static Object invokeGetMethod(Class<?> claszz, Object o, String name) { Object ret = null; try { Method method = claszz.getMethod("get" + StringUtil.firstCharUpperCase(name)); ret = method.invoke(o); } catch (Exception e) { logger.error(e.getMessage(),e); } return ret; } /** * 调用Getter方法. * 支持多级,如:对象名.对象名.方法 */ public static Object invokeGetter(Object obj, String propertyName) { Object object = obj; for (String name : StringUtils.split(propertyName, ".")){ String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name); object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {}); } return object; } /** * 调用Setter方法, 仅匹配方法名。 * 支持多级,如:对象名.对象名.方法 */ public static void invokeSetter(Object obj, String propertyName, Object value) { Object object = obj; String[] names = StringUtils.split(propertyName, "."); for (int i=0; i<names.length; i++){ if(i<names.length-1){ String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]); object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {}); }else{ String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]); invokeMethodByName(object, setterMethodName, new Object[] { value }); } } } /** * 调用Setter方法(赋值) * @param claszz * @param o * @param name * @param argType * @param args * @return */ public static Object invokeSetter(Class<?> claszz, Object o, String name, Class<?> argType, Object args) { Object ret = null; try { // 非 常量 进行反射 if (!checkModifiers(claszz, name)) { Method method = claszz.getMethod("set" + StringUtil.firstCharUpperCase(name), new Class[] { argType }); ret = method.invoke(o, new Object[] { args }); } } catch (Exception e) { logger.error("claszz:{},name:{},argType:{},args:{}",claszz,name,argType, args); } return ret; } /** * 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数. */ public static Object getFieldValue(final Object obj, final String fieldName) { Field field = getAccessibleField(obj, fieldName); if (field == null) { throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]"); } Object result = null; try { result = field.get(obj); } catch (IllegalAccessException e) { logger.error("不可能抛出的异常{}", e.getMessage()); } return result; } /** * 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数. */ public static void setFieldValue(final Object obj, final String fieldName, final Object value) { Field field = getAccessibleField(obj, fieldName); if (field == null) { throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]"); } try { field.set(obj, value); } catch (IllegalAccessException e) { logger.error("不可能抛出的异常:{}", e.getMessage()); } } /** * 直接调用对象方法, 无视private/protected修饰符. * 用于一次性调用的情况,否则应使用getAccessibleMethod()函数获得Method后反复调用. * 同时匹配方法名+参数类型, */ public static Object invokeMethod(final Object obj, final String methodName, final Class<?>[] parameterTypes, final Object[] args) { Method method = getAccessibleMethod(obj, methodName, parameterTypes); if (method == null) { throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]"); } try { return method.invoke(obj, args); } catch (Exception e) { throw convertReflectionExceptionToUnchecked(e); } } /** * 直接调用对象方法, 无视private/protected修饰符, * 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用. * 只匹配函数名,如果有多个同名函数调用第一个。 */ public static Object invokeMethodByName(final Object obj, final String methodName, final Object[] args) { Method method = getAccessibleMethodByName(obj, methodName); if (method == null) { throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]"); } try { return method.invoke(obj, args); } catch (Exception e) { throw convertReflectionExceptionToUnchecked(e); } } /** * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问. * * 如向上转型到Object仍无法找到, 返回null. */ public static Field getAccessibleField(final Object obj, final String fieldName) { Validate.notNull(obj, "object can't be null"); Validate.notBlank(fieldName, "fieldName can't be blank"); for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) { try { Field field = superClass.getDeclaredField(fieldName); makeAccessible(field); return field; } catch (NoSuchFieldException e) {//NOSONAR // Field不在当前类定义,继续向上转型 continue;// new add } } return null; } /** * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. * 如向上转型到Object仍无法找到, 返回null. * 匹配函数名+参数类型。 * * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) */ public static Method getAccessibleMethod(final Object obj, final String methodName, final Class<?>... parameterTypes) { Validate.notNull(obj, "object can't be null"); Validate.notBlank(methodName, "methodName can't be blank"); for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) { try { Method method = searchType.getDeclaredMethod(methodName, parameterTypes); makeAccessible(method); return method; } catch (NoSuchMethodException e) { // Method不在当前类定义,继续向上转型 continue;// new add } } return null; } /** * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. * 如向上转型到Object仍无法找到, 返回null. * 只匹配函数名。 * * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) */ public static Method getAccessibleMethodByName(final Object obj, final String methodName) { Validate.notNull(obj, "object can't be null"); Validate.notBlank(methodName, "methodName can't be blank"); for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) { Method[] methods = searchType.getDeclaredMethods(); for (Method method : methods) { if (method.getName().equals(methodName)) { makeAccessible(method); return method; } } } return null; } /** * 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 */ public static void makeAccessible(Method method) { if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !method.isAccessible()) { method.setAccessible(true); } } /** * 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 */ public static void makeAccessible(Field field) { if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier .isFinal(field.getModifiers())) && !field.isAccessible()) { field.setAccessible(true); } } /** * 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处 * 如无法找到, 返回Object.class. * eg. * public UserDao extends HibernateDao<User> * * @param clazz The class to introspect * @return the first generic declaration, or Object.class if cannot be determined */ @SuppressWarnings("unchecked") public static <T> Class<T> getClassGenricType(final Class clazz) { return getClassGenricType(clazz, 0); } /** * 通过反射, 获得Class定义中声明的父类的泛型参数的类型. * 如无法找到, 返回Object.class. * * 如public UserDao extends HibernateDao<User,Long> * * @param clazz clazz The class to introspect * @param index the Index of the generic ddeclaration,start from 0. * @return the index generic declaration, or Object.class if cannot be determined */ public static Class getClassGenricType(final Class clazz, final int index) { Type genType = clazz.getGenericSuperclass(); if (!(genType instanceof ParameterizedType)) { logger.warn("{}'s superclass not ParameterizedType",clazz.getSimpleName()); return Object.class; } Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); if (index >= params.length || index < 0) { logger.warn("Index: {}, Size of {}'s Parameterized Type: {}",index,clazz.getSimpleName(), params.length); return Object.class; } if (!(params[index] instanceof Class)) { logger.warn(" {} not set the actual class on superclass generic parameter",clazz.getSimpleName()); return Object.class; } return (Class) params[index]; } public static Class<?> getUserClass(Object instance) { if(instance == null){ throw new RuntimeException("Instance must not be null"); } Class clazz = instance.getClass(); if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) { Class<?> superClass = clazz.getSuperclass(); if (superClass != null && !Object.class.equals(superClass)) { return superClass; } } return clazz; } /** * 取得类中某个Field的类型名称 * @param clazz * @param fieldName * @return */ public static String getFieldTypeName(final Class clazz, String fieldName) { Field field = null; Class tclazz = clazz; do { try { field = tclazz.getDeclaredField(fieldName); } catch (NoSuchFieldException e) { tclazz = clazz.getSuperclass(); } catch (SecurityException e) { } } while (tclazz != Object.class && field == null); return (field == null)?null:field.getType().getSimpleName(); } /** * 将反射时的checked exception转换为unchecked exception. */ public static RuntimeException convertReflectionExceptionToUnchecked(Exception e) { if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException || e instanceof NoSuchMethodException) { return new IllegalArgumentException(e); } else if (e instanceof InvocationTargetException) { return new RuntimeException(((InvocationTargetException) e).getTargetException()); } else if (e instanceof RuntimeException) { return (RuntimeException) e; } return new RuntimeException("Unexpected Checked Exception.", e); } /** * object 属性名称及属性值组装为String字符串。 * 组装规则: * field.name1=field.value1&field.name2=field.value2 ... * @param object * @return */ public static String objToString(Object object) { Class<?> clazz = object.getClass(); Field[] fss = new Field[0]; for (; clazz != Object.class; clazz = clazz.getSuperclass()) { try { Field[] fs = clazz.getDeclaredFields(); fss = ArrayUtils.addAll(fss, fs); } catch (Exception e) { // 这里异常不能抛出去。 // 如果这里的异常打印或者往外抛,就不会执行clazz = clazz.getSuperclass(), // 最后就不会进入到父类中了 } } StringBuffer sb = new StringBuffer(50); for (Field f : fss) { // 反射对象中String类型,且不为常量的字段 if (String.class.equals(f.getType()) && !isConstant(f.getModifiers())) { String fieldName = f.getName(); Object o = invokeGetMethod(f.getDeclaringClass(), object, fieldName); String value = null==o?"":o.toString(); if (value == "") { continue; } sb.append(fieldName + "=" + value + "&"); } } logger.info("请求参数:"+sb.toString()); return sb.toString(); } /** * 是否为常量 * @param modifiers * @return 常量返回true,非常量返回false */ private static boolean isConstant(int modifiers) { // static 和 final修饰 if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)) { return true; } return false; } /** * 校验参数类型 * 目前只校验是否为 常量 * @param claszz * @param name * @return 常量返回true,非常量返回false */ private static boolean checkModifiers(Class<?> claszz, String name) { try { Field field = claszz.getField(name); if (isConstant(field.getModifiers())) { return true; } } catch (NoSuchFieldException | SecurityException e) { return false; } return false; } /** * 取得属性 * @param clazz * @return */ public static Map<String, Field> getClassField(Class<?> clazz) { Field[] declaredFields = clazz.getDeclaredFields(); Map<String, Field> fieldMap = new HashMap<String, Field>(); Map<String, Field> superFieldMap = new HashMap<String, Field>(); for (Field field : declaredFields) { fieldMap.put(field.getName(), field); } if (clazz.getSuperclass() != null) { superFieldMap = getClassField(clazz.getSuperclass()); } fieldMap.putAll(superFieldMap); return fieldMap; } }
package com.huatech.common.util; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.asm.*; import org.springframework.util.CollectionUtils; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 切面编程工具类 * @author lh * @version 3.0 * @since 2016-8-26 */ public class AopUtils { private static final Logger LOGGER = LoggerFactory.getLogger(AopUtils.class); private static final String DESC_DOUBLE = "D"; private static final String DESC_SHORT = "J"; private AopUtils() { } /** * <p>获取方法的参数名</p> * * @param m * @return */ public static String[] getMethodParamNames(final Method m) { final String[] paramNames = new String[m.getParameterTypes().length]; final String n = m.getDeclaringClass().getName(); final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); String className = m.getDeclaringClass().getSimpleName(); ClassReader cr = null; InputStream resourceAsStream = null; try { resourceAsStream = Class.forName(n).getResourceAsStream(className + ".class"); cr = new ClassReader(resourceAsStream); } catch (IOException | ClassNotFoundException e) { LOGGER.warn(e.getMessage(), e); } finally { if (resourceAsStream != null) { try { resourceAsStream.close(); } catch (IOException e) { LOGGER.warn(e.getMessage(), e); } } } if (cr != null) { cr.accept(new ClassVisitor(Opcodes.ASM4, cw) { @Override public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { final Type[] args = Type.getArgumentTypes(desc); // 方法名相同并且参数个数相同 if (!name.equals(m.getName()) || !sameType(args, m.getParameterTypes())) { return super.visitMethod(access, name, desc, signature, exceptions); } MethodVisitor v = cv.visitMethod(access, name, desc, signature, exceptions); return new MethodVisitor(Opcodes.ASM4, v) { int fixCount = 0;//步长修正计数器 @Override public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { int i = index - 1; // 如果是静态方法,则第一就是参数 // 如果不是静态方法,则第一个是"this",然后才是方法的参数 if (Modifier.isStatic(m.getModifiers())) { i = index; } if (i > fixCount) { i -= fixCount; } if(desc.equals(DESC_SHORT) || desc.equals(DESC_DOUBLE)){ fixCount++; } if (i >= 0 && i < paramNames.length) { paramNames[i] = name; } super.visitLocalVariable(name, desc, signature, start, end, index); } }; } }, 0); } return paramNames; } /** * <p>比较参数类型是否一致</p> * * @param types asm的类型({@link Type}) * @param clazzes java 类型({@link Class}) * @return */ private static boolean sameType(Type[] types, Class<?>[] clazzes) { // 个数不同 if (types.length != clazzes.length) { return false; } for (int i = 0; i < types.length; i++) { if (!Type.getType(clazzes[i]).equals(types[i])) { return false; } } return true; } /** * 取得切面调用的方法 * @param pjp * @return */ public static Method getMethod(ProceedingJoinPoint pjp){ Signature sig = pjp.getSignature(); if (!(sig instanceof MethodSignature)) { throw new IllegalArgumentException("该注解只能用于方法"); } MethodSignature msig = (MethodSignature) sig; Object target = pjp.getTarget(); Method currentMethod = null; try { currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes()); } catch (NoSuchMethodException | SecurityException e) { LOGGER.warn(e.getMessage(), e); } return currentMethod; } public static List<String> getMatcher(String regex, String source) { List<String> list = new ArrayList<>(); Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(source); while (matcher.find()) { list.add(matcher.group()); } return list; } /** * 取得注解参数 (?=exp) 匹配exp前面的位置 (?<=exp) 匹配exp后面的位置 (?!exp) 匹配后面跟的不是exp的位置 (?<!exp) 匹配前面不是exp的位置 * @param managers * @return */ public static List<String> getAnnoParams(String source){ String regex = "(?<=\\{)(.+?)(?=\\})"; return getMatcher(regex, source); } /** * 获取缓存的key值 * * @param pjp * @param key * @return */ public static String getCacheKey(final ProceedingJoinPoint pjp, final String key) { StringBuilder buf = new StringBuilder(); final Object[] args = pjp.getArgs(); if(StringUtils.isNotBlank(key)){ buf.append(key); List<String> annoParamNames = AopUtils.getAnnoParams(key); String[] methodParamNames = AopUtils.getMethodParamNames(AopUtils.getMethod(pjp)); if(!CollectionUtils.isEmpty(annoParamNames)){ for (String ap : annoParamNames) { buf = replaceParam(buf, args, methodParamNames, ap); } } }else{ buf.append(pjp.getSignature().getDeclaringTypeName()).append(":").append(pjp.getSignature().getName()); for (Object arg : args) { buf.append(":").append(arg.toString()); } } return buf.toString(); } /** * 替换占位参数 * @param buf * @param args * @param methodParamNames * @param ap * @return */ private static StringBuilder replaceParam(StringBuilder buf, final Object[] args, String[] methodParamNames, String ap) { StringBuilder builder = new StringBuilder(buf); String paramValue = ""; for (int i = 0; i < methodParamNames.length; i++) { if(ap.startsWith(methodParamNames[i])){ final Object arg = args[i]; if (ap.contains(".")) { paramValue = String.valueOf(ReflectionUtils.invokeGetter(arg, ap.substring(ap.indexOf('.') + 1))); } else { paramValue = String.valueOf(arg); } break; } } int start = builder.indexOf("{" + ap); int end = start + ap.length() + 2; builder =builder.replace(start, end, paramValue); return builder; } }
package com.huatech.common.util; public class StringUtil { /** * 首字母大写 * * @param s * @return */ public static String firstCharUpperCase(String s) { StringBuffer sb = new StringBuffer(s.substring(0, 1).toUpperCase()); sb.append(s.substring(1, s.length())); return sb.toString(); } }
package com.huatech.common.util; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.core.BoundHashOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.huatech.common.support.SpringContextHolder; /** * 通用缓存工具类 * @author lh * @version 3.0 * @since 2016-6-22 * */ public class CacheUtils { private static final Logger LOGGER = LoggerFactory.getLogger(CacheUtils.class); public static RedisTemplate<String, Object> redisTemplate = SpringContextHolder.getBean("redisTemplate"); public static StringRedisTemplate stringRedisTemplate = SpringContextHolder.getBean("stringRedisTemplate"); private static String redisKeyPrefix = PropertiesUtil.getValueByKey(CacheUtils.class.getResource("/").getPath() + "config/redis.properties", "redis.keyPrefix"); private CacheUtils() { } /** * 删除缓存<br> * 根据key精确匹配删除 * * @param key */ public static void del(String... key) { LOGGER.warn("delete cache, keys in ({})", merge(key)); for (String k : key) { redisTemplate.delete(appendKeyPrefix(k)); } } /** * 批量删除<br> * (该操作会执行模糊查询,请尽量不要使用,以免影响性能或误删) * * @param pattern */ public static void batchDel(String... pattern) { LOGGER.warn("batchDel cache, pattern in ({})", merge(pattern)); for (String kp : pattern) { redisTemplate.delete(redisTemplate.keys(appendKeyPrefix(kp) + "*")); } } /** * 取得缓存(int型) * * @param key * @return */ public static Integer getInt(String key) { String value = stringRedisTemplate.boundValueOps(appendKeyPrefix(key)).get(); if (StringUtils.isNotBlank(value)) { return Integer.valueOf(value); } return 0; } /** * 取得缓存(long型) * * @param key * @return */ public static Long getLong(String key) { String value = stringRedisTemplate.boundValueOps(appendKeyPrefix(key)).get(); if (StringUtils.isNotBlank(value)) { return Long.valueOf(value); } return 0l; } /** * 取得缓存(字符串类型) * * @param key * @return */ public static String getStr(String key) { return stringRedisTemplate.boundValueOps(appendKeyPrefix(key)).get(); } /** * 取得缓存(字符串类型) * * @param key * @return */ public static String getStr(String key, boolean retain) { String value = stringRedisTemplate.boundValueOps(appendKeyPrefix(key)).get(); if (!retain) { stringRedisTemplate.delete(appendKeyPrefix(key)); } return value; } /** * 获取缓存<br> * 注:基本数据类型(Character除外),请直接使用get(String key, Class<T> clazz)取值 * * @param key * @return */ public static Object getObj(String key) { return redisTemplate.boundValueOps(appendKeyPrefix(key)).get(); } /** * 获取缓存<br> * 注:java 8种基本类型的数据请直接使用get(String key, Class<T> clazz)取值 * * @param key * @param retain * 是否保留 * @return */ public static Object getObj(String key, boolean retain) { Object obj = redisTemplate.boundValueOps(appendKeyPrefix(key)).get(); if (!retain && obj != null) { redisTemplate.delete(appendKeyPrefix(key)); } return obj; } /** * 获取缓存<br> * 注:慎用java基本数据类型进行转换(可能会出现空值,转换报错) * * @param key * key * @param clazz * 类型 * @return */ @SuppressWarnings("unchecked") public static <T> T get(String key, Class<T> clazz) { key = appendKeyPrefix(key); if (clazz.equals(String.class)) { return (T) stringRedisTemplate.boundValueOps(key).get(); } else if (clazz.equals(Integer.class) || clazz.equals(Long.class)) { return (T) stringRedisTemplate.boundValueOps(key).get(); } else if (clazz.equals(Double.class) || clazz.equals(Float.class)) { return (T) stringRedisTemplate.boundValueOps(key).get(); } else if (clazz.equals(Short.class) || clazz.equals(Boolean.class)) { return (T) stringRedisTemplate.boundValueOps(key).get(); } return (T) redisTemplate.boundValueOps(key).get(); } /** * 将value对象写入缓存 * * @param key * @param value * @param seconds * 失效时间(秒) */ public static void set(String key, Object value, long seconds) { if (null == key || null == value) { throw new RuntimeException("key or value must not null"); } key = appendKeyPrefix(key); if (value instanceof String) { stringRedisTemplate.opsForValue().set(key, value.toString()); } else if (value instanceof Integer || value instanceof Long) { stringRedisTemplate.opsForValue().set(key, value.toString()); } else if (value instanceof Double || value instanceof Float) { stringRedisTemplate.opsForValue().set(key, value.toString()); } else if (value instanceof Short || value instanceof Boolean) { stringRedisTemplate.opsForValue().set(key, value.toString()); } else { redisTemplate.opsForValue().set(key, value); } if (seconds > 0) { redisTemplate.expire(key, seconds, TimeUnit.SECONDS); } } /** * 更新key对象field的值 * * @param key * 缓存key * @param field * 缓存对象field * @param value * 缓存对象field值 */ public static void setJsonField(String key, String field, String value) { JSONObject obj = JSON.parseObject(stringRedisTemplate.boundValueOps(appendKeyPrefix(key)).get()); obj.put(field, value); stringRedisTemplate.opsForValue().set(appendKeyPrefix(key), obj.toJSONString()); } /** * 递减操作 * * @param key * @param by * @return */ public static double decr(String key, double by) { return redisTemplate.opsForValue().increment(appendKeyPrefix(key), -by); } /** * 递增操作 * * @param key * @param by * @return */ public static double incr(String key, double by) { return redisTemplate.opsForValue().increment(appendKeyPrefix(key), by); } /** * 递减操作 * * @param key * @param by * @return */ public static long decr(String key, long by) { return redisTemplate.opsForValue().increment(appendKeyPrefix(key), -by); } /** * 递增操作 * * @param key * @param by * @return */ public static long incr(String key, long by) { return redisTemplate.opsForValue().increment(appendKeyPrefix(key), by); } /** * 获取double类型值 * * @param key * @return */ public static double getDouble(String key) { String value = stringRedisTemplate.boundValueOps(appendKeyPrefix(key)).get(); if (StringUtils.isNotBlank(value)) { return Double.valueOf(value); } return 0d; } /** * 设置double类型值 * * @param key * @param value * @param seconds * 失效时间(秒) */ public static void setDouble(String key, double value, long seconds) { stringRedisTemplate.opsForValue().set(appendKeyPrefix(key), String.valueOf(value)); if (seconds > 0) { stringRedisTemplate.expire(appendKeyPrefix(key), seconds, TimeUnit.SECONDS); } } /** * 将map写入缓存 * * @param key * @param map */ public static <T> void setMap(String key, Map<String, T> map) { redisTemplate.opsForHash().putAll(appendKeyPrefix(key), map); } /** * 向key对应的map中添加缓存对象 * * @param key * @param map */ public static <T> void addMap(String key, Map<String, T> map) { redisTemplate.opsForHash().putAll(appendKeyPrefix(key), map); } /** * 向key对应的map中添加缓存对象 * * @param key * cache对象key * @param field * map对应的key * @param value * 值 */ public static void addMap(String key, String field, String value) { redisTemplate.opsForHash().put(appendKeyPrefix(key), field, value); } /** * 向key对应的map中添加缓存对象 * * @param key * cache对象key * @param field * map对应的key * @param obj * 对象 */ public static <T> void addMap(String key, String field, T obj) { redisTemplate.opsForHash().put(appendKeyPrefix(key), field, obj); } /** * 获取map缓存 * * @param key * @param clazz * @return */ public static <T> Map<String, T> mget(String key, Class<T> clazz) { BoundHashOperations<String, String, T> boundHashOperations = redisTemplate.boundHashOps(appendKeyPrefix(key)); return boundHashOperations.entries(); } /** * 获取map缓存中的某个对象 * * @param key * @param field * @param clazz * @return */ @SuppressWarnings("unchecked") public static <T> T getMapField(String key, String field, Class<T> clazz) { return (T) redisTemplate.boundHashOps(appendKeyPrefix(key)).get(field); } /** * 删除map中的某个对象 * * @author lh * @date 2016年8月10日 * @param key * map对应的key * @param field * map中该对象的key */ public static void delMapField(String key, String... field) { redisTemplate.opsForHash().delete(appendKeyPrefix(key), field); } /** * 为哈希表key中的域field的值 * * @param key * 键 * @param field * map域 * @param value * 增量值 * @return */ public static long hincr(String key, String field, long value) { return redisTemplate.opsForHash().increment(appendKeyPrefix(key), field, value); } public static void hset(String key, String field, Object value){ redisTemplate.opsForHash().put(appendKeyPrefix(key), field, value); } public static Object hget(String key, String field){ return redisTemplate.boundHashOps(appendKeyPrefix(key)).get(field); } public static void hdel(String key, String...fields){ if (fields == null || fields.length == 0) { redisTemplate.delete(appendKeyPrefix(key)); }else{ redisTemplate.opsForHash().delete(appendKeyPrefix(key), fields); } } public static Long hlen(String key){ return redisTemplate.boundHashOps(appendKeyPrefix(key)).size(); } public static <T> Set<T> hkeys(String key){ return (Set<T>)redisTemplate.boundHashOps(appendKeyPrefix(key)).keys(); } public static <T> List<T> hvals(String key){ return (List<T>)redisTemplate.boundHashOps(appendKeyPrefix(key)).values(); } /** * * @param key * @param value * @param seconds * @return */ public static boolean setnx(String key, String value, long seconds) { boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(appendKeyPrefix(key), value); if (seconds > 0) { redisTemplate.expire(appendKeyPrefix(key), seconds, TimeUnit.SECONDS); } return flag; } /** * 指定缓存的失效时间 * * @author FangJun * @date 2016年8月14日 * @param key * 缓存KEY * @param seconds * 失效时间(秒) */ public static void expire(String key, long seconds) { redisTemplate.expire(appendKeyPrefix(key), seconds, TimeUnit.SECONDS); } /** * 指定缓存的失效时间 * * @author FangJun * @date 2016年8月14日 * @param key * 缓存KEY * @param seconds * 失效时间(秒) */ public static void expire(String key, int seconds) { redisTemplate.expire(appendKeyPrefix(key), seconds, TimeUnit.SECONDS); } /** * 添加set * * @param key * @param value */ public static void sadd(String key, String... value) { redisTemplate.boundSetOps(appendKeyPrefix(key)).add(value); } /** * 删除set集合中的对象 * * @param key * @param value */ public static void srem(String key, String... value) { redisTemplate.boundSetOps(appendKeyPrefix(key)).remove(value); } /** * 判断key对应的缓存是否存在 * * @param key * @return */ public static boolean exists(String key) { return redisTemplate.hasKey(appendKeyPrefix(key)); } /** * 模糊查询keys * @param pattern * @return */ public static Set<String> keys(String pattern){ return redisTemplate.keys(appendKeyPrefix(pattern)); } /** * 合并数组为字符串 * @param strings * @return */ private static String merge(String...strings){ if (strings == null || strings.length == 0) { return null; } StringBuffer sb = new StringBuffer(); int len = strings.length; for (int i = 0; i < len; i++) { sb.append(strings[i]); if(len != i+1){ sb.append(","); } } return sb.toString(); } private static String appendKeyPrefix(String key){ return redisKeyPrefix.concat(key); } }
4、增加AOP支持
package com.huatech.support.redis; import java.util.Set; import java.util.concurrent.TimeUnit; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import com.huatech.common.annotation.CacheEvict; import com.huatech.common.annotation.Cacheable; import com.huatech.common.util.AopUtils; import com.huatech.common.util.CacheUtils; /** * 缓存操作切面 * 注意:一个支持缓存的方法,在对象内部被调用是不会触发缓存功能的。 * @author lh * */ @Aspect @Component public class CacheAspect { /** * 缓存清除标识前缀 */ public static final String KEY_PREFIX_CACHE_EVICT="cacheEvict:interval:"; public static final String REDIS_CACHE_SUPPORT = "1"; private static final Logger LOGGER = LoggerFactory.getLogger(CacheAspect.class); @SuppressWarnings("rawtypes") @Autowired private RedisTemplate redisTemplate; @Value("${redis.cacheSupport}") private String redisCacheSupport; @Value("${redis.keyPrefix}") private String redisKeyPrefix; /** * 启用新的get方法,防止缓存被“击穿” * <p> * 击穿 :缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来, * 这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。 * 如何解决:业界比较常用的做法,是使用mutex。 * 简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成 * 功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行 * load db的操作并回设缓存;否则,就重试整个get缓存的方法。 * </p> * @param key * @param pjp * @param cache * @return * @throws Throwable */ private Object get(final String key, final ProceedingJoinPoint pjp, final Cacheable cache) throws Throwable { @SuppressWarnings("unchecked") ValueOperations<String, Object> valueOper = redisTemplate.opsForValue(); Object value = valueOper.get(key); // 从缓存获取数据 if (value == null) { // 代表缓存值过期 // 设置2min的超时,防止del操作失败的时候,下次缓存过期一直不能load db String keynx = key.concat(":nx"); if (CacheUtils.setnx(keynx, "1", 5)) { // 代表设置成功 value = pjp.proceed(); if (cache.expire() <= 0) { // 如果没有设置过期时间,则无限期缓存 valueOper.set(key, value); } else { // 否则设置缓存时间 valueOper.set(key, value, cache.expire(), TimeUnit.SECONDS); } CacheUtils.del(keynx); return value; } else { // 这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可 Thread.sleep(10); return get(key, pjp, cache); // 重试 } } else { return value; } } /** * 添加缓存 * @param pjp * @param cache * @return * @throws Throwable */ @Around("@annotation(cache)") public Object cacheable(final ProceedingJoinPoint pjp, Cacheable cache)throws Throwable { if(REDIS_CACHE_SUPPORT.equals(redisCacheSupport)){ String key = redisKeyPrefix.concat(AopUtils.getCacheKey(pjp, cache.key())); return get(key, pjp, cache); } return pjp.proceed(); } @Around("@annotation(evict)") public Object cacheEvict(final ProceedingJoinPoint pjp, CacheEvict evict)throws Throwable { if(REDIS_CACHE_SUPPORT.equals(redisCacheSupport)){ Object value = pjp.proceed(); //清除keys对应的缓存数据 if (evict.keys() != null && evict.keys().length > 0) { for (String keyname : evict.keys()) { evictByKeyname(pjp, keyname,evict.interval()); } } return value; } return pjp.proceed(); } @SuppressWarnings("unchecked") private void evictByKeyname(final ProceedingJoinPoint pjp, final String keyname, long interval) { final String key = redisKeyPrefix.concat(AopUtils.getCacheKey(pjp, keyname)); //操作间隔判断 if (interval != 0) { final String intervalKey = KEY_PREFIX_CACHE_EVICT + key; if (CacheUtils.incr(intervalKey, 1) > 1) { return; } CacheUtils.expire(intervalKey, interval); } LOGGER.info("cacheEvict, key={}", key); //使用redisTemplate操作缓存 if (keyname.equals(key)) {// 支持批量删除 Set<String> keys = redisTemplate.keys(key.concat("*")); if (!CollectionUtils.isEmpty(keys)) { redisTemplate.delete(keys); } } else { redisTemplate.delete(key); } } }
5、扩展spring-data-redis,简化配置
package com.huatech.support.redis; import static org.springframework.util.Assert.isTrue; import static org.springframework.util.Assert.notNull; import static org.springframework.util.StringUtils.split; import org.apache.commons.lang3.StringUtils; import org.springframework.data.redis.connection.RedisClusterConfiguration; import org.springframework.data.redis.connection.RedisNode; /** * <p>RedisCluster配置</p> * <p>用于简化spring-data-redis配置</p> * @author lh * @version 3.0 * @since 2017-4-5 * */ public class RedisClusterConfig extends RedisClusterConfiguration{ public RedisClusterConfig(String nodes, Integer maxRedirects) { super(); initNodes(nodes); setMaxRedirects(maxRedirects); } private void initNodes(String nodes){ if(StringUtils.isBlank(nodes)){ throw new RuntimeException("nodes can not be empty!"); } String[] hostAndPorts = nodes.split(","); for (String hostAndPort : hostAndPorts) { addClusterNode(readHostAndPortFromString(hostAndPort)); } } private RedisNode readHostAndPortFromString(String hostAndPort) { String[] args = split(hostAndPort, ":"); notNull(args, "HostAndPort need to be seperated by ':'."); isTrue(args.length == 2, "Host and Port String needs to specified as host:port"); return new RedisNode(args[0], Integer.valueOf(args[1]).intValue()); } }
package com.huatech.support.redis; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; /** * Jedis Single Connection factory * @author lh * */ public class RedisSingleConnFactory extends JedisConnectionFactory { public RedisSingleConnFactory(String address,int timeout, String password, redis.clients.jedis.JedisPoolConfig poolConfig) { String[] hostAndPorts = address.split(":"); setHostName(hostAndPorts[0]); setPort(Integer.valueOf(hostAndPorts[1])); setTimeout(timeout); setPassword(password); setPoolConfig(poolConfig); } public RedisSingleConnFactory(String address, String password, redis.clients.jedis.JedisPoolConfig poolConfig) { String[] hostAndPorts = address.split(":"); setHostName(hostAndPorts[0]); setPort(Integer.valueOf(hostAndPorts[1])); setPassword(password); setPoolConfig(poolConfig); } public RedisSingleConnFactory(String address, redis.clients.jedis.JedisPoolConfig poolConfig) { String[] hostAndPorts = address.split(":"); setHostName(hostAndPorts[0]); setPort(Integer.valueOf(hostAndPorts[1])); setPoolConfig(poolConfig); } }
6、添加redis配置文件:redis.properties
#redis settings redis.cacheSupport=1 redis.keyPrefix=bds_ #redis pool settings redis.pool.maxTotal=60000 redis.pool.maxIdle=300 redis.pool.testOnBorrow=true #redis 单机配置 spring.redis.single.node=172.16.90.30:6379 spring.redis.single.timeout=8000 spring.redis.single.password=redis #redis 集群 spring.redis.cluster.maxRedirects=3 spring.redis.cluster.nodes=192.168.42.128:6379,192.168.42.128:6380,192.168.42.129:6379,192.168.42.129:6380,192.168.42.130:6379,192.168.42.130:6380
7、在applicationContext.xml文件中添加redis配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" 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-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd" > <description>Redis Configuration</description> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="order" value="1" /> <property name="ignoreUnresolvablePlaceholders" value="true" /> <property name="locations"> <list> <value>classpath:/mybatis/jdbc.properties</value> <value>classpath:/config/redis.properties</value> </list> </property> </bean> <aop:aspectj-autoproxy/> <context:component-scan base-package="com.huatech.support"/> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxIdle" value="${redis.pool.maxIdle}" /> <!-- 最大能够保持idel状态的对象数 --> <property name="maxTotal" value="${redis.pool.maxTotal}" /> <!-- 最大分配的对象数 --> <property name="testOnBorrow" value="${redis.pool.testOnBorrow}" /> <!-- 当调用borrow Object方法时,是否进行有效性检查 --> </bean> <!-- ======================单机配置 start ====================== --> <!-- spring-data-redis 单机配置 --> <bean id="jedisConnFactory" class="com.huatech.support.redis.RedisSingleConnFactory" > <constructor-arg name="address" value="${spring.redis.single.node}"/> <constructor-arg name="timeout" type="int" value="${spring.redis.single.timeout}" /> <constructor-arg name="password" value="${spring.redis.single.password}" /> <constructor-arg name="poolConfig" ref="jedisPoolConfig" /> </bean> <!-- ======================单机配置 end ====================== --> <!-- ======================cluster配置 start ====================== --> <!-- spring-data-redis-cluster <bean id="redisClusterConfiguration" class="com.huatech.support.redis.RedisClusterConfig" > <constructor-arg value="${spring.redis.cluster.nodes}"/> <constructor-arg value="${spring.redis.cluster.maxRedirects}" /> </bean> <bean id="jedisConnFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:use-pool="true"> <constructor-arg ref="redisClusterConfiguration" /> <constructor-arg ref="jedisPoolConfig" /> </bean> --> <!-- ======================cluster配置 end ====================== --> <bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer" /> <bean id ="jdkSerializationRedisSerializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/> <bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate" p:connectionFactory-ref="jedisConnFactory" /> <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connectionFactory-ref="jedisConnFactory" p:keySerializer-ref="stringRedisSerializer" p:hashKeySerializer-ref="stringRedisSerializer" p:valueSerializer-ref="jdkSerializationRedisSerializer" /> </beans>
8、系统中添加缓存支持
@Override @Cacheable(key="sc:code:{configCode}", expire = 0) public SystemConfig findByConfigCode(String configCode) { return systemConfigDao.findByConfigCode(configCode); }
9、简单测试
import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.rd.bds.core.common.util.CacheUtils; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:/spring/spring-config.xml", "classpath:/spring/spring-redis.xml" }) public class CacheUtilsTest { @Test public void testString()throws Exception{ String key = "email"; CacheUtils.set(key, "lh@erongdu.com", 20); System.out.println(key+":"+CacheUtils.getStr(key)); Thread.sleep(1500); System.out.println(key+":"+CacheUtils.getStr(key)); Thread.sleep(1500); System.out.println(key+":"+CacheUtils.getStr(key)); } }
import javax.annotation.Resource; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.rd.bds.core.service.SystemConfigService; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:/spring/spring-config.xml", "classpath:/spring/spring-redis.xml" }) public class SystemConfigServiceImplTest { @Resource private SystemConfigService systemConfigService; @Test public void test()throws Exception{ systemConfigService.findByConfigCode("invest_award_limit"); Thread.sleep(100L); systemConfigService.findByConfigCode("invest_award_limit"); Thread.sleep(100L); systemConfigService.findByConfigCode("invest_award_limit"); } }
相关推荐
包含翻译后的API文档:spring-session-data-redis-2.0.4.RELEASE-javadoc-API文档-中文(简体)-英语-对照版.zip; Maven坐标:org.springframework.session:spring-session-data-redis:2.0.4.RELEASE; 标签:spring...
包含翻译后的API文档:spring-data-redis-2.3.9.RELEASE-javadoc-API文档-中文(简体)版.zip; Maven坐标:org.springframework.data:spring-data-redis:2.3.9.RELEASE; 标签:springframework、data、spring、redis...
包含翻译后的API文档:spring-session-data-redis-2.0.4.RELEASE-javadoc-API文档-中文(简体)版.zip; Maven坐标:org.springframework.session:spring-session-data-redis:2.0.4.RELEASE; 标签:springframework、...
包含翻译后的API文档:spring-data-redis-2.6.1-javadoc-API文档-中文(简体)版.zip; Maven坐标:org.springframework.data:spring-data-redis:2.6.1; 标签:spring、data、springframework、redis、jar包、java、...
包含翻译后的API文档:spring-data-redis-2.0.9.RELEASE-javadoc-API文档-中文(简体)-英语-对照版.zip; Maven坐标:org.springframework.data:spring-data-redis:2.0.9.RELEASE; 标签:springframework、data、...
包含翻译后的API文档:spring-data-redis-2.5.5-javadoc-API文档-中文(简体)-英语-对照版.zip; Maven坐标:org.springframework.data:spring-data-redis:2.5.5; 标签:spring、data、springframework、redis、jar...
包含翻译后的API文档:spring-data-redis-2.5.5-javadoc-API文档-中文(简体)版.zip; Maven坐标:org.springframework.data:spring-data-redis:2.5.5; 标签:springframework、data、spring、redis、中文文档、jar...
6. **Key的生命周期管理**:通过`ExpireOperations`,开发者可以方便地设置键的过期时间,以实现缓存等场景的需求。 7. **事件监听**:Spring Data Redis支持事件监听,可以监听Redis中的某些操作(如键的创建、...
包含翻译后的API文档:spring-data-redis-2.0.6.RELEASE-javadoc-API文档-中文(简体)版.zip; Maven坐标:org.springframework.data:spring-data-redis:2.0.6.RELEASE; 标签:springframework、data、spring、redis...
spring-data-redis-1.8.1.RELEASE-sources.jar(spring-data-redis-1.8.1.RELEASE-sources.jar()
spring-data-redis-2.0.0.M1.jar
包含翻译后的API文档:spring-data-redis-1.7.5.RELEASE-javadoc-API文档-中文(简体)-英语-对照版.zip 对应Maven信息:groupId:org.springframework.data,artifactId:spring-data-redis,version:1.7.5.RELEASE ...
包含翻译后的API文档:spring-data-redis-2.0.9.RELEASE-javadoc-API文档-中文(简体)版.zip; Maven坐标:org.springframework.data:spring-data-redis:2.0.9.RELEASE; 标签:springframework、data、spring、redis...
commons-pool2-2.3.jar,jedis-2.8.0.jar,spring-data-redis-1.6.0.RELEASE.jar,spring-session-1.1.1.RELEASE.jar,Spring-data-redis(Version 1.6.0.RC1)中文版.pdf
包含翻译后的API文档:spring-data-redis-2.6.1-javadoc-API文档-中文(简体)-英语-对照版.zip; Maven坐标:org.springframework.data:spring-data-redis:2.6.1; 标签:spring、data、springframework、redis、jar...
spring-data-redis-1.4.1.RELEASE
包含翻译后的API文档:spring-data-redis-1.7.5.RELEASE-javadoc-API文档-中文(简体)版.zip 对应Maven信息:groupId:org.springframework.data,artifactId:spring-data-redis,version:1.7.5.RELEASE 使用方法...
spring-data-redis-1.6.2.RELEASE.jar,官网文档,免费提供,亲测可用
在SpringMVC中集成Spring Data Redis,可以利用Redis的高效特性来提升应用程序的数据处理能力,例如作为session共享的存储、缓存数据或者实现发布/订阅(Pub/Sub)功能。发布/订阅是一种通信模式,允许发送者(pub)将...
基于spring的子项目spring-data-redis写的一个基于hash类型的用户CRUD,经过简单的封装,可以实现通用CRUD,请事先安装好redis,配置文件redis.properties请做相应修改,希望对你有帮助。