springboot 自定义RedisCache注解

springboot提供的Cache注解

当我们在编写查询数据库Service时,需要现在redis中查询是否有所需数据的缓存,如果有,则无需去查询数据库,这无疑提高了查询速度同时减轻了数据库的压力。
在使用springboot整合redis时,相信大家都对@Cacheable这个注解不陌生吧
例如我们需要在Service查询数据库前先去缓存中查询,如果没有在查询数据库,有则不去查询,我们只需要在redisTemplate配置类中加入@EnableCaching注解,并配置Spring Cache注解功能,如下:

package com.sunyuqi.config;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@EnableCaching
class RedisTemplateConfig {
    
    

    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
    
    
        return new LettuceConnectionFactory(new RedisStandaloneConfiguration("127.0.0.1", 6379));
    }
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    
    
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
        return redisTemplate;
    }

    // 配置Spring Cache注解功能
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
    
    
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
        return cacheManager;
    }
}

然后在service方法上加上注解即可

	@Cacheable(cacheManager = "cacheManager", value = "testcache", key = "#userId")
    public User findUserById(String userId) throws Exception {
    
    
        //此处编写查询数据库代码
        User user = ....
        return user;
    }

该方法执行前会先从redis缓存中查询,如果查询到结果,则该方法不会执行,如果没有结果,该方法才会执行。


当我们需要自定义缓存查询逻辑时,就需要定义注解了。

自定义Cache注解

导入相关依赖

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.9</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.9</version>
</dependency>

首先我们需要定义一个注解

package com.sunyuqi.mycache.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * cache注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Cache {
    
    
    String key();
}

该注解定义在方法上,并且需要传入一个参数key。
定义AOP

package com.sunyuqi.mycache.aop;

import com.sunyuqi.mycache.annotations.Cache;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Component
@Aspect
public class CacheAspect {
    
    

    @Autowired
    private RedisTemplate redisTemplate;

    @Pointcut("@annotation(com.sunyuqi.mycache.annotations.Cache)")
    public void cachePointcut() {
    
    
    }
    // 定义相应的事件
    @Around("cachePointcut()")
    public Object doCache(ProceedingJoinPoint joinPoint) {
    
    
        Object value = null;
        try {
    
    
            // 获取当前方法上注解的内容
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            // 获取被注解的方法
            Method method = joinPoint.getTarget().getClass().getMethod(signature.getName(), signature.getMethod().getParameterTypes());
            Cache cacheAnnotation = method.getAnnotation(Cache.class);
            // 获取到传入注解中的key的值 即"#userID"(我们需要EL解析)
            String keyEl = cacheAnnotation.key();
            // 创建解析器
            ExpressionParser parser = new SpelExpressionParser();
            Expression expression = parser.parseExpression(keyEl);
            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());
            }
            // 解析EL表达式
            String key = expression.getValue(context).toString();

            // 查询缓存,并判定缓存中是否存在
            value = redisTemplate.opsForValue().get(key);
            if (value != null) {
    
    
                System.out.println("从缓存中查询到结果:" + value);
                return value;
            }
            // 缓存中没有结果,执行被注解的方法
            value = joinPoint.proceed();
            // 将结果存入缓存
            redisTemplate.opsForValue().set(key, value);
        } catch (Throwable throwable) {
    
    
            throwable.printStackTrace();
        }
        return value;
    }
}

在方法上使用自定义注解

	@Cache(key = "#userId")
    public User findUserById(String userId) throws Exception {
    
    
        //此处编写查询数据库代码
        User user = ....
        return user;
    }

使用方法是不是很简单?
当然别忘了配置redistemplate

package com.sunyuqi.mycache.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Profile;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@EnableAspectJAutoProxy
class MyRedisConfig {
    
    
    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
    
    
        return new LettuceConnectionFactory(new RedisStandaloneConfiguration("127.0.0.1", 6379));
    }

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    
    
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
        return redisTemplate;
    }
}

这里有一个点要提醒大家:
我们存入redis的对象实际上是序列化后的字符串。
在反序列化的过程中,也就是我们在从redis中查询缓存,并转化为对象时,如果当前该对象所在的包路径与存入时对象的包路径不同,则会反序列化失败。

猜你喜欢

转载自blog.csdn.net/weixin_42494845/article/details/108911440