Spring AOP结合Redis 实现透明缓存层,ThreadLocal配合Redis管理缓存————TY

这里使用Windows版的Redis进行测试。开发中必须使用Linux版的Redis。

一、Redis查询数据和保存数据

Spring AOP主要用于给业务层在不改变源代码的情况下添加新的功能,比如对业务方法进行增强处理,比如日志输出。如果想要在增强处理的同时数据量出现高并发的情况,可以使用缓存机制进行处理,减轻服务器的压力,将已经查询好的数据通过key值保存到Redis数据库中,当再次发出同样的请求时,只要根据key将数据从Redis中查出来返回出去就行了。在这里我们将用户的toString方法作为key进行数据的增删改查。

二、Redis删除数据

如果用户对某一条数据进行增删改操作后,Redis中的数据并不会自动修改,所以在用户对他自己的数据进行增伤时必须将他在Redis缓存中的所有相关数据清除掉,否则再次查询来的数据还是原来的数据。但我们如何确定某个用户进行增删改操作后将他在Redis缓存中的数据清除呢?此时ThreadLocal就起到作用了,它可以将为每一个线程保存一个副本,将数据保存到副本中。因为用户的每一个请求都是一个线程,这个线程直到请求结束后才会停止。也就是说只要将用户对象保存到ThreadLocal中,就可以随时访问到用户对象,在进行增强处理时,可以通过用户的toString方法作为key进行增删改查操作,将当前用户的所有数据保存或从Redis中删除。

三、实际操作(步骤)

0、启动Tomcat前必须将Redis服务器启动。注意:操作之前先将整个操作步骤浏览一遍。

1、创建用户类SysEmployee,实现序列化接口,注意这个列的toString方法后面值接力sn这个字段,sn是主键,唯一

public class SysEmployee implements Serializable {

    private String sn;
    private String name;
    private String password;
    private String status;
public String getSn() {
    return sn;
}

public void setSn(String sn) {
    this.sn = sn;
}

public String getName() {
    return name;
}

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

public String getPassword() {
    return password;
}

public void setPassword(String password) {
    this.password = password;
}

public String getStatus() {
    return status;
}

public void setStatus(String status) {
    this.status = status;
}
    @Override
    public String toString() {
        return "SysEmployee{" + "sn='" + sn + '}';
    }

    public SysEmployee(){}
    public SysEmployee(String sn){
        this.sn = sn;
    }

}

 

2、在pom中添加redis相关的依赖jar包

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-redis -->
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>2.0.8.RELEASE</version>
</dependency>

3、添加redis.properties文件

driver=com.mysql.jdbc.Driver
#在和mysql传递数据的过程中,使用unicode编码格式,并且字符集设置为utf-8
url=jdbc:mysql://127.0.0.1:3306/aaa?useUnicode=true&amp;characterEncoding=utf-8
username=root
password=root
minIdle=45
maxIdle=50
initialSize=5
maxActive=100
maxWait=100
removeAbandonedTimeout=180
removeAbandoned=true

4、添加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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">


    <context:component-scan base-package="com.cache"/>

    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxTotal" value="${redis_maxTotal}"/>
        <property name="maxIdle" value="${redis_maxIdle}"/>
        <property name="numTestsPerEvictionRun" value="${redis_numTestsPerEvictionRun}"/>
        <property name="timeBetweenEvictionRunsMillis" value="${redis_timeBetweenEvictionRunsMillis}"/>
        <property name="minEvictableIdleTimeMillis" value="${redis_minEvictableIdleTimeMillis}"/>
        <property name="softMinEvictableIdleTimeMillis" value="${redis_softMinEvictableIdleTimeMillis}"/>
        <property name="maxWaitMillis" value="${redis_maxWaitMillis}"/>
        <property name="testOnBorrow" value="${redis_testOnBorrow}"/>
        <property name="testOnReturn" value="${redis_testOnReturn}"/>
        <property name="testWhileIdle" value="${redis_testWhileIdle}"/>
        <property name="blockWhenExhausted" value="${redis_blockWhenExhausted}"/>
    </bean>

    <!-- jedis客户端 -->
    <bean id="jedisPool" class="redis.clients.jedis.JedisPool">
        <constructor-arg index="0" ref="jedisPoolConfig"/>
        <constructor-arg index="1" value="${redis_ip}"/>
        <!--<constructor-arg index="2" value="${redis_port}"/>-->
    </bean>

</beans>

5、在applicationContext.xml中引入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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">


    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:redis.properties</value>
                <value>classpath:database.properties</value>
            </list>
        </property>
    </bean>
    <!--<context:component-scan base-package="com"/>-->

    <import resource="applicationContext-dao.xml"/>
    <import resource="applicationContext-service.xml"/>
    <import resource="applicationContext-action.xml"/>
    <!--<import resource="spring-redis.xml"/>-->
</beans>

6、创建SessionManager类,这个类用来封装ThreadLocal的方法

public class SessionManager {

    private static ThreadLocal<Object> threadLocal = new ThreadLocal<Object>();

    public static Object get(){
        return threadLocal.get();
    }

    public static void set(Object object){
        threadLocal.set(object);
    }

    public static void remove(){
        threadLocal.remove();
    }
}

7、创建SerializeUtil类,用来将数据进行序列化和反序列化

public class SerializeUtil {

    //序列化
    public static byte[] serialize(Object object){
        ObjectOutputStream oos = null;
        ByteArrayOutputStream baos = null;
        try{
            baos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(baos);
            oos.writeObject(object);
            return baos.toByteArray();
        }catch(Exception e){
            e.printStackTrace();
        }
        return null;
    }

    //反序列化
    public static Object unserialize(byte [] bytes){
        ObjectInputStream ois = null;
        ByteArrayInputStream bais = null;
        try{
            bais = new ByteArrayInputStream(bytes);
            ois = new ObjectInputStream(bais);
            return ois.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

8、创建RedisCache类,对缓存数据的增删查操作

@Component
public class RedisCache {

    private static final Logger logger = Logger.getLogger(RedisCache.class);

    @Autowired
    private JedisPool jedisPool;

    //获取缓存数据
    public Object getCacheData(String key){
        Jedis jedis = jedisPool.getResource();
        byte[] bytes = jedis.get(key.getBytes());
        if(bytes == null){
            return null;
        }
        return SerializeUtil.unserialize(bytes);
    }

    //删除对应的缓存数据
    public void delCacheData(String key){
        Jedis jedis = jedisPool.getResource();
        if(jedis.exists(key.getBytes())){
            jedis.del(key.getBytes());
            if(jedis.exists(key.getBytes())){
                logger.info(" [RedisCache] :数据删除失败!!!请联系程序员");
            }else{
                logger.info(" [RedisCache] :数据删除成功!!!");
            }
        }else{
            logger.info(" [RedisCache] :内存数据不存在!!!");
        }
    }

    //将数据序列化保存到redis缓存中
    public void saveData(String key ,Object object){
        Jedis jedis = jedisPool.getResource();
        byte [] bytes = SerializeUtil.serialize(object);
        String saveResult = jedis.set(key.getBytes(), bytes);
        logger.info("=============>保存返回的指令为:"+saveResult);
        if("OK".equals(saveResult)){
            logger.info("[RedisCache]:数据成功保存到redis cache...");
        }
    }

    public void flushCurrUserAll(){
        Jedis jedis = jedisPool.getResource();
        Set<String> keys = null;
        if(SessionManager.get() instanceof SysEmployee){
            SysEmployee employee = (SysEmployee)SessionManager.get();
            //jedis的keys(pattern)方法中的表达式("-"+devUser.toString()+"*")无需再getByte()了,因为jedis中的keys方法里会自动getByte();
            keys = jedis.keys(employee.toString()+"*");
        }
        for (String key : keys) {
            System.out.println("==================》删除Key:"+key);
            jedis.del(key);
        }
    }
}

9、创建Redis 切面类RedisCacheAspect,在增强处理的同时将数据添加或到Redis数据库中或将对应用户的数据删除掉

@Component
@Aspect
public class RedisCacheAspect {

    private static final Logger logger = Logger.getLogger(RedisCacheAspect.class);

    @Autowired
    private RedisCache redisCache;

    /**
     * 生成一个唯一的Redis key
     * @param joinPoint
     * @return
     */
    private String getRedisKey(ProceedingJoinPoint joinPoint){
        SysEmployee employee = null;
        if(SessionManager.get() instanceof SysEmployee){
            employee = (SysEmployee)SessionManager.get();
        }
        return appendRedisKey("null", joinPoint);
    }

    private String appendRedisKey(String userToString,ProceedingJoinPoint joinPoint){
        //获取目标方法的参数
        StringBuffer sb = new StringBuffer(userToString);
        Object [] objects = joinPoint.getArgs();
        for (int i = 0 ; i < objects.length ; i++){
            sb.append(objects[i]);
    }
        return  sb.toString()+"-"+joinPoint.getTarget().getClass();
    }

    @Pointcut("execution(* com.service..*.find*(..))")
    private void getDataPointcut(){}


    @Around("getDataPointcut()")
    public Object getData(ProceedingJoinPoint joinPoint){
        //生成一个唯一的key值
        String redisKey = getRedisKey(joinPoint);
        logger.info("select进入环绕增强前半部分,尝试从redis查询,生成的缓存key:" + redisKey);
        Object obj = redisCache.getCacheData(redisKey);
        if(obj != null){
            logger.info("根据redis key值:["+redisKey+"]从缓存中查出数据--->"+obj);
            return obj;
        }
        //没有查到,那就从数据库中查询
        try{
            obj = joinPoint.proceed();
        }catch(Throwable e){
            e.printStackTrace();;
        }
        //并把查询出的数据保存到Redis缓存中
        redisCache.saveData(redisKey, obj);
        return obj;
    }


    @Pointcut("execution(* com.service..update*(..))")
    private void updateData(){}
    @Pointcut("execution(* com.service..del*(..))")
    private void delData(){}
    @Pointcut("execution(* com.service..add*(..))")
    private void addData(){}


    @Around("updateData() || delData() || addData()")
    public Object updateOrDel(ProceedingJoinPoint joinPoint){

        String redisKey = getRedisKey(joinPoint);
        logger.info("update进入环绕增强前半部分,尝试从redis查询,生成的缓存key:" + redisKey);
        Object data = redisCache.getCacheData(redisKey);
        try{
            //只要当前用户一有修改,则将当前用户的所有缓存清除
            redisCache.flushCurrUserAll();
            data = joinPoint.proceed();
        }catch(Throwable e){
            e.printStackTrace();
        }
        logger.info("更新操作后目标方法返回的值;"+data);
        return data;
    }
}

10、在用户登录成功过后将当前这个用户对象保存到session中

11、创建一个用户授权拦截器,判断用户登录后,将用户对象保存到ThreadLocal中

public class AuthorizationInteceptor extends AbstractInterceptor {

    @Override
    public String intercept(ActionInvocation invocation) throws Exception {

        HttpServletRequest request = ServletActionContext.getRequest();
        String contextPath = request.getContextPath();  //返回上下文路径
        String requestURI = request.getRequestURI();    //
        if(!requestURI.startsWith(contextPath+"/login") && !requestURI.startsWith(contextPath+"/logout")){
            Map<String, Object> session = invocation.getInvocationContext().getSession();
            if(session.get(Constants.AUTH_EMPLOYEE)!=null){
                SessionManager.set(request.getSession().getAttribute(Constants.AUTH_EMPLOYEE));
                return invocation.invoke();
            }
            return Action.LOGIN;
        }
        return invocation.invoke();
    }
}

12、启动Tomcat进行调试,每次执行一个业务操作,注意查看Redis服务器中的缓存数据的变化

 

 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值