Use AOP technology to implement unified caching through custom annotations

Using AOP technology to implement custom cache annotations

Using java for business development, caching is an essential performance improvement technology. If the cache logic is added to the business code, there are several disadvantages: 1) Code redundancy. 2) The code logic is highly intrusive, and it is easy to introduce bugs. 3) Different people have different code styles, which makes later maintenance difficult. There is a way to add cache annotations on the entry. Before the interface is called, the cache is automatically obtained. Without the cache, the interface logic is followed. This is much clearer and the code is much cleaner. To realize the above idea, three parts need to be done
Step 1: Write aspect logic and create an aspect file, as follows:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.local.datashadow.annotion.LocalRedisCache;
import com.local.datashadow.utils.DateUtils;
import com.local.datashadow.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
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.expression.EvaluationContext;
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.Type;
import java.util.concurrent.TimeUnit;

/**
 * @Author : test
 * @create 2022/12/07 14:42
 * description:
 */
@Slf4j
@Aspect
@Component
public class RedisCacheAspect {
    
    

    private static ExpressionParser parser = new SpelExpressionParser();

    @Pointcut("@annotation(com.local.datashadow.annotion.LocalRedisCache)")//注解在方法上
    public void pointcut() {
    
    
        System.out.println( "进入切面执行" );
    }

    /**
     * 环绕通知
     * 根据redisKey先查缓存、缓存没有就查数据库,然后将结果缓存
     */
    @Around(value = "pointcut() && @annotation(localRedisCache)", argNames = "pjp,localRedisCache")
    public Object AroundMethod(ProceedingJoinPoint pjp, LocalRedisCache localRedisCache) throws Throwable {
    
    
        log.info("redis缓存切面开始");
        String redisKey = localRedisCache.redisKey();
        // 1.获取参数对象数组
        Object[] args = pjp.getArgs();
        // 2.获取参数名数组
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        String[] argNames = methodSignature.getParameterNames();
        // 3.将参数名和参数值数组放入context
        EvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < args.length; i++) {
    
    
            context.setVariable(argNames[i], args[i]);
        }
        //spring EL表达式,此处进行了key的组装,此处需要注意,如果key不需要带参数,需要把key放到注解的单引号里引起来,不然报错
        redisKey = buildKey(redisKey,context);
        String redisEventValue = RedisUtil.get( redisKey );
        long expirationTime = localRedisCache.expirationTime();
        if (expirationTime == 0 ){
    
    
            log.info("获取的缓存时长=",expirationTime);
            expirationTime = DateUtils.getExpirationTime();
        }
        Type genericReturnType = methodSignature.getMethod().getGenericReturnType();
        if(redisEventValue == null){
    
    
            log.info("redis key="+redisKey+"缓存不存在...");
            Object result = pjp.proceed();
            if (result != null){
    
    
                JSONObject jsonObject = (JSONObject)JSON.toJSON(result);
                if (("00000".equals(jsonObject.getString("rspCode")) ||
                        "00000".equals(jsonObject.getString("code")))
                        && (jsonObject.getInteger("resultDataStatus") == 1)){
    
    
                    log.info("将 key="+redisKey+" 缓存到redis里面...");
                    RedisUtil.set(redisKey, JSON.toJSONString(result), expirationTime, TimeUnit.MILLISECONDS);
                }

            } else {
    
    
                log.info("查询结果不存在不缓存到redis里面...");
            }
            return result;
        }else {
    
    
            log.info(" key="+redisKey+"的结果是从缓存里面获取的");
            Object returnObj = JSONObject.parseObject(redisEventValue, genericReturnType);
            return returnObj;
        }
    }

    protected static String buildKey(String key, EvaluationContext context) {
    
    
        String combined = "";
        if (key != null && key.length()>0) {
    
    
            combined = parser.parseExpression(key).getValue(context).toString();
        }
        return combined;
    }
}

Part 2: Write a custom annotation interface

package com.local.datashadow.annotion;

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

/**
 * @Author : test
 * @create 2022/12/07 14:28
 * description:
 * Redis 缓存设置
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LocalRedisCache {
    
    

    String redisKey()  default "";

    /**
     * 缓存默认时长毫秒
     * @return
     */
    long expirationTime() default 0;


}

The attributes in the interface are added according to your own needs. This attribute will be intercepted through the aspect, and then obtained in the aspect for corresponding processing.
Step 3: Use method 1:
After the above work is done, start to use it. In the service implementation class, add annotations to the method. For example,
as follows:

@LocalRedisCache(redisKey = "'dataShadow:kpi_month_user_active_summary'")
    @Override
    public BaseResponse getMonthUserActiveSummary() {
    
    
        log.info("月活逻辑处理开始");
        SummarySearchDTO searchDto = new SummarySearchDTO();
        searchDto.setDate6Month();
        String endMonth = searchDto.getEndDate().substring(0, 6);
        String starMonth = searchDto.getStarDate().substring(0, 6);
        searchDto.setEndDate(endMonth);
        searchDto.setStarDate(starMonth);
注意上面使用的注解,就是自定义注解,注解括号里的参数就是自定义注解接口中定义的参数

Step 3: Use method 2:
Some rediskeys need to be associated with parameters, such as recommended content for users, because different user ids or device ids have different recommendations. At this time, rediskey needs to be associated with user attributes, and also That is, rediskey needs to bring parameters
such as:

@Override
    @LocalRedisCache(
            redisKey = "'dataShadow:event_property_' + #carEventDimPo.eventCode",expirationTime = 86400000)
    public BaseResponse<List> getCarEventProperties(CarEventDimPo carEventDimPo) {
    
    
        try {
    
    
            Integer eventNum = carEventDimPo.getEventNum();
            List<String> eventCodeList = new ArrayList<>();
            //查询公共属性
            eventCodeList.add("all");
        使用#号取参数,直接把请求参数中的这个值获取到,这个方式是使用了EL表达式,EL表达式自行学习。

It is worth noting that the string in the key needs to be enclosed in single quotes to be processed into a string.

The above three steps are for the use of redis. Of course, preparatory work is required before use, such as configuring the redis environment. The following configuration file

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig extends CachingConfigurerSupport {
    
    

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    
    

        RedisTemplate<String, Object> template = new RedisTemplate<>();

        RedisSerializer<String> redisSerializer = new StringRedisSerializer();

        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        template.setConnectionFactory(factory);
        //key序列化方式
        template.setKeySerializer(redisSerializer);
        //value序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //value hashmap序列化
        template.setHashKeySerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);


        return template;
    }
}

The properties parameter of redis is configured in the nacos unified file.

spring:
  main: 
    allow-bean-definition-overriding: true
  redis:
    # Redis服务器地址 ***.**.**.***
    host: ***************.com
    # Redis服务器连接端口
    port: 6379
    # Redis数据库索引(默认为0)
    database: 2
    # Redis服务器连接密码(默认为空)
    password: ********
    # 连接池最大连接数(使用负值表示没有限制)
    max-active: 8
    # 连接池最大阻塞等待时间(使用负值表示没有限制)
    max-wait: -1
    # 连接池中的最大空闲连接
    max-idle: 8
    # 连接池中的最小空闲连接
    min-idle: 0
    # 连接超时时间(毫秒)
    #timeout: 30000

In addition to configuration files, there are public method classes such as redis storage and retrieval.

Guess you like

Origin blog.csdn.net/weixin_48363639/article/details/128399141