这里使用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&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服务器中的缓存数据的变化