前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >java进阶|Springboot整合Redis+Aop+自定义注解实现数据埋点操作

java进阶|Springboot整合Redis+Aop+自定义注解实现数据埋点操作

作者头像
码农王同学
发布2020-04-27 10:25:57
1K0
发布2020-04-27 10:25:57
举报
文章被收录于专栏:后端Coder后端Coder

一,项目所需要的jar信息

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <parent>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-parent</artifactId>        <version>2.2.6.RELEASE</version>        <relativePath/> <!-- lookup parent from repository -->    </parent>    <groupId>com.wpw</groupId>    <artifactId>springboot-redis</artifactId>    <version>0.0.1-SNAPSHOT</version>    <name>springboot-redis</name>    <description>Demo project for Spring Boot</description>
    <properties>        <java.version>1.8</java.version>    </properties>
    <dependencies>
        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>
        <dependency>            <groupId>org.projectlombok</groupId>            <artifactId>lombok</artifactId>            <optional>true</optional>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-test</artifactId>            <scope>test</scope>            <exclusions>                <exclusion>                    <groupId>org.junit.vintage</groupId>                    <artifactId>junit-vintage-engine</artifactId>                </exclusion>            </exclusions>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-data-redis</artifactId>        </dependency>        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-aop</artifactId>            <version>2.2.5.RELEASE</version>        </dependency>
    </dependencies>
    <build>        <plugins>            <plugin>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-maven-plugin</artifactId>            </plugin>        </plugins>    </build>
</project>

这里就把需要的jar信息的pom文件信息粘贴出来了,主要是为了日后方便,里面主要用了web,redis操作需要的jar包信息以及aop需要的jar包依赖信息,到这里需要的jar包信息就结束了。

二,项目的配置文件信息如下

代码语言:javascript
复制
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-redisserver:  port: 8080

项目配置信息,如端口号,项目名称,redis连接地址,端口号,连接数配置信息,写到这突然觉得redis这个点自己还没有去写,之前只有一篇关于docker安装redis以及springboot整合redis文章的操作,还有关于redis操作中缓存雪崩,缓存穿透之类的文字表述,代码方面原不及自己已经写得java基础性操作,以及mybatis系列性文章,以及mysql系列文章的操作,后面有时间自己也需要看下这方面的内容,这里先扯到这里,下面我们看下核心代码的编写过程吧。

三,首先,我们编写一个redis的配置类,首先spring已经提供了下面的操作,只需要注入就可以了,但是它不满足我们这里设置数据的操作,所以重新写了一个redis配置类。

代码语言:javascript
复制
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 */@Configurationpublic class RedisConfig {    @Bean    @Primary    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {        RedisTemplate<String, Object> 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工具类进行操作的,所以很重要的。

代码语言:javascript
复制
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 */@Componentpublic class RedisUtil {    @Autowired    private RedisTemplate<String, Object> 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<Object, Object> hmget(String key) {        return redisTemplate.opsForHash().entries(key);    }
    /**     * hashSet     *     * @param key 键     * @param map 对应多个键值     * @return true设置成功, false设置失败     */    public boolean hmset(String key, Map<String, Object> map) {        try {            redisTemplate.opsForHash().putAll(key, map);            return true;        } catch (Exception e) {            e.printStackTrace();            return false;        }    }
    /**     * hashSet     *     * @param key  键     * @param map  对应多个键值     * @param time 时间(秒)     * @return true设置成功, false设置失败     */    public boolean hmset(String key, Map<String, Object> map, long time) {        try {            redisTemplate.opsForHash().putAll(key, map);            if (time > 0) {                expire(key, time);            }            return true;        } catch (Exception e) {            e.printStackTrace();            return false;        }    }
    /**     * 向一张hash表中放入数据,如果不存在将创建     *     * @param key   键     * @param item  项     * @param value 值     * @return true设置成功,false设置失败     */    public boolean hset(String key, String item, Object value) {        try {            redisTemplate.opsForHash().put(key, item, value);            return true;        } catch (Exception e) {            e.printStackTrace();            return false;        }    }
    /**     * hashSet  设置时间     *     * @param key   键     * @param item  项     * @param value 值     * @param time  时间(秒)     * @return true设置成功, false设置失败     */    public boolean hset(String key, String item, Object value, long time) {        try {            redisTemplate.opsForHash().put(key, item, value);            if (time > 0) {                expire(key, time);            }            return true;        } catch (Exception e) {            e.printStackTrace();            return false;        }    }}

五,关于redis操作的信息上面都介绍完了,下面我们先定义一个自定义注解,然后使用这个注解进行方法的标注,为下面基于aop操作做下铺垫。

代码语言:javascript
复制
package com.wpw.springbootredis.config;
import java.lang.annotation.*;
/** * @author wpw */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface CountInvokeTimes {}

上面定义了一个名字为CountInvokeTimes,生命周期在运行时,作用范围在方法上的自定义注解,关于自定义注解,自己这方面也写过一点,不过用的也少了一些,其中写了一篇关于自定义注解内容的介绍,以及写了一篇基于aop和自定义注解进行统计方法执行耗时时间的,有需要的可以查看历史文章数据进行查找,所以这篇就自己再写了一下关于注解的作用。

六,下面我们定义一个切面类,这个切面类也是本篇文章的重点内容,这里先贴上代码,然后具体看下里面实现的内容。

代码语言:javascript
复制
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@Componentpublic 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这样的测试工具手动测试的,不知道并发操作下会不会有问题,所以改成了下面的操作对了,就算出现并发操作,也没什么问题。

因为我要的数据不一定是非常精确的,只要误差不太大就可以了,关于如何模拟多人操作,这里自己还没有真正的实操过,所以暂时不做测试分析了,这里还是继续下面的分析好了,日后写到关于这方面的操作时再进行说明一下吧。

代码语言:javascript
复制
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方法的测试,使用了三个方法进行模拟测试。

代码语言:javascript
复制
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 */@RestControllerpublic class UserController {    @CountInvokeTimes    @RequestMapping(value = "/hello")    public String hello() {        return "hello redis";    }
    @CountInvokeTimes    @GetMapping(value = "/list")    public List<String> 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和自定义注解实现数据埋点操作就到这里结束了。

为啥会写这篇文章呢?就是为了日后遇到这样的需求操作时,能很快的完成,以及自己将这个内容保存到互联网上,如果能帮助到别人再合适不过了,其实就是一个总结和分享的过程,到这里结束了,需要内容的可以直接下载代码,代码地址为:

代码语言:javascript
复制
https://github.com/myownmyway/springboot-redis.git
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-04-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农王同学 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档