redis+aop实现数据缓存

redis+aop实现数据缓存

在ssm项目下首先使用spring集成redis
我用的是maven项目,先导入必要的依赖。这里的集成也是花了点时间,因为jar版本冲突的问题,tomcat启动老是找不到jar。用对了版本之后,还是有异常,就去project structure查看了一下,发现直接在pom.xml添加的jar并没有被添加到打包项目的lib中。。。,导入之后就好了
这个是集成redis的相关依赖,我的spring版本用的是4.2.5.RELEASE

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>1.5.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.4.2</version>
        </dependency>

然后修改spring-mybaties.xml 配置文件,新建一个spring-redis.xml也可以,新建的记得添加到web.xml中,让web容器加载。

spring-mybaties.xml
添加如下

 <!-- redis config start -->
    <!-- 配置JedisPoolConfig实例 -->
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxIdle" value="${redis.maxIdle}"></property>
        <property name="minIdle" value="${redis.minIdle}"></property>
        <property name="maxTotal" value="${redis.maxTotal}"></property>
        <property name="maxWaitMillis" value="${redis.maxWaitMillis}"></property>
        <property name="testOnBorrow" value="${redis.testOnBorrow}"></property>
    </bean>
    <!-- redis连接池 -->
    <bean id="jedisPool" class="redis.clients.jedis.JedisPool" destroy-method="close">
        <constructor-arg name="poolConfig" ref="poolConfig"/>
        <constructor-arg name="host" value="${redis.host}"/>
        <constructor-arg name="port" value="${redis.port}"/>
    </bean>
    <!-- 配置JedisConnectionFactory -->
    <bean id="jedisConnectionFactory"
          class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="${redis.host}"/>
        <property name="port" value="${redis.port}"/>
        <property name="password" value="${redis.password}"/>
        <property name="poolConfig" ref="poolConfig"/>
    </bean>

    <!-- 配置RedisTemplate -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
          p:connection-factory-ref="jedisConnectionFactory">
        <!--以下针对各种数据进行序列化方式的选择-->
        <property name="keySerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>
        <property name="valueSerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>
        <property name="hashKeySerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>
        <!--<property name="hashValueSerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
        </property>-->
    </bean>
redis.host =127.0.0.1
redis.port =6379
redis.password =123456
redis.maxIdle=300
redis.minIdle=100
redis.maxWaitMillis=3000
redis.testOnBorrow=false
redis.maxTotal=500

这时如果能正常启动说明,配置没错。

接下来是配置aop切点,也是配置在spring-mybatis.xml中,这里的切面是service实现层中所有的get方法。我的本意是缓存所有get方法,然后也可以再controller调用的时候选择不缓存,这个再后面的代码中,可以实现控制。

    <bean id="aop" class="com.onelove.untils.AopAspect"/>

    <aop:config proxy-target-class="true">
        <aop:aspect ref="aop">
            <aop:pointcut id="getPointcut" expression="execution(* com.onelove.service.*.get*(..))"/>
            <aop:around method="caching" pointcut-ref="getPointcut"/>
        </aop:aspect>
    </aop:config>

然后,就是核心的代码,AopAspest.class,这个类中实现了在aop切面执行方法caching中使用redis的操作api jedis去控制数据的添加、获取,以及数据的过期时间

@Aspect
@Component
public class AopAspect {
    @Resource(name = "redisTemplate")
    private RedisTemplate redisTemplate;
    @Resource(name = "jedisPool")
    private JedisPool jedisPool;
    @Resource(name = "jedisConnectionFactory")
    JedisConnectionFactory jedisConnectionFactory;

    public Object caching(ProceedingJoinPoint joinPoint) throws Throwable {
        String redisKey = null;
        Object[] args = joinPoint.getArgs();//切点方法中的参数,比如get(User user),args.size=1,args[0]=user。get(User user,String aa),args.size=2
        //这里在所有的service接口和impl实现方法中添加了一个String op的参数,这个参数是从controller层中传递下来的
        //op这个参数作为redis中的key,保证key不会重复,并且含义可知。比如获取首页的数据,op = index,获取列表数据 op = articleList
        for (int i = 0; i < args.length&&args!=null; i++) {
            //get方法中,我的项目里,尽量使用实体类去实现查询,所以参数中避免出现有String的类型
            if (args[i] instanceof String) {
                redisKey = (String) args[i];
                break;
            }
        }
        //实际编码过程中,如果save插入数据库时,不同步一份到redis的情况下,有些数据是不能缓存存储的
        //比如我写了一篇文章,save到mysql中了,这是我点开文章列表(用redis缓存了),这时发现没有刚才编辑的文章
        //有些数据如果需要时时更新,要么同步一份到redis,要么就每次都查询数据库,不走redis
        //所以在这里使用 op = "" 来限制,不走redis
        //含有op操作,则查询
        if (redisKey!=null&&!redisKey.equals("")) {
            //获取切点方法的名称
            String methodName = joinPoint.getSignature().getName();
            //getDataFromRedis中根据key拿取数据
            Object objectFromRedis = getDataFromRedis(redisKey,methodName);
            //判断数据是否是list集合
            if(objectFromRedis instanceof List<?>){
                //数据是list集合,判断size是否大于0,如果大于直接返回结果。否则再次查询数据库
                if(((List<?>)objectFromRedis).size()>0){
                    return objectFromRedis;
                }
            }else{
                if (null != objectFromRedis) {
                    return objectFromRedis;
                }
            }
        }
        Object object = null;
        try {
            //执行数据库操作
            object = joinPoint.proceed();
        } catch (Throwable e) {

            e.printStackTrace();
        }
        //数据库操作执行完,redisKey不为空,才保存数据到redis
        if (redisKey!=null&&!redisKey.equals("")) {
            setDataToRedis(redisKey, object);
        }
        return object;
    }
    //从redis缓存中查询,反序列化
    public Object getDataFromRedis(String redisKey,String methodName) {
        //查询
        Object result=null;
        //Jedis连接池中获取jedis对象
        Jedis jedis = jedisPool.getResource();
        //这是我的redis密码,redis没有密码的可以去掉。redis设置密码的可以看我其他的博客,在liunx下redis
        jedis.auth("123456");
        byte[] byteData = jedis.get(redisKey.getBytes());
        //这里通过方法名的List判断存储的数据是list集合,还是单一的实体类
        //redis中不能直接存放实体类,所以这里存取数据都要先序列化和反序列化
        //List集合的序列化和单一实体的序列化方法并不同,所以这里区分
        if(methodName.indexOf("List")>0){
            try {
                result = SerializeUtil.unserializeForList(byteData);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }else{
            try {
                result = SerializeUtil.unSerialize(byteData);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return result;

    }

    //将数据库中查询到的数据放入redis
    public void setDataToRedis(String redisKey, Object obj) {

        //存入redis
        Jedis jedis = jedisPool.getResource();
        jedis.auth("123456");
        String success;
        //判断obj数据类型,list集合先转化成list集合
        if (obj instanceof List<?>) {
            List<?> list = (List<?>) obj;
            //jedis.setex(key,key有效时间(/秒),序列化对象);
            success = jedis.setex(redisKey.getBytes(),600,SerializeUtil.serialize(list));
        } else {
            success = jedis.setex(redisKey.getBytes(),600, SerializeUtil.serialize(obj));
        }
        if (success != null && success.equals("OK")) {
            System.out.print("保存至redis成功");
        }
    }
}

redis存取中用到的序列化工具类,序列化的实体类,必须实现Serializable接口,才能正常实例化

public class SerializeUtil {

    public static byte[] serialize(Object obj) {

        ObjectOutputStream oos = null;
        ByteArrayOutputStream baos = null;

        try {
            //序列化
            baos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(baos);

            oos.writeObject(obj);
            byte[] byteArray = baos.toByteArray();
            return byteArray;

        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 31      *
     * 32      * 反序列化
     * 33      * @param bytes
     * 34      * @return
     * 35
     */
    public static Object unSerialize(byte[] bytes) throws Exception{

        ByteArrayInputStream bais = null;
        //反序列化为对象
        bais = new ByteArrayInputStream(bytes);
        ObjectInputStream ois = new ObjectInputStream(bais);
        return ois.readObject();

    }

    /**
     * 列表序列化(用于Redis整存整取)
     *
     * @param value
     * @return
     */
    public static <T> byte[] serialize(List<T> value) {
        if (value == null) {
            throw new NullPointerException("Can't serialize null");
        }
        byte[] rv = null;
        ByteArrayOutputStream bos = null;
        ObjectOutputStream os = null;
        try {
            bos = new ByteArrayOutputStream();
            os = new ObjectOutputStream(bos);
            for (T obj : value) {
                os.writeObject(obj);
            }
            os.writeObject(null);
            os.close();
            bos.close();
            rv = bos.toByteArray();
        } catch (IOException e) {
            throw new IllegalArgumentException("Non-serializable object", e);
        } finally {
            close(os);
            close(bos);
        }
        return rv;
    }

    /**
     * 反序列化列表(用于Redis整存整取)
     *
     * @param in
     * @return
     */
    public static <T> List<T> unserializeForList(byte[] in) throws Exception{
        List<T> list = new ArrayList<T>();
        ByteArrayInputStream bis = null;
        ObjectInputStream is = null;
        if (in != null) {
            bis = new ByteArrayInputStream(in);
            is = new ObjectInputStream(bis);
            while (true) {
                T obj = (T) is.readObject();
                if (obj == null) {
                    break;
                } else {
                    list.add(obj);
                }
            }
            is.close();
            bis.close();
        }
        close(is);
        close(bis);
        return list;
    }

    /**
     * 关闭的数据源或目标。调用 close()方法可释放对象保存的资源(如打开文件)
     * 关闭此流并释放与此流关联的所有系统资源。如果已经关闭该流,则调用此方法无效。
     *
     * @param closeable
     */
    public static void close(Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (Exception e) {
                //  log.info("Unable to close %s", closeable, e);
            }
        }
    }

}

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值