没有废话,以spring注解的形式整合redis缓存

spring-redis.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:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:cache="http://www.springframework.org/schema/cache"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-4.2.xsd
                        http://www.springframework.org/schema/mvc
                        http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
                        http://www.springframework.org/schema/cache
                        http://www.springframework.org/schema/cache/spring-cache-4.2.xsd">

    <!-- redis 相关配置 -->
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxTotal" value="2048"/>
        <property name="maxIdle" value="16"/>
        <property name="numTestsPerEvictionRun" value="1024"/>
        <property name="timeBetweenEvictionRunsMillis" value="30000"/>
        <property name="minEvictableIdleTimeMillis" value="-1"/>
        <property name="softMinEvictableIdleTimeMillis" value="10000"/>
        <property name="maxWaitMillis" value="1500"/>
        <property name="testOnBorrow" value="true"/>
        <property name="testWhileIdle" value="true"/>
        <property name="testOnReturn" value="false"/>
        <property name="jmxEnabled" value="true"/>
        <property name="jmxNamePrefix" value="youyuan"/>
        <property name="blockWhenExhausted" value="false"/>
    </bean>

    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="${redis.hostName}"/>
        <property name="port" value="6379"/>
        <property name="timeout" value="${redis.timeout}"/>
        <property name="password" value="${redis.password}"/>
        <property name="usePool" value="${redis.usePool}"/>
        <property name="poolConfig" ref="poolConfig"/>
    </bean>
    <!-- 工具类1 -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="jedisConnectionFactory"/>
        <!--配置序列化方式,防止乱码-->
        <!-- key使用String序列化方式 -->
        <property name="keySerializer" ref="keySerializer"/>
        <!-- value使用String序列化 -->
        <property name="hashKeySerializer" ref="keySerializer"/>
        <!-- value使用json序列化 -->
        <property name="valueSerializer" ref="jackson2JsonRedisSerializer"/>
        <!--<property name="hashValueSerializer" ref="jackson2JsonRedisSerializer"/>-->
        <property name="hashValueSerializer" ref="jdkSerializer"/>
    </bean>
    <bean id="keySerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
    <bean id="jackson2JsonRedisSerializer" class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
    <bean id="jdkSerializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />

    <!-- 工具类2 -->
    <bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
        <property name="connectionFactory" ref="jedisConnectionFactory"></property>
    </bean>
    <!-- spring自己的缓存管理器,这里定义了缓存位置名称 ,即注解中的value -->
    <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
        <property name="caches">
            <set>
                <bean class="cn.kingomgr.common.cache.RedisCache">
                    <property name="redisTemplate" ref="redisTemplate"/>
                    <property name="name" value="common"/>
                </bean>
            </set>
        </property>
    </bean>
</beans>

RedisCache类中的内容:

// An highlighted block
package cn.kingomgr.common.cache;


import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;

import java.io.*;
import java.util.concurrent.Callable;

public class RedisCache implements Cache {

private RedisTemplate<String, Object> redisTemplate;
    private String name;
    public RedisTemplate<String, Object> getRedisTemplate() {
        return redisTemplate;
    }

    public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        // TODO Auto-generated method stub
        return this.name;
    }

    @Override
    public Object getNativeCache() {
        // TODO Auto-generated method stub
        return this.redisTemplate;
    }

    @Override
    public ValueWrapper get(Object key) {
        // TODO Auto-generated method stub
        System.out.println("get key");
        final String keyf =  key.toString();
        Object object = null;
        object = redisTemplate.execute(new RedisCallback<Object>() {
            public Object doInRedis(RedisConnection connection)
                    throws DataAccessException {
                byte[] key = keyf.getBytes();
                byte[] value = connection.get(key);
                if (value == null) {
                    return null;
                }
                return toObject(value);
            }
        });
        return (object != null ? new SimpleValueWrapper(object) : null);
    }

    @Override
    public void put(Object key, Object value) {
        // TODO Auto-generated method stub
        System.out.println("put key");
        final String keyf = key.toString();
        final Object valuef = value;
        final long liveTime = 86400;
        redisTemplate.execute(new RedisCallback<Long>() {
            public Long doInRedis(RedisConnection connection)
                    throws DataAccessException {
                byte[] keyb = keyf.getBytes();
                byte[] valueb = toByteArray(valuef);
                connection.set(keyb, valueb);
                if (liveTime > 0) {
                    connection.expire(keyb, liveTime);
                }
                return 1L;
            }
        });
    }

    private byte[] toByteArray(Object obj) {
        byte[] bytes = null;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(obj);
            oos.flush();
            bytes = bos.toByteArray();
            oos.close();
            bos.close();
        }catch (IOException ex) {
            ex.printStackTrace();
        }
        return bytes;
    }

    private Object toObject(byte[] bytes) {
        Object obj = null;
        try {
            ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
            ObjectInputStream ois = new ObjectInputStream(bis);
            obj = ois.readObject();
            ois.close();
            bis.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        } catch (ClassNotFoundException ex) {
            ex.printStackTrace();
        }
        return obj;
    }

    @Override
    public void evict(Object key) {
        // TODO Auto-generated method stub
        System.out.println("del key");
        final String keyf = key.toString();
        redisTemplate.execute(new RedisCallback<Long>() {
            public Long doInRedis(RedisConnection connection)
                    throws DataAccessException {
                return connection.del(keyf.getBytes());
            }
        });
    }

    @Override
    public void clear() {
        // TODO Auto-generated method stub
        System.out.println("clear key");
        redisTemplate.execute(new RedisCallback<String>() {
            public String doInRedis(RedisConnection connection)
                    throws DataAccessException {
                connection.flushDb();
                return "ok";
            }
        });
    }

    @Override
    public <T> T get(Object key, Class<T> type) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public <T> T get(Object o, Callable<T> callable) {
        return null;
    }

    @Override
    public ValueWrapper putIfAbsent(Object key, Object value) {
        // TODO Auto-generated method stub
        return null;
    }

}

在applicationContext.xml文件中引入spring-redis.xml配置文件即可,并且在主配置文件applicationContext.xml中声明
<cache:annotation-driven cache-manager=“cacheManager” />
才会生效。

applicationContext.xml内容:

	<!-- 启用缓存注解功能,这个是必须的,否则注解不会生效,另外,该注解一定要声明在spring主配置文件中才会生效 -->
	<cache:annotation-driven cache-manager="cacheManager" />
	<!--省略其他配置-->
	<!-- 导入redis的配置文件 -->
	<import resource="spring-redis.xml"/>

在实现类中使用注解:

@Service
public class UserServiceImpl implements UserService{
 
	@Autowired
	private UserBo userBo;
 
	@Cacheable(value="common",key="'id_'+#id")
	public User selectByPrimaryKey(Integer id) {
		return userBo.selectByPrimaryKey(id);
	}
	
	@CachePut(value="common",key="#user.getUserName()")
	public void insertSelective(User user) {
		userBo.insertSelective(user);
	}
 
	@CacheEvict(value="common",key="'id_'+#id")
	public void deleteByPrimaryKey(Integer id) {
		userBo.deleteByPrimaryKey(id);
	}
}

注意
1.将@Cacheable(value=“common”,key="‘id_’+#id")变为@Cacheable(value=“common”,key="+#id")。仅仅是去掉了 ‘id_’,就会报错:

java.io.StreamCorruptedException: invalid stream header: 63346537

问题待解决。
2.@Cacheable(value=“common”,key="‘id_’+#id")中的value一定要取RedisCache中的name对应的value。否则会出现以下错误:

java.lang.IllegalArgumentException: Cannot find cache named 'xx' for Builder
    <!-- spring自己的缓存管理器,这里定义了缓存位置名称 ,即注解中的value -->
    <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
        <property name="caches">
            <set>
                <!-- 这里可以配置多个redis -->
                <!-- <bean class="com.cn.util.RedisCache">
                     <property name="redisTemplate" ref="redisTemplate" />
                     <property name="name" value="default"/>
                </bean> -->
                <bean class="cn.kingomgr.common.cache.RedisCache">
                    <property name="redisTemplate" ref="redisTemplate"/>
                    <property name="name" value="common"/>
                </bean>
            </set>
        </property>
    </bean>

补充:Spring缓存注解@Cacheable、@CacheEvict、@CachePut使用.

上述注解的缺陷:
1.@CacheEvict:无法删除所有以"id_"为前缀的key。
2.@Cacheable:不能自定义缓存过期时间。

针对以上问题,提出解决方案如下:
自定义注解:
@ExtCacheable注解

/**
 * 扩展Cacheable注解
 * 用于用户自定义redis缓存过期时间
 *
 * @author : yuanjinqiao
 * @date : 11:57 2020/6/2
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtCacheable {
    /**
     * 使用方法在方法上使用@ExtCacheable(key="测试+#P0 + P1#...")
     * 表示键值为:测试+方法第一个参数+方法第二个参数,值为该方法的返回值。
     *
     * @return
     */
    String key() default "";

    //String nextKey() default "";
    //过期时间
    int expireTime() default 1800;//30分钟
}

@ExtCacheEvict注解

/**
 * 扩展CacheEvict注解
 * 使用key进行模糊匹配删除redis缓存数据
 *
 * @author : yuanjinqiao
 * @date : 9:53 2020/6/2
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtCacheEvict {
    /**
     * 需要清除的key,可以模糊匹配
     *
     * @return
     */
    String[] key() default {};
}

RedisCacheAspect切面类:

package cn.kingomgr.common.aop;

        import lombok.extern.java.Log;
        import org.aspectj.lang.JoinPoint;
        import org.aspectj.lang.ProceedingJoinPoint;
        import org.aspectj.lang.annotation.AfterReturning;
        import org.aspectj.lang.annotation.Around;
        import org.aspectj.lang.annotation.Aspect;
        import org.aspectj.lang.annotation.Pointcut;
        import org.aspectj.lang.reflect.MethodSignature;

        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.data.redis.core.RedisTemplate;
        import org.springframework.stereotype.Component;

        import java.lang.reflect.Method;
        import java.util.ArrayList;
        import java.util.List;
        import java.util.Set;
        import java.util.concurrent.TimeUnit;

/**
 * 功能描述:redis缓存切面类
 *
 * @author jasin
 * DATE 2019/9/25.
 */
@Component
@Aspect
@Log
public class RedisCacheAspect {
    public RedisCacheAspect() {
        log.info("........................................................");
        log.info(".........清除缓存切面类CacheRemoveAspect已加载.............");
        log.info("........................................................");
    }

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    //截获标有@ExtCacheEvict的方法
    @Pointcut(value = "execution(* *.*(..) )&& @annotation(cn.kingomgr.common.aop.ExtCacheEvict))")
    private void ExtCacheEvictAspect() {
    }

    //截获标有@ExtCacheEvict的方法
    @Pointcut(value = "execution(* *.*(..) )&& @annotation(cn.kingomgr.common.aop.ExtCacheable))")
    private void ExtCacheableAspect() {
    }


    /**
     * 功能描述: 根据方法注解上的key模糊匹配删除
     *
     * @return void
     * @throws
     * @author fuyuchao
     * @date 2018/9/14 16:55
     * @params [joinPoint]
     */
    @AfterReturning(value = "ExtCacheEvictAspect()")
    private void doAfterReturningExtCacheEvictAspect(JoinPoint joinPoint) {
        //获取被代理的类
        Object target = joinPoint.getTarget();
        //获取切入方法的数据
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //获取切入方法
        Method method = signature.getMethod();
        //获得注解
        ExtCacheEvict extCacheEvict = method.getAnnotation(ExtCacheEvict.class);

        if (extCacheEvict != null) {
            //需要移除的正则key
            String[] keys = extCacheEvict.key();
            for (String key : keys) {
                //清除指定清除的key的缓存
                cleanRedisCache("*" + key + "*");
            }
        }
    }

    /**
     * 功能描述: 根据方法注解上的key以及过期时间expireTime缓存数据
     * @param joinPoint
     */
    @Around(value = "ExtCacheableAspect()")
    private Object doAroundExtCacheableAspect(ProceedingJoinPoint joinPoint) {
        // 获得当前访问的class
        Class<?> className = joinPoint.getTarget().getClass();
        // 获得访问的方法名
        String methodName = joinPoint.getSignature().getName();
        // 得到方法的参数的类型
        Class<?>[] argClass = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
        Object[] args = joinPoint.getArgs();
        String key = "";
        int expireTime = 1800;
        try {
            // 得到访问的方法对象
            Method method = className.getMethod(methodName, argClass);
            method.setAccessible(true);
            // 判断是否存在@ExtCacheable注解
            if (method.isAnnotationPresent(ExtCacheable.class)) {
                ExtCacheable annotation = method.getAnnotation(ExtCacheable.class);
                key = getRedisKey(args,annotation);
                expireTime = annotation.expireTime();
            }
        } catch (Exception e) {
            throw new RuntimeException("redis缓存注解参数异常", e);
        }
        // 获取缓存是否存在
        boolean hasKey = redisTemplate.hasKey(key);
        if (hasKey) {
            return redisTemplate.opsForValue().get(key);
        } else {
            //执行原方法(java反射执行method获取结果)
            Object res = null;
            try {
                res = joinPoint.proceed();
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
            //设置缓存以及设置过期时间
            redisTemplate.opsForValue().set(key, res, expireTime, TimeUnit.SECONDS);
            return res;
        }
    }

    private String getRedisKey(Object[] args, ExtCacheable annotation) {
        String primalKey = annotation.key();
        //获取#p0...集合
        List<String> keyList = getKeyParsList(primalKey);
        for (String keyName : keyList) {
            int keyIndex = Integer.parseInt(keyName.toLowerCase().replace("#p", ""));
            Object parValue = args[keyIndex];
            primalKey = primalKey.replace(keyName, String.valueOf(parValue));
        }
        return primalKey.replace("+", "").replace("'", "");
    }

    // 获取key中#p0中的参数名称
    private static List<String> getKeyParsList(String key) {
        List<String> ListPar = new ArrayList<String>();
        if (key.indexOf("#") >= 0) {
            int plusIndex = key.substring(key.indexOf("#")).indexOf("+");
            int indexNext = 0;
            String parName = "";
            int indexPre = key.indexOf("#");
            if (plusIndex > 0) {
                indexNext = key.indexOf("#") + key.substring(key.indexOf("#")).indexOf("+");
                parName = key.substring(indexPre, indexNext);
            } else {
                parName = key.substring(indexPre);
            }
            ListPar.add(parName.trim());
            key = key.substring(indexNext + 1);
            if (key.indexOf("#") >= 0) {
                ListPar.addAll(getKeyParsList(key));
            }
        }
        return ListPar;
    }

    private void cleanRedisCache(String key) {
        if (key != null) {
            Set<String> stringSet = redisTemplate.keys(key);
            redisTemplate.delete(stringSet);//删除缓存
            log.info("清除 " + key + " 缓存");
        }
    }
}

最后在applicationContext.xml中配置扫描切面类:

	<context:component-scan base-package="cn.kingomgr.common.aop"/>

注意: 若切面在service层不起作用可能原因是:
如果只是监听service层函数,那么在spring的xml配置文件配置切面代理即可 如果需要监听controller层,涉及到springmvc,所以需要配置springmvc的配置文件开启切面代理。
在applicationContext.xml和springmvc.xml下都配置如下代码:

<aop:aspectj-autoproxy proxy-target-class="true" />
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值