SpringBoot自定义分布式缓存starter

1.创建maven项目,添加依赖

项目结构

image-202203142****8031

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.sgg</groupId>
    <artifactId>aopCache-spring-boot-start</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>aopCache-spring-boot-start</name>
    <description>aopCache-spring-boot-start</description>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
    </properties>

    <dependencies>
        <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- spring2.X集成redis所需common-pool2-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.6.0</version>
        </dependency>

        <!-- redisson 分布式锁-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.15.3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

2.自定义注解

1.@EnableGmalllCache 启动功能注解

package com.sgg.aopcache.cache.anno;

import com.sgg.aopcache.cache.selector.AopCacheSelector;
import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Import(AopCacheSelector.class)
public @interface EnableGmalllCache {
}

2.@GmalllCache 目标方法缓存注解

@Target({ ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME) 
@Documented
public @interface GmalllCache {

    String cacheKey() default ""; //支持写表达式

    String bloomName() default "";//默认是不用布隆

    String bloomValue() default "";//布隆需要判断哪个值的表达式

    String lockKey() default ""; // 分布式锁值

}

3.配置类

1.RedissonProperties 配置信息绑定类

/**
 * redisson配置信息
 */
@Data
@ConfigurationProperties("spring.redis")
public class RedissonProperties {

    private String host = "localhost";

    private String addresses = "";

    private String password = "";

    private String port = "6379";

    private int timeout = 3000;
    private int connectionPoolSize = 64;
    private int connectionMinimumIdleSize=10;
    private int pingConnectionInterval = 60000;
    public static String ADDRESS_PREFIX = "redis://";


}

2. AopCacheConf 组件配置类

public class AopCacheConf {

    //需要 redisTemplate  redissonClient

    @Bean
    public CacheService cacheService(StringRedisTemplate stringRedisTemplate,RedissonClient redissonClient)
    {
        if (StringUtils.isEmpty(redissonClient)){
            throw new RuntimeException("请往容器中添加RedissonClient组件");
        }
        System.out.println("cacheService方法执行了,往容器中添加CacheService组件");
        return new CacheServiceImpl(stringRedisTemplate,redissonClient);
    }


    @Bean
    public GmallCacheAspect gmallCacheAspect(CacheService cacheService)
    {
        System.out.println("GmallCacheAspect方法执行了,往容器中添加GmallCacheAspect组件");
        return new GmallCacheAspect(cacheService);
    }

}

3.RedissonAotoConfiguration 自动装配类

@EnableConfigurationProperties(RedissonProperties.class)
public class RedissonAotoConfiguration {



    /**
     * 自动装配
     *
     */


    @Bean
    RedissonClient redissonClient(RedissonProperties redissonConfig) {
        Config config = new Config();
        if(StringUtils.isEmpty(redissonConfig.getHost())){
            throw new RuntimeException("host is  empty");
        }
        SingleServerConfig serverConfig = config.useSingleServer()
                .setAddress(RedissonProperties.ADDRESS_PREFIX + redissonConfig.getHost() + ":" +redissonConfig.getPort())
                .setTimeout(redissonConfig.getTimeout())
                .setPingConnectionInterval(redissonConfig.getPingConnectionInterval())
                .setConnectionPoolSize(redissonConfig.getConnectionPoolSize())
                .setConnectionMinimumIdleSize(redissonConfig.getConnectionMinimumIdleSize())
                ;
        if(!StringUtils.isEmpty(redissonConfig.getPassword())) {
            serverConfig.setPassword(redissonConfig.getPassword());
        }
        // RedissonClient redisson = Redisson.create(config);
        System.out.println("RedissonClient组件创建完成");
        return Redisson.create(config);
    }

}

4.Selector

/**
 * @author sz
 * @DATE 2022/3/13  17:54
 */
public class AopCacheSelector implements ImportSelector {

    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //要添加的组件的全限定类名
        System.out.println("selectImports方法执行了,往容器中添加AopCacheConf组件");
        return new String[]{"com.sgg.aopcache.cache.conf.AopCacheConf"};
    }
}

5.切面类

@Slf4j
@Aspect
public class GmallCacheAspect {


    //没抢到默认的等待时间
    private AtomicLong atomicLong = new AtomicLong(1000);

    private CacheService cacheService;

    public GmallCacheAspect(CacheService cacheService) {
        this.cacheService = cacheService;
    }

    public GmallCacheAspect() {

    }

    @Around("@annotation(com.sgg.aopcache.cache.anno.GmalllCache)")
    public Object around(ProceedingJoinPoint joinPoint) throws Exception {
        //先拿到目标方法执行的参数
        Object[] args = joinPoint.getArgs();
        //拿到方法声明
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //拿到方法声明上注解
        Method method = signature.getMethod();
        //获取方法方法的返回值类型
        final Type returnType = method.getGenericReturnType();
        GmalllCache cacheAnnotation = method.getAnnotation(GmalllCache.class);
        //拿到注解上的值  缓存数据的key
        String cacheKeyExpr = cacheAnnotation.cacheKey();
        String bloomValueExpr = cacheAnnotation.bloomValue();
        //Sp EL表达式 解析
        String cacheKey = parseSpel(cacheKeyExpr, joinPoint,"cacheKey");

        Long bloomValue=null;

        if (!StringUtils.isEmpty(bloomValueExpr)){
            String bloomVlaueStr = parseSpel(bloomValueExpr, joinPoint,"bloomValue");
             bloomValue = Long.valueOf(bloomVlaueStr);
        }

       while (true){
           //从缓冲中拿数据  并根据方法的返回值类型封装数据
           Object dataFromCache = cacheService.getDataFromCache(cacheKey, new TypeReference<Object>() {
               @Override
               public Type getType() {
                   //返回方法对应的返回类型
                   return returnType;
               }
           });

           if (StringUtils.isEmpty(dataFromCache)) {
               //缓存中没有数据  回源  经过布隆过滤器和锁
               boolean contains = true;

               if (!StringUtils.isEmpty(bloomValue)){

                   RBloomFilter bloomFilter = cacheService.getBloomFilter(cacheAnnotation.bloomName());
                   //布隆过滤器判断是否有值
                   contains = bloomFilter.contains(bloomValue);
               }

               if (contains) {
                   //如果布隆过滤器中有这个对象
                   //获取注解的分布式锁 lockKey的值
                   String lockKeyExpr = cacheAnnotation.lockKey();
                   String lockKey = (String) parseSpel(lockKeyExpr, joinPoint,"lockKey");
                   //连接点方法的返回值对象
                   Object detail = null;

                   //如果锁的值为空  不使用分布式锁  直接查数据库
                   if (StringUtils.isEmpty(lockKey)) {
                       detail = getProceed(joinPoint);

                   } else {
                       //拿到锁对象
                       RLock rLock = cacheService.getLock(lockKey);


                       boolean tryLockFlag = false;

                       try {
                           //尝试加锁
                           tryLockFlag = rLock.tryLock();

                           //如果加锁成功
                           if (tryLockFlag) {
                               long curr = System.currentTimeMillis();
                               //查询数据库  也就是执行目标方法
                               detail = getProceed(joinPoint);
                               atomicLong.set(System.currentTimeMillis() - curr);
                               //
                               //将切入点方法查询到的数据存入缓存
                               cacheService.saveCacheData(cacheKey, detail, returnType);
                               return detail;
                           } else {
                               //如果加锁失败  等待一秒后重新查询
                               TimeUnit.MINUTES.sleep(atomicLong.get());
                           }

                       } catch (InterruptedException e) {
                           log.error("加锁出现了异常");
                           throw new RuntimeException(e);
                       } finally {

                           try {
                               //加锁成功后解锁
                               if (tryLockFlag) {
                                   rLock.unlock();
                               }
                           } catch (Exception e) {
                               log.error("解错了锁");
                               throw new RuntimeException(e);
                           }
                       }
                   }
               } else {
                   //如果布隆过滤器中没有,返回空对象
                   return null;
               }
           } else {
               //缓存中有数据  返回缓存中数据
               return dataFromCache;
           }
       }


    }

    /**
     * 执行目标方法  获取返回数据
     *
     * @param joinPoint
     * @return
     */
    private Object getProceed(ProceedingJoinPoint joinPoint) {

        Object proceed = null;


        try {
            //前置通知

            Object[] args = joinPoint.getArgs();

            proceed = joinPoint.proceed(args);
            //返回后通知
        } catch (Throwable e) {
            //异常通知
            throw new RuntimeException(e);
        } finally {
            //后置通知
        }

        return proceed;
    }

    /**
     * 解析spel表达式的值
     *
     * @param cacheKey
     * @return
     */
    private String parseSpel(String cacheKey, ProceedingJoinPoint joinPoint,String type) {

        //拿到方法的参数值
        Object[] args = joinPoint.getArgs();


        //拿到方法对象
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();


        if (args.length==0){
            if (type.contains("lockKey")){
                return signature.getMethod().getName()+":lockKey:";
            }
            if (type.contains("cacheKey")){
                return signature.getMethod().getName()+":cacheKey:";
            }
        }


        ExpressionParser parser = new SpelExpressionParser();

        StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
        evaluationContext.setVariable("args", args);
        evaluationContext.setVariable("method", signature);

        String value = parser.parseExpression(cacheKey, new TemplateParserContext())
                .getValue(evaluationContext, String.class);

        return value;

    }

}

6.CacheService

CacheService接口

public interface CacheService {

    /**
     * 从缓冲中获取数据
     * @param spelValue
     * @param typeReference
     * @return
     */
   <T> T getDataFromCache(String spelValue, TypeReference<T> typeReference) throws Exception;

    /**
     * 获取布隆过滤器名
     * @param bloomName
     */
    RBloomFilter getBloomFilter(String bloomName);

    /**
     * 获取一把分布式锁
     * @param lockKey
     * @return
     */
    RLock getLock(String lockKey);

    /**
     * 将数据存入缓存
     * @param cacheKey
     * @param detail
     * @param returnType
     */
    void saveCacheData(String cacheKey, Object detail, Type returnType) throws Exception;
}

CacheServiceImpl 实现类

/**
 * @author sz
 * @DATE 2022/3/13  13:29
 */
public class CacheServiceImpl implements CacheService {

    StringRedisTemplate redisTemplate; //操作redis

    RedissonClient redissonClient; //使用redisson

    ObjectMapper objectMapper = new ObjectMapper();

    public CacheServiceImpl(StringRedisTemplate redisTemplate, RedissonClient redissonClient) {
        this.redisTemplate = redisTemplate;
        this.redissonClient = redissonClient;
    }


    /**
     * 从缓冲获取数据
     *
     * @param spelValue
     * @param returnType
     * @return
     */
    public <T> T getDataFromCache(String spelValue, TypeReference<T> returnType) throws Exception {
        String json = redisTemplate.opsForValue().get(spelValue);

        if (StringUtils.isEmpty(json)) {
            return null;
        }

        T value = objectMapper.readValue(json, returnType);

        return value;

    }

    /**
     * 获取布隆过滤器
     *
     * @param bloomName
     */
    public RBloomFilter getBloomFilter(String bloomName) {
        RBloomFilter<Object> bloomFilter = redissonClient.getBloomFilter(bloomName);
        return bloomFilter;
    }

    /**
     * 获取分布式锁
     *
     * @param lockKey
     * @return
     */
    public RLock getLock(String lockKey) {
        return redissonClient.getLock(lockKey);
    }

    /**
     * 将数据存入缓存
     * @param cacheKey
     * @param detail
     * @param returnType
     */
    public void saveCacheData(String cacheKey, Object detail, Type returnType) throws Exception {

        //要缓存的数据
        Object target = detail;

        //缓存空值
        if (StringUtils.isEmpty(target)) {
            //如果从数据库中查询的数据时null
            redisTemplate.opsForValue().set(cacheKey,"n",30, TimeUnit.MINUTES);
        }else {

            String writeValueAsString = new ObjectMapper().writeValueAsString(target);

            redisTemplate.opsForValue().set(cacheKey, writeValueAsString,3, TimeUnit.DAYS);

        }


    }
}
#java求职##学习路径#
全部评论

相关推荐

02-24 10:34
门头沟学院 Java
已注销:之前发最美的女孩基本爱答不理,发最帅的hr终于有反馈了,女孩子也要自信起来
点赞 评论 收藏
分享
冰皮月饼_FLORRIEEE:你是准备投产品嘛?可以重新整理一下实习的bulletpoint,侧重描述你的工作所带来的结果收益,不要只写泛泛的内容(比如改写通过xx数据分析,提升xx),产品的价值并不在处理和分析数据的过程
点赞 评论 收藏
分享
评论
点赞
3
分享

创作者周榜

更多
牛客网
牛客企业服务