Springboot custom RedisCache annotation

Cache annotation provided by springboot

When we are writing the query database service, we need to query whether there is a cache of the required data in redis. If there is, we don't need to query the database, which undoubtedly improves the query speed and reduces the pressure on the database.
When using springboot to integrate redis, I believe everyone is familiar with the @Cacheable annotation.
For example, we need to query in the cache before the Service queries the database. If you are not querying the database, you will not query. We only need to configure the redisTemplate Add the @EnableCaching annotation to the class and configure the Spring Cache annotation function, as follows:

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;
    }
}

Then add an annotation on the service method

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

Before the method is executed, the redis cache will be queried. If the result is found, the method will not be executed. If there is no result, the method will be executed.


When we need to customize the cache query logic, we need to define annotations.

Custom Cache annotation

Import related dependencies

<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>

First we need to define an annotation

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();
}

The annotation is defined on the method, and a parameter key needs to be passed in.
Define 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;
    }
}

Use custom annotations on methods

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

Is it easy to use?
Of course don't forget to configure 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;
    }
}

Here is a point to remind everyone:
the object we store in redis is actually a serialized string.
In the process of deserialization, that is, when we query the cache from redis and convert it to an object, if the current package path of the object is different from the package path of the object when it is stored, the deserialization will fail.

Guess you like

Origin blog.csdn.net/weixin_42494845/article/details/108911440