Java如何设计内存框架呢?

书欣 Java经验 发布时间:2022-09-02 11:49:12 阅读数:14114 1
下文笔者讲述java设计内存框架的方法分享,如下所示

如何使用缓存呢?

缓存的常用操作
查询时:
   先读取缓存
   当缓存中没有数据,则触发真正的数据获取
   当缓存中有数据,则直接返回缓存中的数据;

   新增数据时,将数据写入缓存;

   删除数据时,删除对应的缓存数据
     且自定义每个KEY的缓存有效期

Spring Cache 提供缓存注解

@Cacheable
   此注解对方法配置
   可根据方法的请求参数对其进行缓存
@CacheEvict
   清空缓存
@CachePut
   保证方法被调用
   结果被缓存与@Cacheable区别在于是否每次都调用方法
   常用于更新

缓存架构的示例

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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.java265</groupId>
    <artifactId>custom-cache</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>custom-cache</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>Runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
            <version>2.6.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Cacheable注解

package com.java265.aop.annotation;

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

/**
 * @ClassName Cacheable
 * @Description 用于缓存读取
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Cacheable {

    String cacheName() default ""; // 缓存名称
    String cacheKey(); // 缓存key
    int expire() default 3600; // 有效时间(单位,秒),默认1个小时
    int reflash() default -1; // 缓存主动刷新时间(单位,秒)
}


package com.java265.aop.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @ClassName CacheEvict
 * @Description 缓存清除
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CacheEvict {
    String cacheName() default ""; //缓存名称
    String cacheKey(); //缓存key
    boolean allEntries() default false; //是否清空cacheName的全部数据
}

//CachePut 注解

package com.java265.aop.annotation;

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

/**
 * @ClassName CachePut
 * @Description 缓存写入
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CachePut {
    String cacheName() default ""; //缓存名称

    String cacheKey(); //缓存key

    int expire() default 3600; //有效期时间(单位:秒),默认1个小时
}

//缓存配置文件

package com.java265.aop.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @ClassName CachePut
 * @Description 缓存写入
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CachePut {
    String cacheName() default ""; //缓存名称
    String cacheKey(); //缓存key
    int expire() default 3600; //有效期时间(单位:秒),默认1个小时
}

//默认缓存键生成器
package com.java265.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;

/**
 * @Description: key生成策略,缓存名+缓存KEY(支持Spring EL表达式)
 */
@Component
public class DefaultKeyGenerator {
    private static Logger logger = LoggerFactory.getLogger(DefaultKeyGenerator.class);

    // 用于SpEL表达式解析
    private SpelExpressionParser parser = new SpelExpressionParser();

    // 用于获取方法参数定义名字
    private DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

    /**
     * @Description cache key生成
     * @Param cacheKey:key值必传,cacheNames:缓存名称,不传取方法路径
     **/
    public String generateKey(ProceedingJoinPoint pjp, String cacheName, String cacheKey) throws NoSuchMethodException {
        if (StringUtils.isEmpty(cacheKey)) {
            throw new NullPointerException("CacheKey can not be null...");
        }
        Signature signature = pjp.getSignature();
        if (cacheName == null) {
            cacheName = new String(signature.getDeclaringTypeName() + "." + signature.getName());
        }
        EvaluationContext evaluationContext = new StandardEvaluationContext();
        if (!(signature instanceof MethodSignature)) {
            throw new IllegalArgumentException("This annotation can only be used for methods...");
        }
        MethodSignature methodSignature = (MethodSignature) signature; //method参数列表
        Method method = pjp.getTarget().getClass().getMethod(methodSignature.getName(), methodSignature.getMethod().getParameterTypes());
        String[] parameterNames = nameDiscoverer.getParameterNames(method);
        Object[] args = pjp.getArgs();
        for (int i = 0; i < parameterNames.length; i++) {
            evaluationContext.setVariable(parameterNames[i], args[i]);
        }
        //解析cacheKey
        String result = "CacheName_" + cacheName + "_CacheKey_" + parser.parseExpression(cacheKey).getValue(evaluationContext, String.class); //暂时只使用String类型
        logger.info("=============>>> generateKeys : {}", result);
        return result;
    }
}

//AOP
package com.java265.aop;

import com.java265.customcache.aop.annotation.CacheEvict;
import com.java265.customcache.aop.annotation.CachePut;
import com.java265.customcache.aop.annotation.Cacheable;
import com.github.benmanes.caffeine.cache.Cache;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.SerializationUtils;

/** 
 * @Description: 自定义缓存注解AOP实现
 */
@Aspect
@Component
public class CacheableAspect {
    private static Logger logger = LoggerFactory.getLogger(CacheableAspect.class);

    @Autowired
    private Cache caffeineCache;

    @Autowired
    private DefaultKeyGenerator defaultKeyGenerator;

    /**
     * 读取缓存数据
     * 定义增强,pointcut连接点使用@annotaion(xxx)进行定义
     * @param pjp
     * @param cache
     * @return
     * @throws Throwable
     */
    @Around(value = "@annotation(cache)") // cache 与 下面参数名around对应
    public Object cache(final ProceedingJoinPoint pjp, Cacheable cache) throws Throwable {
        try {
            String key = defaultKeyGenerator.generateKey(pjp, cache.cacheName(), cache.cacheKey());
            Object valueData = null;
            // 获取缓存中的值
            Object value = caffeineCache.getIfPresent(key);
            if (value != null) {
                //如果缓存有值,需要判断刷新缓存设置和当前缓存的失效时间
                if (cache.reflash() > 0) {
                    //查询当前缓存失效时间是否在主动刷新规则范围内
                    // caffeine 中刷新的话可以使用同步加载的方式调用reflash()
                }
                return value;
            }
            //缓存中没有值,执行实际数据查询方法
            if (valueData == null) {
                valueData = pjp.proceed(); //写入缓存
            }
            if (cache.expire() > 0) {
                caffeineCache.put(key, valueData);
            } else { //否则设置缓存时间 ,序列化存储
                caffeineCache.put(key, valueData);
            }
            return valueData;
        } catch (Exception e) {
            logger.error("读取caffeine缓存失败,异常信息:" + e.getMessage());
            return pjp.proceed();
        }
    }

    /**
     * 新增缓存
     */
    @Around(value = "@annotation(cache)")
    public Object cachePut(final ProceedingJoinPoint pjp, CachePut cache) throws Throwable {
        try {
            String key = defaultKeyGenerator.generateKey(pjp, cache.cacheName(), cache.cacheKey());
            Object valueData = pjp.proceed();
            // 写入缓存
            // 由于 caffeine 是内存缓存,对每个可以设置超时支持并不够好。除非每个 key 都构造一个 cache 对象;可以使用 redis 代替
            if (cache.expire() > 0) {
                caffeineCache.put(key, SerializationUtils.serialize(pjp.getArgs()[0]));
            } else {
                caffeineCache.put(key, SerializationUtils.serialize(pjp.getArgs()[0]));
            }
            return valueData;
        } catch (Exception e) {
            logger.error("写入caffeine缓存失败,异常信息:" + e.getMessage());
            return pjp.proceed();
        }
    }

    /**
     * 删除缓存
     */
    @Around(value = "@annotation(cache)")
    public Object cacheEvict(final ProceedingJoinPoint pjp, CacheEvict cache) throws Throwable {
        try {
            String cacheName = cache.cacheName();
            boolean allEntries = cache.allEntries();
            if (allEntries) {
                if (cacheName == null) {
                    Signature signature = pjp.getSignature();
                    cacheName = new String(signature.getDeclaringTypeName() + "." + signature.getName());
                }
                caffeineCache.invalidate("CacheName_" + cacheName);
            } else {
                String key = defaultKeyGenerator.generateKey(pjp, cache.cacheName(), cache.cacheKey());
                caffeineCache.invalidate(key);
            }
        } catch (Exception e) {
            logger.error("删除caffeine缓存失败,异常信息:" + e.getMessage());
        }
        return pjp.proceed();
    }
}

//模拟实现类
package com.java265.entity;

import lombok.Data;
import java.io.Serializable;

@Data
public class Country implements Serializable {
    /**
     * 主键
     */
    private Integer id;

    /**
     * 名称
     */
    private String countryname;

    /**
     * 代码
     */
    private String countrycode;

}

//service相关

package com.java265.service;
import com.java265.entity.Country;
import java.util.list;
 
public interface ICountryService {

    List<Country> listAll();
    int save(Country country);
    int update(Country country);
    int deleteById(int id);
    Country getById(int id);
}
package com.java265.customcache.service.impl;

import com.java265.customcache.aop.annotation.CacheEvict;
import com.java265.customcache.aop.annotation.CachePut;
import com.java265.customcache.aop.annotation.Cacheable;
import com.java265.customcache.entity.Country;
import com.java265.customcache.mapper.CountryMapper;
import com.java265.customcache.service.ICountryService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * @Author: Neco
 * @Description:
 * @Date: create in 2022/6/14 13:07
 */
@Service
public class CountryServiceImpl implements ICountryService {

    @Resource
    private CountryMapper countryMapper;

    @Override
    public List<Country> listAll() {
        return countryMapper.listAll();
    }

    @Override
    @CachePut(cacheName = "country_", cacheKey = "#country.id", expire = 3600)
    public int save(Country country) {
        return countryMapper.save(country);
    }

    @Override
    @CacheEvict(cacheName = "country_", cacheKey = "#id")
    public int update(Country country) {
        return countryMapper.update(country);
    }

    @Override
    @CacheEvict(cacheName = "country_", cacheKey = "#id")
    public int deleteById(int id) {
        return countryMapper.deleteById(id);
    }

    @Override
    @Cacheable(cacheName = "country_", cacheKey = "#id", expire = 3600)
    public Country getById(int id) {
        return countryMapper.getById(id);
    }
}
9. Mapper
package com.java265.customcache.mapper;

import com.java265.customcache.entity.Country;
import org.apache.ibatis.annotations.*;
import java.util.List;

@Mapper
public interface CountryMapper {

    @Select("select * from country")
    List<Country> listAll();

    @Insert("insert into country (id, countryname, countrycode) values(#{country.id}, #{country.countryname}, #{country.countrycode})")
    int save(@Param("country") Country country);

    @Update("update country set countryname = #{country.countryname} and countrycode = #{country.countrycode} where id=#{country.id}")
    int update(@Param("country") Country country);
    
    @Delete("delete from country where id=#{country.id}")
    int deleteById(int id);

    @Select("select * from country where id = #{id}")
    Country getById(int id);

}

//Controller

package com.java265.customcache.controller;

import com.java265.customcache.entity.Country;
import com.java265.customcache.entity.vo.Result;
import com.java265.customcache.service.ICountryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;


@RestController
@RequestMapping("/cache")
public class CacheController {

    @Autowired
    private ICountryService countryService;

    @GetMapping("/country/list")
    public Result list() {
        List<Country> list = countryService.listAll();
        return Result.OK(list);
    }

    @GetMapping("/country/{id}")
    public Result getById(@PathVariable Integer id) {
        Country country = countryService.getById(id);
        return Result.OK(country);
    }

    @DeleteMapping("/country/{id}")
    public Result deleteById(@PathVariable Integer id) {
        countryService.deleteById(id);
        return Result.OK("删除成功!");
    }

    @PostMapping("/country/save")
    public Result save(@RequestBody Country country) {
        countryService.save(country);
        return Result.OK("保存成功!");
    }

    @PutMapping("/country/update")
    public Result update(@RequestBody Country country) {
        countryService.update(country);
        return Result.OK("更新成功!");
    }
}
多次访问 http://localhost:8080/cache/country/1
即可看出从缓存中获取数据
版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。

本文链接: https://www.Java265.com/JavaJingYan/202209/16620905974307.html

最近发表

热门文章

好文推荐

Java265.com

https://www.java265.com

站长统计|粤ICP备14097017号-3

Powered By Java265.com信息维护小组

使用手机扫描二维码

关注我们看更多资讯

java爱好者