springboot custom Cache @ Cache

Statement:

If the article is inappropriate, please summarize it for your own reference.

 

The source code of this article has been uploaded to Baidu online disk.

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

Extraction code: ric4

 

Spring Boot is a new framework provided by pivot team. It is designed to simplify the initial construction and development process of new spring applications.

In Spring Boot applications, there are many basic data in the database that will not change for a long time.

When the concurrency of Spring Boot applications becomes high, always reading these basic data from the database will increase the access pressure of the database.

Therefore, @ Cacheable, @ CachePut, @ CacheEvict and @ Caching are defined in Spring Boot for Caching. Many blogs have introduced and used them, which will not be introduced here.

 

The caching scheme provided in Spring Boot has some disadvantages according to personal project experience:

1. @ Cacheable is defined on the method. Mark that the returned results of the method need to be cached.

a. If you need to cache the return result of a method in the parent class, you need to copy the method of the parent class and add @ Cacheable in the subclass.

b. For multiple methods and methods with similar names, if you need to cache, you need to @ Cacheable multiple times.

2. @ CacheEvict is defined on the method and is used to mark and clear a cache.

a. For multiple methods and methods with similar names, you need to clear the cache multiple times @ CacheEvict.

b. For related functions (such as users and departments), use @ CacheEvict in multiple places and times.

 

Our software developers are also very lazy. If we can centrally configure the cache, we don't have to modify the cache annotation in multiple places.

To solve these problems, I designed a simple caching scheme by using the aspect oriented of spring:

1. Add cache annotations to the class definition.

2. In cache annotation

a. Define the cache keyword key of this class

b. Associated cache key (multiple)

c. When calling methods with similar names, clear the cache

d. When calling methods with similar names, the returned results of these methods need to be cached.

3. Define facet classes, cut into these methods, and implement relevant logic according to cache annotations. If the subclass calls the method of the parent class, it can also carry out the relevant caching logic of the aspect.

4. redis, memcached and other technologies are used to realize data caching.

5. Since this is just a demonstration, the final static Map is used to cache data. The method directly new data without reading the database.

 

In idea, create a spring boot project, which contains a test case environment. Many blogs have related introductions, which will not be explained here.

I am also a lazy person, ha ha.

The project source code directory is shown in the figure below:

 

Custom Cache annotation @ Cache, the source code is as follows

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;

/**
 * Cache annotation
 *
 * @author zhanglinlin
 * @Description:
 * @date 2020 17:04, October 26
 */
@Target({ElementType.TYPE})
@Retention(RUNTIME)
public @interface Cache {

    /**
     * Cache key
     *
     * @return
     */
    String key() default "";

    /**
     * Associated cleared key
     *
     * @return
     */
    String[] cleanKeys() default {};

    /**
     * If the method name contains the specified string, the cache returns the result
     *
     * @return
     */
    String[] cacheMethods() default {"list"};

    /**
     * If the method name contains the specified string, the cache is cleared
     *
     * @return
     */
    String[] cleanMethods() default {"insert", "save", "update", "delete", "edit", "change"};

}

 

The facet class CacheAspect implements the logic related to caching. The source code is as follows

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;

/**
 * Cache slice
 *
 * @author zhanglinlin
 * @Description:
 * @date 2020 17:21, October 26
 */
@Aspect
@Component
public class CacheAspect {

    /**
     * Here, Map is used as the cache. redis, memcached and other caches can be used in practical application
     */
    private final static Map<String, Object> cacheMap = new HashMap<>();

    /**
     * Define the pointcut and all methods under the package and its sub packages
     */
    @Pointcut("execution(public * com.example.demo.service..*.*(..)))")
    public void cacheAspect() {
    }

    /**
     * Intercept the pointcut and do cache related processing
     *
     * @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();
    }

    /**
     * Check whether the relevant cache needs to be cleared according to the calling method
     *
     * @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);
                    }
                }
            }
        }
    }

    /**
     * Check whether caching is required according to the calling method
     *
     * @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("Add cache key: " + cacheKey);
                            CacheAspect.cacheMap.put(cacheKey, object);
                        }
                    } else {
                        System.out.println("Get cache key: " + cacheKey);
                    }
                    return object;
                }
            }
        }

        return joinPoint.proceed();
    }

    /**
     * Clear key cache
     *
     * @param key
     * @throws Exception
     */
    private void cleanKey(String key) {
        System.out.println("Clear prefix 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("Clear cache key: " + cacheKey);
                    CacheAspect.cacheMap.remove(cacheKey);
                }
            }
        }
    }

    /**
     * Get cache 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();
    }

    /**
     * Verify whether the method name is in the given array
     *
     * @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;
    }

}

 

Interfaces BaseService and TestService, and implementation classes BaseServiceImpl and TestServiceImpl are used to demonstrate the methods of calling subclasses, which are implemented in the parent class.

 

BaseService source code is as follows

package com.example.demo.service;

import java.util.List;

/**
 * @author zhanglinlin
 * @Description:
 * @date 2020 18:30, October 26
 */
public interface BaseService {

    public void insert(String s);

    public List<String> list2(String s);

}

 

The source code of BaseServiceImpl is as follows:

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 18:31, October 26
 */
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 source code is as follows

package com.example.demo.service;

import java.util.List;

/**
 * @author zhanglinlin
 * @Description:
 * @date 2020 17:15, October 26
 */
public interface TestService extends BaseService {

}

 

Add a custom cache annotation to the class definition of TestServiceImpl. The source code is as follows

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 17:15, October 26
 */
@Service
@Cache(key = "testKey", cleanKeys = {"testCleanKey"})
public class TestServiceImpl extends BaseServiceImpl implements TestService {

}

 

The source code of the test case DemoApplicationTests is as follows

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("for the first time insert");
        testService.insert("ss");
        System.out.println();
        Thread.sleep(2 * 1000);

        System.out.println("for the first time list2");
        List<String> list = testService.list2("ss");
        System.out.println(list);
        System.out.println();
        Thread.sleep(2 * 1000);

        System.out.println("The second time list2");
        list = testService.list2("ss");
        System.out.println(list);
        System.out.println();
        Thread.sleep(2 * 1000);

        System.out.println("The second time insert");
        testService.insert("ss");
        System.out.println();
        Thread.sleep(2 * 1000);

        System.out.println("third time list2");
        list = testService.list2("ss");
        System.out.println(list);
        System.out.println();
        Thread.sleep(2 * 1000);

        System.out.println("Fourth time list2");
        list = testService.list2("ss");
        System.out.println(list);
        System.out.println();
        Thread.sleep(2 * 1000);

    }

}

 

Run the test case, and the results are as follows:

 

Tags: Java Spring Spring Boot

Posted by jacko_162 on Mon, 09 May 2022 15:19:03 +0300