文章标题

来源:互联网 发布:网络安全书 编辑:程序博客网 时间:2024/06/09 21:52

SpringAOP+Redis实现数据缓存

  • 前沿:转行学习Java也有差不多4个月了,但是之前一直没有写过一篇属于自己的博客,也许是自己组织语言的能力不强,我想更多的还是自己技术程度太低。但是今天,现在我要将自己学习过程中的的点点滴滴的知识通过写博客的过程巩固、积累。这既能方便我以后随时查阅复习,同时也能同各位网友相互学习和借鉴(本人目前是个纯新手,写的不好希望各位大牛们相互学习和交流)。

  • 本人在练习网上培训班项目中遇到了利用Redis缓存数据的问题,视频中是将业务逻辑与缓存逻辑写在一起的,这样会造成后期代码耦合度较高,维护较为麻烦。因此本人打算用SpringAOP的方式实现,做到业务主要逻辑与功能增强间的分离。

  • redis配置

    <!-- 连接池配置 -->    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">        <!-- 最大连接数 -->        <property name="maxTotal" value="30" />        <!-- 最大空闲连接数 -->        <property name="maxIdle" value="10" />        <!-- 每次释放连接的最大数目 -->        <property name="numTestsPerEvictionRun" value="1024" />        <!-- 释放连接的扫描间隔(毫秒) -->        <property name="timeBetweenEvictionRunsMillis" value="30000" />        <!-- 连接最小空闲时间 -->        <property name="minEvictableIdleTimeMillis" value="1800000" />        <!-- 连接空闲多久后释放, 当空闲时间>该值 且 空闲连接>最大空闲连接数 时直接释放 -->        <property name="softMinEvictableIdleTimeMillis" value="10000" />        <!-- 获取连接时的最大等待毫秒数,小于零:阻塞不确定的时间,默认-1 -->        <property name="maxWaitMillis" value="1500" />        <!-- 在获取连接的时候检查有效性, 默认false -->        <property name="testOnBorrow" value="true" />        <!-- 在空闲时检查有效性, 默认false -->        <property name="testWhileIdle" value="true" />        <!-- 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true -->        <property name="blockWhenExhausted" value="false" />    </bean>     <!-- jedis客户端单机版 -->    <bean id="redisClient" class="redis.clients.jedis.JedisPool">        <constructor-arg name="host" value="192.168.254.132"></constructor-arg>        <constructor-arg name="port" value="6379"></constructor-arg>        <constructor-arg name="poolConfig" ref="jedisPoolConfig"></constructor-arg>    </bean>    <bean id="jedisClient" class="com.taotao.rest.service.impl.RedisClientServiceImpl"/> 

spring中自定义注解类配置,通过如下配置扫描自定义注解接口
这里写图片描述

  • 开始编写aop内容,自定义注解
import java.lang.annotation.*;@Retention(RetentionPolicy.RUNTIME) // 运行时可获取目标对象方法注解上的相关属性值@Target(ElementType.METHOD)  // 应用于方法@Documented         // 生成文档public @interface RedisCache {    String keyId() default "0";  // 可以在AOP实现方法上取到传递的参数值}
  • AOP切面编写,使用全注解的方式
package com.taotao.rest.cache;import org.apache.log4j.Logger;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import com.taotao.common.constants.ConfigConstants;import com.taotao.common.util.ResourceUtil;import com.taotao.rest.service.RedisClientService;@Component@Aspectpublic class RedisAspect {    private static final Logger LOGGER= Logger.getLogger(RedisAspect.class);    private static final String DELIMITER="|";    @Autowired    private RedisClientService redisClientService;     /**     * Service层切点 使用到了我们定义的 RedisCache 作为切点表达式(替代了execution表达式)     * 而且我们可以看出此表达式是基于 annotation的。     */    @Pointcut("@annotation(com.taotao.rest.cache.RedisCache)")  // @annotation关键字表示使用括号内的自定义注解类做标记    public void redisCacheAspect(){    }    /**     * AOP实现方法,将真正业务逻辑关联的缓存逻辑写在这里     */    @Around("redisCacheAspect()")  // redisCacheAspect() 就是表示的上面的切点,他会在上面切点中自定义注解标记的目标对象方法上使用around增强    public Object getContentCache(ProceedingJoinPoint joinPoint){         // 得到类名、方法名和参数        String clazzName = joinPoint.getTarget().getClass().getName(); //com.taotao.rest.service.impl.ContentServiceImpl        String methodName = joinPoint.getSignature().getName(); // getContentList        Object[] args = joinPoint.getArgs();        // 根据类名、方法名和参数生成Key        LOGGER.info("key参数: " + clazzName + "." + methodName);        //System.out.println("key参数: " + clazzName + "." + methodName);        String path=ResourceUtil.getValueByName(ConfigConstants.INDEX_CONTENT_REDIS_KEY, ConfigConstants.REST_RESOURCE_PATH);        //String key = getKey(clazzName, methodName, args);       String key = path+String.valueOf(args[0]);        if (LOGGER.isInfoEnabled()) {            LOGGER.info("生成key: " + key);        }        //获取从redis中查询到的对象        Object objectFromRedis = redisClientService.getDataFromRedis(key);        //如果查询到了        if(null != objectFromRedis){            System.out.println("从redis中查询到了数据...不需要查询数据库");            return objectFromRedis;        }        System.out.println("没有从redis中查到数据...");        //没有查到,那么查询数据库        Object object = null;        try {            object = joinPoint.proceed();        } catch (Throwable e) {            e.printStackTrace();        }        System.out.println("从数据库中查询的数据...");        //后置:将数据库中查询的数据放到redis中        System.out.println("调用把数据库查询的数据存储到redis中的方法...");        redisClientService.setDataToRedis(key, object);        //将查询到的数据返回        return object;    }}

其中redisClientService 中的实现方法:

public Object getDataFromRedis(String redisKey) {        Jedis jedis = jedisPool.getResource();         byte[] result = jedis.get(redisKey.getBytes());            //如果查询结果为空,就直接返回            if(null == result){                return null;            }            //若是查询到了,就反序列化            return SerializeUtil.unSerialize(result);    }    @Override    public void setDataToRedis(String redisKey, Object obj) {          //序列化        byte[] bytes = SerializeUtil.serialize(obj);        //存入redis        Jedis jedis = jedisPool.getResource();        String success = jedis.set(redisKey.getBytes(), bytes);        String expireValue=ResourceUtil.getValueByName(ConfigConstants.INDEX_REDIS_CONTENT_EXPIRE, ConfigConstants.REST_RESOURCE_PATH);        jedis.expire(redisKey.getBytes(), Integer.parseInt(expireValue));        if("OK".equals(success)){            System.out.println("数据成功保存到redis...");        }    }

配置文件工具类和序列化工具类:

public class ResourceUtil {    public static String getValueByName(String key,String path){        if(key==null){            return null;        }        if(key.equals("")){            return null;        }        //test为属性文件名,放在包resources/resource下,如果是放在classpath下,直接用test即可 ,           ResourceBundle resource = ResourceBundle.getBundle(path);        String value = resource.getString(key);        return value;    }}public class SerializeUtil {    /**     * POJO 对象需 实现serializble接口,这样即使对象属性改变后也不影响     * 序列化     */    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;    }    /**     *      * 反序列化     * @param bytes     * @return     */    public static Object unSerialize(byte[] bytes){        ByteArrayInputStream bais = null;        try {            //反序列化为对象            bais = new ByteArrayInputStream(bytes);            ObjectInputStream ois = new ObjectInputStream(bais);            return ois.readObject();        } catch (Exception e) {            e.printStackTrace();        }        return null;    }}
  1. 缓存使用的目标对象方法

    @Override@RedisCache(keyId="99")public List<TbContent> getContentList(long contentCid) {    //根据内容分类id查询内容列表    TbContentExample example = new TbContentExample();    Criteria criteria = example.createCriteria();    criteria.andCategoryIdEqualTo(contentCid);    //执行查询    List<TbContent> list = contentMapper.selectByExample(example);    return list;}

    5.运行结果:
    这里写图片描述
    上图是在清除缓存后的运行结果
    这里写图片描述
    这是第二次的运行结果

0 0
原创粉丝点击