springboot自定义缓存@Cache

声明:

本篇文章根据个人项目经验,进行的项目总结,仅代表个人意见,给大家提供个参考,如有不当之处,敬请提出,共同讨论,共同进步。

 

本篇文章的源码已上传百度网盘。

链接: https://pan.baidu.com/s/1lS6Ff6gZFk6mirEagP-rcQ

提取码: ric4

 

Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。

Spring Boot应用中,数据库中有许多基础数据长时间是不会变化的。

当Spring Boot应用的并发变高时候,总是从数据库读取这些基础数据,会增加数据库的访问压力。

因此Spring Boot中定义了@Cacheable、@CachePut、@CacheEvict、@Caching用于缓存,有很多博客做了相关介绍和使用,这里不做介绍。

 

Spring Boot中提供的缓存方案,根据个人项目经验,看到的一些弊端:

1、@Cacheable定义在方法上,标记该方法的返回结果需要缓存。

a、如果需要缓存父类中某个方法的返回结果,子类中需要复写父类的方法,并加上@Cacheable。

b、对于多个方法、名字类似的方法,如果需要缓存,就要多次@Cacheable。

2、@CacheEvict定义在方法上,用于标记清除某个缓存。

a、对于多个方法、名字类似的方法,需要清除缓存时候,就要多次@CacheEvict。

b、对于有关联的功能(比如用户与部门),就要多个地方、多次的使用@CacheEvict。

 

我们软件开发的人也是很懒的,如果能够集中配置缓存,我们就不用在多个地方修改缓存注解了。

针对我看到的这些问题,使用spring的面向切面,自己设计了简单的缓存方案:

1、在类的定义上添加缓存注解。

2、缓存注解中

a、定义该类的缓存关键字key

b、受关联的缓存关键字key (多个)

c、调用哪些方法、名字类似方法时候,要清除缓存

d、调用哪些方法、名字类似方法时候,这些方法的返回结果需要缓存。

3、定义切面类,切入到这些方法中,根据缓存注解,实现相关逻辑。如果子类调用父类的方法,也能进行切面的相关缓存逻辑。

4、使用redis、memcached等技术,实现数据的缓存。

5、由于这里只是演示,使用final static Map缓存数据,方法直接new数据,不读取数据库。

 

在idea中,创建spring boot项目,项目中含有测试用例环境,很多博客有相关介绍,这里不再说明。

我也是个懒人,哈哈哈哈。

项目源码目录如下图:

 

自定义缓存注解@Cache,源码如下

package com.example.demo.cache.annotations;

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

import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * 缓存注解
 *
 * @author zhanglinlin
 * @Description:
 * @date 2020年10月26日 17:04
 */
@Target({ElementType.TYPE})
@Retention(RUNTIME)
public @interface Cache {

    /**
     * 缓存key
     *
     * @return
     */
    String key() default "";

    /**
     * 关联清除的key
     *
     * @return
     */
    String[] cleanKeys() default {};

    /**
     * 方法名称含有指定字符串,则缓存返回结果
     *
     * @return
     */
    String[] cacheMethods() default {"list"};

    /**
     * 方法名称含有指定字符串,则清除缓存
     *
     * @return
     */
    String[] cleanMethods() default {"insert", "save", "update", "delete", "edit", "change"};

}

 

切面类CacheAspect,实现缓存的相关逻辑,源码如下

在实际使用中,发现不能对父类方法进行切面。

解决方案:定义多个切入点,将父类包含在切入点, 

@Around("cacheAspect()")  改为  @Around("cacheAspect() || cacheAspect2() ")

package com.example.demo.cache.aop;

import com.example.demo.cache.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.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * 缓存切面
 *
 * @author zhanglinlin
 * @Description:
 * @date 2020年10月26日 17:21
 */
@Aspect
@Component
public class CacheAspect {

    /**
     * 这里使用Map作为缓存,实际运用中可以使用redis、memcached等缓存
     */
    private final static Map<String, Object> cacheMap = new HashMap<>();

    /**
     * 定义切入点,包及其子包下的所有方法
     */
    @Pointcut("execution(public * com.example.demo.service..*.*(..)))")
    public void cacheAspect() {
    }

    /**
     * 拦截切入点,做缓存相关处理
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("cacheAspect()")
    public Object doAspect(ProceedingJoinPoint joinPoint) throws Throwable {
        Object target = joinPoint.getTarget();
        Cache cache = target.getClass().getAnnotation(Cache.class);
        if (cache != null) {
            String key = cache.key();
            if (key != null && key.length() != 0) {
                this.cleanKey(joinPoint);
                return this.cache(joinPoint);
            }
        }

        return joinPoint.proceed();
    }

    /**
     * 根据调用方法,检查是否需要清除相关缓存
     *
     * @param joinPoint
     * @throws Exception
     */
    private void cleanKey(ProceedingJoinPoint joinPoint) {
        Cache cache = joinPoint.getTarget().getClass().getAnnotation(Cache.class);
        if (cache != null) {
            String method = joinPoint.getSignature().getName();
            String[] methods = cache.cleanMethods();
            boolean methodFlag = this.methodFlag(method, methods);
            if (methodFlag) {
                this.cleanKey(cache.key());
                String[] cleanKeys = cache.cleanKeys();
                if (cleanKeys != null && cleanKeys.length != 0) {
                    for (String key : cleanKeys) {
                        this.cleanKey(key);
                    }
                }
            }
        }
    }

    /**
     * 根据调用方法,检查是否需要缓存
     *
     * @param joinPoint
     * @throws Exception
     */
    private Object cache(ProceedingJoinPoint joinPoint) throws Throwable {
        Cache cache = joinPoint.getTarget().getClass().getAnnotation(Cache.class);
        if (cache != null) {
            String key = cache.key();
            if (key != null && key.length() != 0) {
                String method = joinPoint.getSignature().getName();
                String[] methods = cache.cacheMethods();
                boolean methodFlag = this.methodFlag(method, methods);
                if (methodFlag) {
                    String cacheKey = this.cacheKey(cache.key(), joinPoint);
                    Object object = CacheAspect.cacheMap.get(cacheKey);
                    if (object == null) {
                        object = joinPoint.proceed();
                        if (object != null) {
                            System.out.println("添加缓存key:" + cacheKey);
                            CacheAspect.cacheMap.put(cacheKey, object);
                        }
                    } else {
                        System.out.println("获取缓存key:" + cacheKey);
                    }
                    return object;
                }
            }
        }

        return joinPoint.proceed();
    }

    /**
     * 清除key缓存
     *
     * @param key
     * @throws Exception
     */
    private void cleanKey(String key) {
        System.out.println("清除前缀key:" + key);
        if (key != null && key.length() != 0) {
            Set<String> cacheKeys = CacheAspect.cacheMap.keySet();
            for (String cacheKey : cacheKeys) {
                if (cacheKey.startsWith(key)) {
                    System.out.println("清除缓存key:" + cacheKey);
                    CacheAspect.cacheMap.remove(cacheKey);
                }
            }
        }
    }

    /**
     * 获取缓存key
     *
     * @param key
     * @param joinPoint
     * @return
     */
    private String cacheKey(String key, ProceedingJoinPoint joinPoint) {
        StringBuffer cacheKey = new StringBuffer();
        cacheKey.append(key);
        cacheKey.append(":");
        cacheKey.append(joinPoint.getTarget().getClass().getName());
        cacheKey.append(":");
        cacheKey.append(joinPoint.getSignature().getName());
        Object[] args = joinPoint.getArgs();
        if (args != null) {
            for (Object arg : args) {
                if (arg != null) {
                    cacheKey.append(":");
                    cacheKey.append(arg.toString());
                }
            }
        }
        return cacheKey.toString();
    }

    /**
     * 验证方法名称,是否在给定数组中
     *
     * @param method
     * @param methods
     * @return
     */
    private boolean methodFlag(String method, String[] methods) {
        boolean methodFlag = false;
        if (method != null && method.length() != 0 && methods != null && methods.length != 0) {
            method = method.toUpperCase();
            for (String m : methods) {
                if (method.contains(m.toUpperCase())) {
                    methodFlag = true;
                    break;
                }
            }
        }
        return methodFlag;
    }

}

 

接口BaseService、TestService,实现类BaseServiceImpl、TestServiceImpl,用于演示调用子类的方法,在父类中有实现。

 

BaseService源码如下

package com.example.demo.service;

import java.util.List;

/**
 * @author zhanglinlin
 * @Description:
 * @date 2020年10月26日 18:30
 */
public interface BaseService {

    public void insert(String s);

    public List<String> list2(String s);

}

 

BaseServiceImpl源码如下:

package com.example.demo.service.impl;

import com.example.demo.service.BaseService;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @author zhanglinlin
 * @Description:
 * @date 2020年10月26日 18:31
 */
public class BaseServiceImpl implements BaseService {


    public void insert(String s) {

    }

    public List<String> list2(String s) {
        List<String> list = new ArrayList<>();
        list.add(new Date().toString());
        return list;
    }

}

 

TestService源码如下

package com.example.demo.service;

import java.util.List;

/**
 * @author zhanglinlin
 * @Description:
 * @date 2020年10月26日 17:15
 */
public interface TestService extends BaseService {

}

 

TestServiceImpl的类定义上,添加自定义缓存注解,源码如下

package com.example.demo.service.impl;

import com.example.demo.cache.annotations.Cache;
import com.example.demo.service.TestService;
import org.springframework.stereotype.Service;

/**
 * @author zhanglinlin
 * @Description:
 * @date 2020年10月26日 17:15
 */
@Service
@Cache(key = "testKey", cleanKeys = {"testCleanKey"})
public class TestServiceImpl extends BaseServiceImpl implements TestService {

}

 

测试用例DemoApplicationTests的源码如下

package com.example.demo;

import com.example.demo.service.TestService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
class DemoApplicationTests {

    @Autowired
    TestService testService;

    @Test
    void test() throws InterruptedException {

        System.out.println("第一次insert");
        testService.insert("ss");
        System.out.println();
        Thread.sleep(2 * 1000);

        System.out.println("第一次list2");
        List<String> list = testService.list2("ss");
        System.out.println(list);
        System.out.println();
        Thread.sleep(2 * 1000);

        System.out.println("第二次list2");
        list = testService.list2("ss");
        System.out.println(list);
        System.out.println();
        Thread.sleep(2 * 1000);

        System.out.println("第二次insert");
        testService.insert("ss");
        System.out.println();
        Thread.sleep(2 * 1000);

        System.out.println("第三次list2");
        list = testService.list2("ss");
        System.out.println(list);
        System.out.println();
        Thread.sleep(2 * 1000);

        System.out.println("第四次list2");
        list = testService.list2("ss");
        System.out.println(list);
        System.out.println();
        Thread.sleep(2 * 1000);

    }

}

 

运行测试用例,结果如下图:

 

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值