Java缓存框架之Caffeine源码解析

 更新时间:2023年11月13日 08:39:52   作者:阿靖哦  
这篇文章主要介绍了Java缓存框架之Caffeine源码解析,Caffeine 是一个基于Java 8的高性能本地缓存框架,其结构和 Guava Cache 基本一样,api也一样,基本上很容易就能替换,需要的朋友可以参考下

前言

在系统中,有些数据,访问十分频繁,往往把这些数据放入分布式缓存中,但为了减少网络传输,加快响应速度,缓存分布式缓存读压力,会把这些数据缓存到本地JVM中,大多是先取本地缓存中,再取分布式缓存中的数据,Caffeine是一个高性能Java 缓存库,使用Java8对Guava缓存重写版本,在Spring Boot 2.0中将取代Guava

一. SpringBoot缓存注解相关知识点

1. @Cacheable:

@Cacheable可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。对于一个支持缓存的方法,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。Spring在缓存方法的返回值时是以键值对进行缓存的,值就是方法的返回结果,至于键的话,Spring又支持两种策略,默认策略和自定义策略,这个稍后会进行说明。需要注意的是当一个支持缓存的方法在对象内部被调用时是不会触发缓存功能的。@Cacheable可以指定三个属性,value、key和condition。

参数解释例子
value缓存的名称,在 spring 配置文件中定义,必须指定至少一个例如:@Cacheable(value=”mycache”)
key缓存的key,可以为空,如果指定要按照SpEL表达式编写,如不指定,则按照方法所有参数组合@Cacheable(value=”testcache”,key=”#userName”)
condition缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存@Cacheable(value=”testcache”,condition=”#userName.length()>2”)

使用案例

 // key 是指传入时的参数
 @Cacheable(value="users", key="#id")
 public Integer find(Integer id) {
     return id;
 }
// 表示第一个参数
@Cacheable(value="users", key="#p0")
 public Long find(Long id) {
     return id;
 }
// 表示User中的id值
 @Cacheable(value="users", key="#user.id")
 public User find(User user) {
      return user;
 }
 // 表示第一个参数里的id属性值
 @Cacheable(value="users", key="#p0.id")
 public User find(User user) {
     return user;
 }

除了上面的案例使用方法,还有以下几种

属性名称描述示例
methodName当前方法名#root.methodName
method当前方法#root.method.name
target当前被调用的对象#root.target
targetClass当前被调用的对象的class#root.targetClass
args当前方法参数组成的数组#root.args[0]
caches当前被调用的方法使用的Cache#root.caches[0].name

condition属性指定发生的条件

有的时候我们可能并不希望缓存一个方法所有的返回结果。通过condition属性可以实现这一功能。condition属性默认为空,表示将缓存所有的调用情形。其值是通过SpringEL表达式来指定的,当为true时表示进行缓存处理;当为false时表示不进行缓存处理,即每次调用该方法时该方法都会执行一次。如下示例表示只有当user的id为偶数时才会进行缓存。

  // 根据条件判断是否缓存
 @Cacheable(value="users", key="#user.id", condition="#user.id%2==0")
 public User find(User user) {
    return user;
 }

2. CacheEvict

@CacheEvict是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。@CacheEvict可以指定的属性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的语义与@Cacheable对应的属性类似。即value表示清除操作是发生在哪些Cache上的(对应Cache的名称);key表示需要清除的是哪个key,如未指定则会使用默认策略生成的key;condition表示清除操作发生的条件。下面我们来介绍一下新出现的两个属性allEntries和beforeInvocation。

allEntries属性

allEntries是boolean类型,表示是否需要清除缓存中的所有元素。默认为false,表示不需要。当指定了allEntries为true时,Spring Cache将忽略指定的key。有的时候我们需要Cache一下清除所有的元素,这比一个一个清除元素更有效率。

   @CacheEvict(value="user", allEntries=true)
   public void delete(Integer id) {
      System.out.println(id);
   }

beforeInvocation属性

清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。

   @CacheEvict(value="user", beforeInvocation=true)
   public void delete(Integer id) {
      System.out.println(id);
   }

3. @Caching

@Caching注解可以让我们在一个方法或者类上同时指定多个Spring Cache相关的注解。其拥有三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict。

   @Caching(
        cacheable = @Cacheable("user"),
        evict = {
                @CacheEvict(value = "user1", key = "#id"),
                @CacheEvict(value = "user", allEntries = true)})
   public Integer find(Integer id) {
      return id;
   }

4. 自定义注解

Spring允许我们在配置可缓存的方法时使用自定义的注解,前提是自定义的注解上必须使用对应的注解进行标注。如我们有如下这么一个使用@Cacheable进行标注的自定义注解

二. Caffeine相关知识点

Caffeine常用配置说明:

  • initialCapacity=[integer]: 初始的缓存空间大小
  • maximumSize=[long]: 缓存的最大条数
  • maximumWeight=[long]: 缓存的最大权重
  • expireAfterAccess=[duration]: 最后一次写入或访问后经过固定时间过期
  • expireAfterWrite=[duration]: 最后一次写入后经过固定时间过期
  • refreshAfterWrite=[duration]: 创建缓存或者最近一次更新缓存后经过固定的时间间隔,刷新缓存

注意点:

  • expireAfterWrite和expireAfterAccess同事存在时,以expireAfterWrite为准
  • maximumSize和maximumWeight不可以同时使用

配置案例:

spring:
# 配置缓存,初始缓存容量为10,最大容量为200,过期时间(这里配置写入后过期时间为3秒)
  cache:
    type: caffeine
    caffeine:
      spec: initialCapacity=10,maximumSize=200,expireAfterWrite=3s

三. pringBoot集成Caffeine简单demo

1. pom文件

<?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 http://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.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.gj</groupId>
    <artifactId>boot-cache-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>boot-cache-demo</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.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.gjing</groupId>
            <artifactId>tools-common</artifactId>
            <version>1.0.4</version>
        </dependency>
        <dependency>
            <groupId>cn.gjing</groupId>
            <artifactId>tools-starter-swagger</artifactId>
            <version>1.0.9</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
            <version>2.7.0</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2. 配置文件

server:
  port: 8080
spring:
  application:
    name: springboot-cache-demo
# 配置数据库信息和连接池
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/cache?characterEncoding=utf8&useSSL=false&serverTimezone=UTC
    password: root
    username: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimum-idle: 1
      maximum-pool-size: 15
      idle-timeout: 30000
      connection-timeout: 20000
# 开启jpa自动建表
  jpa:
    database: mysql
    hibernate:
      ddl-auto: update
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
# 配置缓存,初始缓存容量,最大容量,过期时间(这里配置写入后过期时间)
  cache:
    type: caffeine
    caffeine:
      spec: initialCapacity=10,maximumSize=200,expireAfterWrite=3s
# 配置controller路径
swagger:
  base-package: com.gj.web
  title: springboot使用caffeine缓存

3. 启动类

@SpringBootApplication
@EnableSwagger
@EnableJpaAuditing
@EnableCaching
public class BootCacheDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(BootCacheDemoApplication.class, args);
    }
}

4. 定义一个实体

/**
 * @author Gjing
 **/
@Entity
@Table(name = "custom")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EntityListeners(AuditingEntityListener.class)
public class Custom {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(name = "custom_name", columnDefinition = "varchar(20) not null comment '用户名'")
    private String customName;

    @Column(name = "custom_number", columnDefinition = "int not null comment '用户编号'")
    private Integer customNumber;

    @Column(name = "create_time", columnDefinition = "datetime")
    @CreatedDate
    private Date createTime;

    @Column(name = "update_time", columnDefinition = "datetime")
    @LastModifiedDate
    private Date updateTime;
}

5. 定义持久层接口

/**
 * @author Gjing
 **/
@Repository
public interface CustomRepository extends JpaRepository<Custom,Integer> {
    /**
     * 通过用户名查询
     * @param customName 用户名
     * @return 用户
     */
    Custom findByCustomName(String customName);
}

6. 定义service

/**
 * @author Gjing
 **/
@Service
@Slf4j
public class CustomService {
    @Resource
    private CustomRepository customRepository;


    /**
     * 获取一个用户
     *
     * @param customId 用户id
     * @return custom
     */
    @Cacheable(value = "user", key = "#customId")
    public Custom getCustom(Integer customId) {
        log.warn("通过数据库去查询,用户id为:{}", customId);
        return customRepository.findById(customId)
                .orElseThrow(() -> new UserNotFoundException("Users don't exist"));
    }

    @CacheEvict(value = "user", key = "#customId")
    public void deleteCustom(Integer customId) {
        Custom custom = customRepository.findById(customId)
                .orElseThrow(() -> new UserNotFoundException("Users don't exist"));
        customRepository.delete(custom);
    }

    public Boolean insertCustom(String customName) {
        Custom custom = customRepository.findByCustomName(customName);
        if (custom == null) {
            customRepository.save(Custom.builder()
                    .customName(customName)
                    .customNumber(Integer.valueOf(RandomUtil.generateNumber(6)))
                    .build());
            return true;
        }
        return false;
    }
}

7. 定义异常

/**
 * @author Gjing
 **/
public class UserNotFoundException extends RuntimeException{
    public UserNotFoundException(String message) {
        super(message);
    }
}

/**
 * @author Gjing
 **/
@RestControllerAdvice
class DemoExceptionHandler {
    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity userNot(UserNotFoundException e) {
        return ResponseEntity.badRequest().body(ErrorResult.error(e.getMessage()));
    }
}

8. 定义接口

/**
 * @author Gjing
 **/
@RestController
public class CustomController {
    @Resource
    private CustomService customService;

    @PostMapping("/custom")
    @ApiOperation(value = "添加用户", httpMethod = "POST")
    @ApiImplicitParam(name = "customName", value = "用户名", required = true, dataType = "String", paramType = "Query")
    public ResponseEntity insertCustom(String customName) {
        Boolean insertCustom = customService.insertCustom(customName);
        if (insertCustom) {
            return ResponseEntity.ok("New successful");
        }
        return ResponseEntity.ok("Add failed, user already exists");
    }

    @GetMapping("/custom/{custom-id}")
    @ApiOperation(value = "查询指定用户", httpMethod = "GET")
    public ResponseEntity getCustom(@PathVariable("custom-id") Integer customId) {
        return ResponseEntity.ok(customService.getCustom(customId));
    }

    @DeleteMapping("/custom")
    @ApiOperation(value = "删除指定用户", httpMethod = "DELETE")
    @ApiImplicitParam(name = "customId", value = "用户id", required = true, dataType = "int", paramType = "Query")
    public ResponseEntity deleteCustom(Integer customId) {
        customService.deleteCustom(customId);
        return ResponseEntity.ok("Delete successful");
    }
}

启动后访问//localhost:8080/swagger-ui.html即可测试,第一次获取数据会从数据库中查询,接下来会直接读取缓存直到缓存失效

到此这篇关于Java缓存框架之Caffeine源码解析的文章就介绍到这了,更多相关Caffeine源码解析内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 聊聊如何在springboot中添加模版

    聊聊如何在springboot中添加模版

    本文,我们谈谈如何在 spring boot 中添加模版,因为有时候我们也是需要后端渲染的嘛,比如公司官网,文中有详细的代码示例供我们参考,需要的朋友可以参考下
    2023-08-08
  • Java 多线程优先级实例详解

    Java 多线程优先级实例详解

    这篇文章主要介绍了Java 多线程优先级实例详解的相关资料,需要的朋友可以参考下
    2017-04-04
  • java利用SMB读取远程文件的方法

    java利用SMB读取远程文件的方法

    这篇文章主要为大家详细介绍了java利用SMB读取远程文件的方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-05-05
  • Java线程协作的两种方式小结

    Java线程协作的两种方式小结

    Java中线程协作的最常见的两种方式是利用Object.wait()、Object.notify()和使用Condition,本文主要介绍了Java线程协作的两种方式小结,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2023-05-05
  • Java数据结构之简单的连接点(link)实现方法示例

    Java数据结构之简单的连接点(link)实现方法示例

    这篇文章主要介绍了Java数据结构之简单的连接点(link)实现方法,涉及java指针指向节点的相关使用技巧,需要的朋友可以参考下
    2017-10-10
  • Java后台开发之表单提交之前验证

    Java后台开发之表单提交之前验证

    这篇文章主要介绍了Java后台开发之表单提交之前验证的实现代码,非常不错具有参考借鉴价值,需要的朋友参考下吧
    2017-02-02
  • 通过Java代码来创建view的方法

    通过Java代码来创建view的方法

    本文给大家分享通过java代码创建view的方法,以TextView为例创建控件的方法,需要的的朋友参考下吧
    2017-08-08
  • MyBatis使用注解开发和无主配置文件开发的情况

    MyBatis使用注解开发和无主配置文件开发的情况

    这篇文章主要介绍了MyBatis使用注解开发和无主配置文件开发的情况,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-03-03
  • Java socket通信模拟QQ实现多人聊天室

    Java socket通信模拟QQ实现多人聊天室

    Socket在Java实战网络通信编程应用中有非常重要的作用,你想要跟别人联系都得通过socket占据端口来实现,掌握Socket技术不仅在聊天应用程序中需要用到(比如QQ什么的都都是用socket来写的),而且对于学习 Asp.net 也非常有帮助
    2022-07-07
  • Spring Boot教程之利用ActiveMQ实现延迟消息

    Spring Boot教程之利用ActiveMQ实现延迟消息

    这篇文章主要给大家介绍了关于Spring Boot教程之利用ActiveMQ实现延迟消息的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Spring Boot具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-11-11

最新评论