一,项目所需要的jar信息
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.2.6.RELEASE
com.wpw
springboot-redis
0.0.1-SNAPSHOT
springboot-redis
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter-web
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
org.springframework.boot
spring-boot-starter-data-redis
org.springframework.boot
spring-boot-starter-aop
2.2.5.RELEASE
org.springframework.boot
spring-boot-maven-plugin
这里就把需要的jar信息的pom文件信息粘贴出来了,主要是为了日后方便,里面主要用了web,redis操作需要的jar包信息以及aop需要的jar包依赖信息,到这里需要的jar包信息就结束了。
二,项目的配置文件信息如下
spring:
redis:
database: 0
host: localhost
port: 6379
password:
jedis:
pool:
max-active: 200
max-wait: -1ms
max-idle: 10
min-idle: 0
application:
name: springboot-redis
server:
port: 8080
项目配置信息,如端口号,项目名称,redis连接地址,端口号,连接数配置信息,写到这突然觉得redis这个点自己还没有去写,之前只有一篇关于docker安装redis以及springboot整合redis文章的操作,还有关于redis操作中缓存雪崩,缓存穿透之类的文字表述,代码方面原不及自己已经写得java基础性操作,以及mybatis系列性文章,以及mysql系列文章的操作,后面有时间自己也需要看下这方面的内容,这里先扯到这里,下面我们看下核心代码的编写过程吧。
三,首先,我们编写一个redis的配置类,首先spring已经提供了下面的操作,只需要注入就可以了,但是它不满足我们这里设置数据的操作,所以重新写了一个redis配置类。
package com.wpw.springbootredis.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* redis配置类
*
* @author wpw
*/
@Configuration
public class RedisConfig {
@Bean
@Primary
public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
RedisTemplate redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//key采用String的序列化方式
redisTemplate.setKeySerializer(stringRedisSerializer);
//hash的key也采用String的序列化方式
redisTemplate.setHashKeySerializer(stringRedisSerializer);
//value序列化方式采用jackson
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
//hash的value序列化方式采用jackson
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
四,基于redis配置类,这里封装了一下常用操作的redis工具类,代码如下,需要的可以看下,本文就是基于这个redis工具类进行操作的,所以很重要的。
package com.wpw.springbootredis.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* Redis工具类
*
* @author wpw
*/
@Component
public class RedisUtil {
@Autowired
private RedisTemplate redisTemplate;
/**
* 设置缓存失效时间
*
* @param key 键
* @param time 时间(秒)
* @return
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key获取过期时间
*
* @param key 键 不能为null
* @return 时间(秒)返回0代表永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
*
* @param key 键
* @return true存在,false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
*
* @param key 键 可以传一个值或者多个值
*/
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
/**
* 根据键获取值
*
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 设置key之间的对应关系
*
* @param key 键
* @param value 值
* @return true设置成功,false设置失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 设置key/value之间对应的关系且设置过期时间
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return true成功, false失败
*/
public boolean setKeyWithTime(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* hashGet
*
* @param key 键 ,不能为null
* @param item 项 ,不能为null
* @return 值
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
public Map
五,关于redis操作的信息上面都介绍完了,下面我们先定义一个自定义注解,然后使用这个注解进行方法的标注,为下面基于aop操作做下铺垫。
package com.wpw.springbootredis.config;
import java.lang.annotation.*;
/**
* @author wpw
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CountInvokeTimes {
}
上面定义了一个名字为CountInvokeTimes,生命周期在运行时,作用范围在方法上的自定义注解,关于自定义注解,自己这方面也写过一点,不过用的也少了一些,其中写了一篇关于自定义注解内容的介绍,以及写了一篇基于aop和自定义注解进行统计方法执行耗时时间的,有需要的可以查看历史文章数据进行查找,所以这篇就自己再写了一下关于注解的作用。
六,下面我们定义一个切面类,这个切面类也是本篇文章的重点内容,这里先贴上代码,然后具体看下里面实现的内容。
package com.wpw.springbootredis.config;
import com.wpw.springbootredis.util.RedisUtil;
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.stereotype.Component;
import java.lang.reflect.Method;
/**
* @author wpw
*/
@Aspect
@Component
public class CountInvokedTimesAspect {
private final RedisUtil redisUtil;
public CountInvokedTimesAspect(RedisUtil redisUtil) {
this.redisUtil = redisUtil;
}
@Pointcut("@annotation(com.wpw.springbootredis.config.CountInvokeTimes)")
public void countInvokeTimes() {
}
@Around(value = "countInvokeTimes()")
public Object doAround(ProceedingJoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
Class>[] argTypes = new Class[args.length];
for (int i = 0, length = args.length; i < length; i++) {
argTypes[i] = args[i].getClass();
}
try {
String methodName = joinPoint.getSignature().getName();
Method method = joinPoint.getTarget().getClass().getMethod(methodName, argTypes);
boolean isAnnotationPresent = method.isAnnotationPresent(CountInvokeTimes.class);
if (isAnnotationPresent) {
if (redisUtil.get(methodName) == null) {
redisUtil.set(methodName, 1);
} else {
Integer countTimes = (Integer) redisUtil.get(methodName);
countTimes += 1;
redisUtil.set(methodName, countTimes);
}
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
Object object = null;
try {
object = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return object;
}
}
首先获取方法的参数,然后获取方法的名称即methodName,根据方法的名称以及所在的类得到具体的方法,判断方法上是否标注了CountInvokeTimes注解。
若标记了这个注解,则我们需要对其进行操作,首先我们先根据方法名称去redis里面去查询,判断是否已经存在,若没有存在则把对应的方法名设置为key,值设置为1。
若存在,则获取对应的方法名称,然后值自增,最后再设置一下,这里由于自己基于postman这样的测试工具手动测试的,不知道并发操作下会不会有问题,所以改成了下面的操作对了,就算出现并发操作,也没什么问题。
因为我要的数据不一定是非常精确的,只要误差不太大就可以了,关于如何模拟多人操作,这里自己还没有真正的实操过,所以暂时不做测试分析了,这里还是继续下面的分析好了,日后写到关于这方面的操作时再进行说明一下吧。
if (isAnnotationPresent) {
if (redisUtil.get(methodName) == null) {
redisUtil.set(methodName, 1);
} else {
AtomicInteger countTimes = (AtomicInteger) redisUtil.get(methodName);
redisUtil.set(methodName, countTimes.incrementAndGet());
}
}
七,最后这里贴下关于controller层的代码,由于很简单,只涉及到get方法的测试,使用了三个方法进行模拟测试。
package com.wpw.springbootredis.controller;
import com.wpw.springbootredis.config.CountInvokeTimes;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.List;
/**
* @author wpw
*/
@RestController
public class UserController {
@CountInvokeTimes
@RequestMapping(value = "/hello")
public String hello() {
return "hello redis";
}
@CountInvokeTimes
@GetMapping(value = "/list")
public List list() {
return Arrays.asList("hello", "hello");
}
@GetMapping(value = "/say")
public String say() {
return "say";
}
}
八,最后测试了一下,我手动通过postman进行调用list方法15次,hello方法2次,say方法2次,我们看下redis数据库的数据信息,看下是否和我们操作的一致。
这里由于使用了windows下安装redis的操作,所以redis可以看成是单机版服务,这里说下为啥采用了redis进行数据的存储,而不是map或者其它的缓存服务器,其一,redis是基于内存级别的,所以可以达到高性能,其二,redis可以以集群的方式进行部署,即redis的cluster模式可以达到高可用,其三redis是可以将数据持久化到磁盘数据进行保存的,所以避免了数据丢失,最后redis也是很重要的一点是可以达到缓存一致性的,这是其他map所不具备的,所以基于其这么多优点,自己采用了redis进行数据的保存,关于缺点吗,自己暂时先说下,因为引入了第三方的依赖包,所以如何保证其高可用特性就很有必要了,后面关于redis的操作,自己有时间再写了,到这里关于redis的操作基于aop和自定义注解实现数据埋点操作就到这里结束了。
为啥会写这篇文章呢?就是为了日后遇到这样的需求操作时,能很快的完成,以及自己将这个内容保存到互联网上,如果能帮助到别人再合适不过了,其实就是一个总结和分享的过程,到这里结束了,需要内容的可以直接下载代码,代码地址为:
https://github.com/myownmyway/springboot-redis.git