相关推荐recommended
Spring与Redis集成
作者:mmseoamin日期:2024-02-04

1.引入RedisTemplate

据以前的情况,我们在Java中使用Redis时一般是使用Jedis来操作的,大致的一段代码如下所示

    @Override
    public User findUserById(Integer id) {
        User user = null;
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            String userStr = jedis.get("user_" + id); // 尝试获取数据
            if (userStr != null && !userStr.isEmpty()) { // 如果获取到有效数据,则转换后返回
                user = JSONObject.parseObject(userStr, User.class);
            } else {// 如果没有获取到数据,则查询数据库返回
                user = userMapper.findUserById(id);
                if (user != null) jedis.set("user_" + id, JSONObject.toJSONString(user)); // 设置到redis中
            }
        } finally {
            // 记得关闭Jedis,因为这里使用的是JedisPool,所以这里的关闭并不是直接关闭连接,而是释放,以供其他的业务使用
            if (jedis != null) jedis.close();
        }
        return user;
    }

上边的这样的一段代码其实是有些臃肿的,但是如果我们引入RedisTemplate,其实会简化不少。

  • maven 引入 spring-data-redis
      
        redis.clients
        jedis
        3.9.0
      
      
        org.springframework.data
        spring-data-redis
        2.2.13.RELEASE
      
    
    • 将RedisTemplate 加入Bean容器中,让Spring进行管理。
        @Bean
        public RedisConnectionFactory redisConnectionFactory() {
            RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
            redisStandaloneConfiguration.setHostName(host);
            redisStandaloneConfiguration.setPort(port);
            RedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory(redisStandaloneConfiguration);
            return redisConnectionFactory;
        }
        @Bean
        public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
            RedisTemplate redisTemplate = new RedisTemplate();
            redisTemplate.setConnectionFactory(redisConnectionFactory);
            //设置key值的序列化方式,默认是JDK的形式
            redisTemplate.setKeySerializer(StringRedisSerializer.UTF_8);
            return redisTemplate;
        }
      
      • 如果使用RedisTemplate的替换的话,会简洁很多。
          @Autowired
          private RedisTemplate redisTemplate;
          @Override
          public User findUserById(Integer id) {
              Object result = redisTemplate.opsForValue().get("user_" + id);
              if (result != null) return (User) result;
              User user = userMapper.findUserById(id);
              // 设置到redis中
              if (user != null) redisTemplate.opsForValue().set("user_" + id, user);
              return user;
          }
        
        • 大概看一下关于RedisTemplate的方法

          Spring与Redis集成,第1张

          看了以上的内容,可以看到引入了RedisTemplate其实已经很简洁了,但是明显还不够,下面我们将考虑引入 “注解


          2. 引入注解

          • 开启缓存 @EnableCaching
              
            @Configuration  
            @EnableCaching  
            public class AppConfig {
            ...
            } 
            
            • 引入@Cacheable,表示这个方法将会访问缓存,如果无法命中缓存的话,会将方法返回的值存入redis,假设有注解为 @Cacheable(value="user", key = "#id"),那么生成的key值为 user::{id},即如果id为1 那么生成的 key就是 user::1
              @Override
              @Cacheable(value="user", key = "#id")
              // 这里返回的值会被存放到redis,key-value格式,其中生成的key值(假设id为1): user::1
              public User findUserById(Integer id) {
              User user = userMapper.findUserById(id);
              return user;
              }
              

              但是这样还不够,因为Spring并不清楚缓存的方式是什么,这就涉及到CacheManager 了

              • 设置CacheManager,在AppConfig中加入以下内容
                @Bean
                public RedisConnectionFactory redisConnectionFactory() {
                    RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
                    redisStandaloneConfiguration.setHostName(host); // 这里是redis的ip
                    redisStandaloneConfiguration.setPort(port);// 这里是redis的端口
                    // 自适应集群变化
                    RedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory(redisStandaloneConfiguration);
                    return redisConnectionFactory;
                }
                @Bean
                public RedisCacheConfiguration redisCacheConfiguration() {
                    RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                    .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.string()))
                    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()));
                    return redisCacheConfiguration;
                }
                @Bean
                public RedisCacheWriter redisCacheWriter(RedisConnectionFactory redisConnectionFactory) {
                RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
                return redisCacheWriter;
                }
                @Bean
                public CacheManager cacheManager(RedisCacheWriter redisCacheWriter, RedisCacheConfiguration redisCacheConfiguration) {
                    CacheManager cacheManager = new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
                    ((RedisCacheManager) cacheManager).isTransactionAware();
                    return cacheManager;
                }
                

                3. 自行通过注解和AOP实现缓存

                • 引入AOP相关的包
                  
                    org.springframework
                    spring-aop
                    5.3.22
                  
                  
                    org.springframework
                    spring-aspects
                    5.3.22
                  
                  
                  
                    com.fasterxml.jackson.core
                    jackson-core
                    2.9.8
                  
                  
                    com.fasterxml.jackson.core
                    jackson-databind
                    2.9.8
                  
                  
                    com.fasterxml.jackson.core
                    jackson-annotations
                    2.9.8
                  
                  
                    com.alibaba
                    fastjson
                    1.2.28
                  
                  
                  • 创建@CustomCache
                    @Target(ElementType.METHOD)  
                    @Retention(RetentionPolicy.RUNTIME)
                    public @interface  CustomCache {  
                        /**
                       * key的规则,可以使用springEL表达式,可以使用方法执行的一些参数
                       */
                        String key();
                        /**
                       *  类似前缀
                       * @return
                       */
                        String value();
                    }
                    
                    • 修改AppConfig
                      @EnableAspectJAutoProxy // 开启AOP自动代理
                      public class AppConfig {
                          @Bean  
                          public RedisConnectionFactory redisConnectionFactory() {
                              RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
                              redisStandaloneConfiguration.setHostName(host);
                              redisStandaloneConfiguration.setPort(port); 
                              // 自适应集群变化
                              RedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory(redisStandaloneConfiguration);
                              return redisConnectionFactory;
                          }
                          @Bean
                          public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
                              RedisTemplate redisTemplate = new RedisTemplate();
                              redisTemplate.setConnectionFactory(redisConnectionFactory);
                              redisTemplate.setKeySerializer(StringRedisSerializer.UTF_8);
                              redisTemplate.setValueSerializer(StringRedisSerializer.UTF_8);
                              return redisTemplate;
                          }
                      }
                      
                      • 创建 CustomCacheAspect
                        @Component  
                        @Aspect  
                        public class CustomCacheAspect {
                            @Autowired  
                            private RedisTemplate redisTemplate;
                            @Pointcut("@annotation(cn.lazyfennec.cache.redis.annotation.CustomCache)")
                            public void cachePointcut() {
                            }
                            @Around("cachePointcut()")
                            public Object doCache(ProceedingJoinPoint joinPoint) {
                                Object obj = null;
                                try {
                                    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
                                    Method method = joinPoint.getTarget().getClass().getMethod(signature.getName(), signature.getMethod().getParameterTypes());
                                    CustomCache customCache = method.getAnnotation(CustomCache.class);
                                    String cacheKey = customCache.key();
                                    String cacheValue = customCache.value(); 
                                    // 创建解析器
                                    ExpressionParser parser = new SpelExpressionParser();
                                    Expression expression = parser.parseExpression(cacheKey);
                                    EvaluationContext context = new StandardEvaluationContext(); // 参数
                                    // 添加参数
                                    Object[] args = joinPoint.getArgs();
                                    DefaultParameterNameDiscoverer discover = new DefaultParameterNameDiscoverer();
                                    String[] parameterNames = discover.getParameterNames(method);
                                    for (int i = 0; i < parameterNames.length; i++) {
                                        context.setVariable(parameterNames[i], args[i].toString());
                                    }
                                    // 解析
                                    String key = cacheValue + "::" + expression.getValue(context).toString();
                                    // 1、 判定缓存中是否存在
                                    obj = redisTemplate.opsForValue().get(key);
                                    if (obj != null) return obj;
                                    // 2、不存在则继续行方法
                                    obj = joinPoint.proceed();
                                    // 3、 同步存储value到缓存。
                                    redisTemplate.opsForValue().set(key, obj);
                                } catch (NoSuchMethodException e) {
                                    e.printStackTrace();
                                } catch (Throwable throwable) {
                                    throwable.printStackTrace();
                                }
                                return obj;
                            }
                        }