SpringBoot与AOP集成实现Redis缓存

1. 什么是 AOP

概念

AOP是Aspect Oriented Programming的缩写,意思是:面向切面编程,一种在运行过程中通过预编译方式和动态代理实现程序功能统一维护的技术。 AOP是OOP的延续,是软件开发的热点,是Spring框架的重要内容,是函数式编程的衍生范式。 AOP可以隔离各部分业务逻辑,减少各部分业务逻辑之间的耦合,提高程序的复用性,提高开发效率。
通常来说:
AOP就是在原代码的基础上,在不改变原代码的情况下,增加附加功能。

优点

为什么AOP会降低耦合? 例如:
开发代码时,如果需要添加日志功能怎么办? 一般来说,我们可以在需要添加日志的界面或者函数上添加日志的功能。 这样就需要修改原来的代码了。 如果数量少,可以接受。 但如果数量大,工作量就会很大。 另外,如果需要升级或删除日志,必须修改源代码,非常麻烦且容易出错。 但是如果使用AOP,只需要一个入口点。

2. 代码实现

Redis配置

@Component
@DependsOn("mybatisConfig")
public class RedisService{

    @Resource
    private ISystemConfigService systemConfigService;
    private JedisPool jedisPool;
    private final Logger log = LoggerFactory.getLogger(RedisService.class);

    public void setJedis(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }

    public JedisPool instance() {
        try {
            if (this.jedisPool != null) return this.jedisPool;
            // 获取redis的连接
            // host
            SystemConfig systemConfigHost = systemConfigService.selectByKey("redis_host");
            String host = systemConfigHost.getValue();
            // port
            SystemConfig systemConfigPort = systemConfigService.selectByKey("redis_port");
            String port = systemConfigPort.getValue();
            // password
            SystemConfig systemConfigPassword = systemConfigService.selectByKey("redis_password");
            String password = systemConfigPassword.getValue();
            password = StringUtils.isEmpty(password) ? null : password;
            // database
            SystemConfig systemConfigDatabase = systemConfigService.selectByKey("redis_database");
            String database = systemConfigDatabase.getValue();
            // timeout
            SystemConfig systemConfigTimeout = systemConfigService.selectByKey("redis_timeout");
            String timeout = systemConfigTimeout.getValue();

            if (!this.isRedisConfig()) {
                log.info("redis配置信息不全或没有配置...");
                return null;
            }
            JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
            // 配置jedis连接池最多空闲多少个实例,源码默认 8
            jedisPoolConfig.setMaxIdle(7);
            // 配置jedis连接池最多创建多少个实例,源码默认 8
            jedisPoolConfig.setMaxTotal(20);
            //在borrow(引入)一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;
            jedisPoolConfig.setTestOnBorrow(true);
            //return 一个jedis实例给pool时,是否检查连接可用性(ping())
            jedisPoolConfig.setTestOnReturn(true);
            jedisPool = new JedisPool(jedisPoolConfig, host, Integer.parseInt(port), Integer.parseInt(timeout), password,
                    Integer.parseInt(database));
            log.info("redis连接对象获取成功...");
            return this.jedisPool;
        } catch (Exception e) {
            log.error("配置redis连接池报错,错误信息: {}", e.getMessage());
            return null;
        }
    }

    // 判断redis是否配置了
    public boolean isRedisConfig() {
        SystemConfig systemConfigHost = systemConfigService.selectByKey("redis_host");
        String host = systemConfigHost.getValue();
        // port
        SystemConfig systemConfigPort = systemConfigService.selectByKey("redis_port");
        String port = systemConfigPort.getValue();
        // database
        SystemConfig systemConfigDatabase = systemConfigService.selectByKey("redis_database");
        String database = systemConfigDatabase.getValue();
        // timeout
        SystemConfig systemConfigTimeout = systemConfigService.selectByKey("redis_timeout");
        String timeout = systemConfigTimeout.getValue();

        return !StringUtils.isEmpty(host) && !StringUtils.isEmpty(port) && !StringUtils.isEmpty(database) && !StringUtils
                .isEmpty(timeout);
    }

    // 获取String值
    public String getString(String key) {
        JedisPool instance = this.instance();
        if (StringUtils.isEmpty(key) || instance == null) return null;
        Jedis jedis = instance.getResource();
        String value = jedis.get(key);
        jedis.close();
        return value;
    }

    public void setString(String key, String value) {
        this.setString(key, value, 300); // 如果不指定过时时间,默认为5分钟
    }

    /**
     * 带有过期时间的保存数据到redis,到期自动删除
     *
     * @param key
     * @param value
     * @param expireTime 单位 秒
     */
    public void setString(String key, String value, int expireTime) {
        JedisPool instance = this.instance();
        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(value) || instance == null) return;
        Jedis jedis = instance.getResource();
        SetParams params = new SetParams();
        params.px(expireTime * 1000);
        jedis.set(key, value, params);
        jedis.close();
    }

    public void delString(String key) {
        JedisPool instance = this.instance();
        if (StringUtils.isEmpty(key) || instance == null) return;
        Jedis jedis = instance.getResource();
        jedis.del(key); // 返回值成功是 1
        jedis.close();
    }

    // TODO  list, map 等方法

}

定义切点方法

package co.abc.demo.hook;
import org.aspectj.lang.annotation.Pointcut;

public class UserServiceHook {

    @Pointcut("execution(public * co.abc.demo.service.IUserService.selectByUsername(..))")
    public void selectByUsername() {
    }

    @Pointcut("execution(public * co.abc.demo.service.IUserService.selectByToken(..))")
    public void selectByToken() {
    }

    @Pointcut("execution(public * co.abc.demo.service.IUserService.selectById(..))")
    public void selectById() {
    }

    @Pointcut("execution(public * co.abc.demo.service.IUserService.delRedisUser(..))")
    public void delRedisUser() {
    }

}


package co.abc.demo.hook;  
import org.aspectj.lang.annotation.Pointcut;

public class TopicServiceHook {

    @Pointcut("execution(public * co.abc.demo.service.ITopicService.search(..))")
    public void search() {
    }

    @Pointcut("execution(public * co.abc.demo.service.ITopicService.selectById(..))")
    public void selectById() {
    }

    @Pointcut("execution(public * co.abc.demo.service.ITopicService.update(..))")
    public void update() {
    }

    @Pointcut("execution(public * co.abc.demo.service.ITopicService.vote(..))")
    public void vote() {
    }

    @Pointcut("execution(public * co.abc.demo.service.ITopicService.downVote(..))")
    public void downVote() {
    }

    @Pointcut("execution(public * co.abc.demo.service.ITopicService.updateViewCount(..))")
    public void updateViewCount() {
    }

}

实现aop切点处需要执行的方法以及逻辑

@Component
@Aspect
public class RedisCachePlugin {

    @Resource
    private RedisService redisService;
    @Resource
    private ITopicService topicService;
    @Resource
    private ISystemConfigService systemConfigService;

    @Around("co.abc.demo.hook.TopicServiceHook.selectById()")
    public Object topicSelectById(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        String topicJson = redisService.getString(String.format(RedisKeys.REDIS_TOPIC_KEY, proceedingJoinPoint.getArgs()[0]));
        if (topicJson == null) {
            Object topic = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
            // 缓存在redis里
            redisService.setString(String.format(RedisKeys.REDIS_TOPIC_KEY, proceedingJoinPoint.getArgs()[0]), JsonUtil.objectToJson(topic));
            return topic;
        } else {
            return JsonUtil.jsonToObject(topicJson, Topic.class);
        }
    }

    @Around("co.abc.demo.hook.TopicServiceHook.vote()")
    public Object voteTopic(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        // 点赞后删除redis内缓存的topic数据
        Topic topic = (Topic) proceedingJoinPoint.getArgs()[0];
        redisService.delString(String.format(RedisKeys.REDIS_TOPIC_KEY, topic.getId()));
        return proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
    }

    @Around("co.abc.demo.hook.TopicServiceHook.downVote()")
    public Object downVoteTopic(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        // 点赞后删除redis内缓存的topic数据
        Topic topic = (Topic) proceedingJoinPoint.getArgs()[0];
        redisService.delString(String.format(RedisKeys.REDIS_TOPIC_KEY, topic.getId()));
        return proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
    }

    @After("co.abc.demo.hook.TopicServiceHook.update()")
    public void topicUpdate(JoinPoint joinPoint) {
        Topic topic = (Topic) joinPoint.getArgs()[0];
        // 缓存到redis里
        redisService.setString(String.format(RedisKeys.REDIS_TOPIC_KEY, topic.getId()), JsonUtil.objectToJson(topic));
    }

    @Around("co.abc.demo.hook.UserServiceHook.selectByUsername()")
    public Object userSelectByUsername(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        String username = (String) proceedingJoinPoint.getArgs()[0];
        String userJson = redisService.getString(String.format(RedisKeys.REDIS_USER_USERNAME_KEY, username));
        if (userJson != null) {
            // 带泛型转换, 这里如果不带泛型转换,会报错
            return JsonUtil.jsonToObject(userJson, User.class);
        } else {
            Object returnValue = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
            if (returnValue != null) {
                redisService.setString(String.format(RedisKeys.REDIS_USER_USERNAME_KEY, username), JsonUtil.objectToJson(returnValue));
            }
            return returnValue;
        }
    }

    @Around("co.abc.demo.hook.UserServiceHook.selectByToken()")
    public Object userSelectByToken(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        String token = (String) proceedingJoinPoint.getArgs()[0];
        String userJson = redisService.getString(String.format(RedisKeys.REDIS_USER_TOKEN_KEY, token));
        if (userJson != null) {
            // 带泛型转换, 这里如果不带泛型转换,会报错
            return JsonUtil.jsonToObject(userJson, User.class);
        } else {
            Object returnValue = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
            if (returnValue != null) {
                redisService.setString(String.format(RedisKeys.REDIS_USER_TOKEN_KEY, token), JsonUtil.objectToJson(returnValue));
            }
            return returnValue;
        }
    }

    @Around("co.abc.demo.hook.UserServiceHook.selectById()")
    public Object userSelectById(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Integer id = (Integer) proceedingJoinPoint.getArgs()[0];
        String userJson = redisService.getString(String.format(RedisKeys.REDIS_USER_ID_KEY, id));
        if (userJson != null) {
            // 带泛型转换, 这里如果不带泛型转换,会报错
            return JsonUtil.jsonToObject(userJson, User.class);
        } else {
            Object returnValue = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
            if (returnValue != null) {
                redisService.setString(String.format(RedisKeys.REDIS_USER_ID_KEY, id), JsonUtil.objectToJson(returnValue));
            }
            return returnValue;
        }
    }

    @After("co.abc.demo.hook.UserServiceHook.delRedisUser()")
    public void userDelRedisUser(JoinPoint joinPoint) {
        User user = (User) joinPoint.getArgs()[0];
        redisService.delString(String.format(RedisKeys.REDIS_USER_ID_KEY, user.getId()));
        redisService.delString(String.format(RedisKeys.REDIS_USER_USERNAME_KEY, user.getUsername()));
        redisService.delString(String.format(RedisKeys.REDIS_USER_TOKEN_KEY, user.getToken()));
    }

    static class RedisKeys {
        public static final String REDIS_TOPIC_KEY = "demo_topic_%s"; // 后面还要拼上话题的id
        public static final String REDIS_TOPIC_VIEW_IP_ID_KEY = "demo_topic_view_ip_%s_topic_%s"; // 需要格式化字符串填充上ip地址跟话题id
        public static final String REDIS_COMMENTS_KEY = "demo_comments_%s"; // 后面还要拼上话题的id

        public static final String REDIS_USER_ID_KEY = "demo_user_id_%s"; // 后面还要拼上用户的id
        public static final String REDIS_USER_USERNAME_KEY = "demo_user_username_%s"; // 后面还要拼上用户名
        public static final String REDIS_USER_TOKEN_KEY = "demo_user_token_%s"; // 后面还要拼上用户的token
    }
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
禁止转载,如需转载请通过简信或评论联系作者。

推荐阅读更多精彩内容