草庐IT

SpringBoot之缓存篇

醉卧沙场丶 2024-01-22 原文

SpringBoot与缓存

​ 随着时间的积累,应用的使用用户不断增加,数据规模也越来越大,往往数据库查询操作会成为影响用户使用体验的瓶颈,此时使用缓存往往是解决这一问题非常好的手段之一。Spring 3开始提供了强大的基于注解的缓存支持,可以通过注解配置方式低侵入的给原有Spring应用增加缓存功能,提高数据访问性能。在Spring Boot中对于缓存的支持,提供了一系列的自动化配置,使我们可以非常方便的使用缓存。

首先了解下JSR107、Spring缓存抽象等等概念。

一 、Cache缓存的作用

1. JSR107

Java Caching定义了5个核心接口,分别是CachingProvider, CacheManager, Cache, Entry 和 Expiry。

  1. CachingProvider定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期访问多个CachingProvider。

  2. CacheManager定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。

  3. Cache是一个类似Map的数据结构并临时存储以Key为索引的值。一个Cache仅被一个CacheManager所拥有。

  4. Entry是一个存储在Cache中的key-value对.

  5. Expiry 每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。

2.Spring缓存抽象

Spring从3.1开始定义了org.springframework.cache.Cache 和org.springframework.cache.CacheManager接口来统一不同的缓存技术; 并支持使用JCache(JSR-107)注解简化我们开发。

  • Cache接口为缓存的组件规范定义,包含缓存的各种操作集合。

  • Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache , ConcurrentMapCache。

  • 每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否 已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法 并缓存结果后返回给用户。下次调用直接从缓存中获取。

  • 使用Spring缓存抽象时我们需要关注以下两点:

    1、确定方法需要被缓存以及他们的缓存策略

​ 2、从缓存中读取之前缓存存储的数据

二、几个重要概念&缓存注解

概念/注解作用
Cache缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等
CacheManager缓存管理器,管理各种缓存(Cache)组件
@Cacheable可以针对方法和类进行配置,主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
缓存存在,则使用缓存;不存在,则执行方法,并将结果塞入缓存
@CacheEvict清空缓存
@CachePut保证方法被调用,又希望结果被缓存。与@Cacheable区别在于是否每次都调用方法,常用于更新
@Caching@Cacheable、@CachePut、@CacheEvict的组合,定义复杂的缓存规则,在这个组合中只要有@CachePut就一定会调用被注解的方法
@CacheConfig标注在类上,抽取缓存相关注解的公共配置,可抽取的公共配置有缓存名字、主键生成器等
@EnableCaching开启基于注解的缓存
keyGenerator缓存数据时key生成策略
serialize缓存数据时value序列化策略

说明:
   ①@Cacheable标注在方法上,表示该方法的结果需要被缓存起来,缓存的键由keyGenerator的策略决定,缓存的值的形式则由serialize序列化策略决定(序列化还是json格式);标注上该注解之后,在缓存时效内再次调用该方法时将不会调用方法本身而是直接从缓存获取结果
   ②@CachePut也标注在方法上,和@Cacheable相似也会将方法的返回值缓存起来,不同的是标注@CachePut的方法每次都会被调用,而且每次都会将结果缓存起来,适用于对象的更新

@Cacheable

主要参数

属性名描述示例
cacheNames/valuecacheNames和value互为别名。其作用是指定缓存的名字,缓存使用CacheManager管理多个缓存组件Cache,这些Cache组件就是根据这个名字进行区分的。对缓存的真正CRUD操作在Cache中定义,每个缓存组件Cache都有自己唯一的名字,通过cacheNames或者value属性指定,相当于是将缓存的键值对进行分组,缓存的名字是一个数组,也就是说可以将一个缓存键值对分到多个组里面。@Cacheable(value=“testCache”) 或者 @Cacheable(cacheNames={“cache1”,“cache2”})
key缓存数据时的key的值,默认是使用方法参数的值,可以使用SpEL表达式计算key的值@Cacheable(value=“testCache”,key=“#userName”)
keyGenerator缓存的生成策略,和key二选一,都是生成键的,keyGenerator可自定义
cacheManager指定缓存管理器(如ConcurrentHashMap、Redis等)
cacheResolver和cacheManager功能一样,和cacheManager二选一
condition指定缓存的条件(满足什么条件时才缓存),可用SpEL表达式(如#id>0,表示当入参id大于0时才缓存)@Cacheable(value=“testCache”,condition=“#userName.length()>2”
unless否定缓存,即满足unless指定的条件时,方法的结果不进行缓存,使用unless时可以在调用的方法获取到结果之后再进行判断(如#result==null,表示如果结果为null时不缓存)@Cacheable(value=“testCache”,unless=“#result == null”)
sync是否使用异步模式进行缓存;使用异步模式进行缓存时(sync=true):unless条件将不被支持

注:
  ①既满足condition又满足unless条件的也不进行缓存
  ②使用异步模式进行缓存时(sync=true):unless条件将不被支持

可用的SpEL表达式见下表:

名字位置描述示例
methodNameroot object当前被调用的方法名#root.methodName
methodroot object当前被调用的方法#root.method.name
targetroot object当前被调用的目标对象#root.target
targetClassroot object当前被调用的目标对象类root.targetClass
argsroot object当前被调用的方法的参数列表#root.args[0]
cachesroot object当前方法调用使用的缓存列表(如@Cacheable(value={“cache1”, “cache2”})),则有两个cache#root.caches[0].name
Argument Nameevaluation context当前被调用的方法的参数,可以直接 #参数名,如findArtisan(Artisan artisan),可以通过#artsian.id获得参数;也可以使用#p0或#a0的形式,0代表参数的索引#iban、#a0、#p0
resultevaluation context方法执行后的返回值(仅当方法执行之后的判断有效,如"unless","cache put"的表达式,"cache evict"的表达式beforeInvocation=false)#result

详解

​ 这个注解用于修饰方法或者类,当我们访问它修饰的方法时,优先从缓存中获取,若缓存中存在,则直接获取缓存的值;缓存不存在时,则执行方法,并将结果写入缓存。

这个注解,有两个比较核心的设置

 /**
  * 与 cacheNames 效果等价
  */
 @AliasFor("cacheNames")
 String[] value() default {};

 /**
  * 与 value 效果等价
  */
 @AliasFor("value")
 String[] cacheNames() default {};

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

cacheNames 可以理解为缓存 key 的前缀,可以为组件缓存的 key 变量;当 key 不设置时,使用方法参数来初始化,注意 key 为 SpEL 表达式,因此如果要写字符串时,用单引号括起来。

/**
 * 首先从缓存中查,查到之后,直接返回缓存数据;否则执行方法,并将结果缓存
 *
 * redisKey: cacheNames + key 组合而成 --> 支持SpEL
 * redisValue: 返回结果
 */
@Cacheable(cacheNames = "say", key = "'p_'+ #name")
public String sayHello(String name) {
    return "hello+" + name + "-->" + UUID.randomUUID().toString();
}

如我们传参为 somnus, 那么缓存 key 为 say::somnus

condition参数,这个表示当它设置的条件达成时,才写入缓存。下面这个 case 中,age 为偶数的时候,才走缓存;否则不走缓存。

/**
 * 满足condition条件的才写入缓存
 */
@Cacheable(cacheNames = "condition", key = "#age", condition = "#age % 2 == 0")
public String setByCondition(int age) {
    return "condition:" + age + "-->" + UUID.randomUUID().toString();
}

接下来是unless参数,这个表示它设置的条件不满足时才写入缓存。下面这个 case 中,age 为偶数的时候,不走缓存;否则走缓存。

/**
 * unless, 不满足条件才写入缓存
 */
@Cacheable(cacheNames = "unless", key = "#age", unless = "#age % 2 == 0")
public String setUnless(int age) {
    return "unless:" + age + "-->" + UUID.randomUUID().toString();
}

service层代码

第一次查询数据库打印service类方法日志,并把数据保存到Cahce中

第二次传入相同参数不再执行service类方法,不会打印日志,查询的数据直接从缓存中获取

@Service
public class PersonService {
  
    @Autowired
    PersonDao personDao;
 
     //@Cacheable(cacheNames= "person")
     //@Cacheable(cacheNames= "person",key="#id",condition="#id>3")
     @Cacheable(cacheNames= "person",key="#id")
     public Person queryPersonById(Integer id){
        System.out.println("查询"+id+"号员工信息");
        Person person=new Person();
        person.setId(id);
        return personDao.query(person);
    }
}

@CachePut

不管缓存有没有,都将方法的返回结果写入缓存;适用于缓存更新

通俗讲就是:既调用方法,又更新缓存数据 ,即数据库中的数据和缓存都更新!

主要参数

属性名描述示例
cacheNames/valuecacheNames和value互为别名。其作用是指定缓存的名字,缓存使用CacheManager管理多个缓存组件Cache,这些Cache组件就是根据这个名字进行区分的。对缓存的真正CRUD操作在Cache中定义,每个缓存组件Cache都有自己唯一的名字,通过cacheNames或者value属性指定,相当于是将缓存的键值对进行分组,缓存的名字是一个数组,也就是说可以将一个缓存键值对分到多个组里面。@CachePut(value=“testCache”) 或者 @CachePut(cacheNames={“cache1”,“cache2”})
key缓存数据时的key的值,默认是使用方法参数的值,可以使用SpEL表达式计算key的值@CachePut(value=“testCache”,key=“#userName”)
keyGenerator缓存的生成策略,和key二选一,都是生成键的,keyGenerator可自定义
cacheManager指定缓存管理器(如ConcurrentHashMap、Redis等)
cacheResolver和cacheManager功能一样,和cacheManager二选一
condition指定缓存的条件(满足什么条件时才缓存),可用SpEL表达式(如#id>0,表示当入参id大于0时才缓存)@CachEvict(value=“testCache”,condition=“#userName.length()>2”
unless否定缓存,即满足unless指定的条件时,方法的结果不进行缓存,使用unless时可以在调用的方法获取到结果之后再进行判断(如#result==null,表示如果结果为null时不缓存)@Cacheable(value=“testCache”,unless=“#result == null”)
sync是否使用异步模式进行缓存;使用异步模式进行缓存时(sync=true):unless条件将不被支持

详解

@Service
public class PersonService {
    @Autowired
    PersonDao personDao;
   /**
     *运行时机:
     * 1.先调用目标方法
     * 2.将目标方法返回的结果缓存起来
     *
     * 测试步骤:
     * 1.查询1号的个人信息
     * 2.以后查询还是之前的结果
     * 3.更新1号的个人信息
     * 4.查询一号员工返回的结果是什么?
     *     应该是更新后的员工
     *     但只更新了数据库,但没有更新缓存是什么原因?
     * 5.如何解决缓存和数据库同步更新?
     * 这样写:@CachePut(cacheNames = "person",key = "#person.id")
     *       @CachePut(cacheNames = "person",key = "#result.id")
     */
    @CachePut(cacheNames = "person",key = "#result.id")
    public Person updatePerson(Person person){
        System.out.println("修改"+person.getId()+"号员工信息");
        personDao.update(person);
        return person;
    }
}

@CacheEvict

这个就是我们理解的删除缓存,可以清除缓存中的指定数据或清除缓存中所有数据。

主要参数

属性名描述示例
cacheNames/valuecacheNames和value互为别名。其作用是指定缓存的名字,缓存使用CacheManager管理多个缓存组件Cache,这些Cache组件就是根据这个名字进行区分的。对缓存的真正CRUD操作在Cache中定义,每个缓存组件Cache都有自己唯一的名字,通过cacheNames或者value属性指定,相当于是将缓存的键值对进行分组,缓存的名字是一个数组,也就是说可以将一个缓存键值对分到多个组里面。@CachEvict(value=“testCache”) 或者 @CachEvict(cacheNames={“cache1”,“cache2”})
key缓存数据时的key的值,默认是使用方法参数的值,可以使用SpEL表达式计算key的值@CachEvict(value=“testCache”,key=“#userName”)
keyGenerator缓存的生成策略,和key二选一,都是生成键的,keyGenerator可自定义
cacheManager指定缓存管理器(如ConcurrentHashMap、Redis等)
cacheResolver和cacheManager功能一样,和cacheManager二选一
condition指定缓存的条件(满足什么条件时才缓存),可用SpEL表达式(如#id>0,表示当入参id大于0时才缓存)@CachEvict(value=“testCache”,condition=“#userName.length()>2”)
allEntries是否清空所有缓存内容,缺省为 false;如果指定为 true,则方法调用后将立即清空所有缓存@CachEvict(value=“testCache”,allEntries=true)
beforeInvocation是否在方法执行前就清空,缺省为 false;如果指定 为 true,则在方法还没有执行的时候就清空缓存, 缺省情况下,如果方法执行抛出异常,则不会清空缓存@CachEvict(value=“testCache”,beforeInvocation=true)

详解

清除缓存中的单个数据

/**
 * 失效缓存
 */
@CacheEvict(cacheNames = "say", key = "'p_'+ #name")
public String evict(String name) {
    return "evict+" + name + "-->" + UUID.randomUUID().toString();
}

清除缓存中的所有数据

@Service
public class PersonService {
    @Autowired
    PersonDao personDao;
 
    /**
     * @CacheEvict:清除缓存
     *    1.key:指定要清除缓存中的某条数据
     *    2.allEntries=true:删除缓存中的所有数据
     *    3.beforeInvocation=false:默认是在方法之后执行清除缓存
     *      beforeInvocation=true:现在是在方法执行之前执行清除缓存
     */
    //@CacheEvict(cacheNames = "person",key = "#id")
    @CacheEvict(cacheNames = "person",allEntries=true)
    public void deletePerson(Integer id){
        System.out.println("删除"+id+"号个人信息");
        //删除数据库数据的同时删除缓存数据
        //personDao.delete(id);
 
        /**
         * beforeInvocation=true
         * 使用在方法之前执行的好处:
         * 1.如果方法出现异常,缓存依旧会被删除
         */
        //int a=1/0;
    }
}

也可以使用方法来生成key,格式为T(类的全类名).方法名(参数列表)

@Service
public class PersonService {

    @Autowired
    private PersonDao personDao;

    //@CacheEvict(cacheNames = "person",key = "#id")
    @CacheEvict(key = "T(org.zpli.service.PersonCacheUtils).generateIdKey(#result.id)")
    public Person deletePerson(Person person) {
        System.out.println("删除" + person.getId() + "号个人信息");
        //删除数据库数据的同时删除缓存数据
        Person deletePerson = personDao.delete(person);
        return deletePerson;
    }
}
package org.zpli.service;


/**
 * created at 2023/3/9 16:52
 *
 * @author somnuszpli
 */
public class PersonCacheUtils {

    public static String generateIdKey(String id) {
        return "p_" + id;
    }

}

@Caching

在实际的工作中,经常会遇到一个数据变动,更新多个缓存的场景,对于这个场景,可以通过@Caching来实现

/**
 * caching实现组合,添加缓存,并失效其他的缓存
 */
@Caching(cacheable = @Cacheable(cacheNames = "caching", key = "#age"), evict = @CacheEvict(cacheNames = "t4", key = "#age"))
public String caching(int age) {
    return "caching: " + age + "-->" + UUID.randomUUID().toString();
}

上面这个就是组合操作

  • caching::age缓存取数据,不存在时执行方法并写入缓存;
  • 失效缓存 t4::age
@Service
public class PersonService {
    @Autowired
    PersonDao personDao;
   /**
     *   @Caching是 @Cacheable、@CachePut、@CacheEvict注解的组合
     *   以下注解的含义:
     *   1.当使用指定名字查询数据库后,数据保存到缓存
     *   2.现在使用id、age就会直接查询缓存,而不是查询数据库
     */
    @Caching(
            cacheable = {@Cacheable(value = "person",key="#name")},
            put={ @CachePut(value = "person",key = "#result.id"),
                  @CachePut(value = "person",key = "#result.age")
                }
    )
    public Person queryPersonByName(String name){
        System.out.println("查询的姓名:"+name);
        return personDao.queryByName(name);
    }
}

三、使用缓存

第一步: 导入spring-boot-starter-cache模块

第二步: @EnableCaching开启缓存

@SpringBootApplication
@EnableCaching
public class SpringbootCacheApplication {

   public static void main(String[] args) {
      SpringApplication.run(SpringbootCacheApplication.class, args);
   }
}

第三步: 使用缓存注解

四、缓存工作原理

1、自动配置类:CacheAutoConfiguration,通过CacheAutoConfiguration导入的CacheConfigurationImportSelector会向数组中添加一些缓存的配置类全类名
2、缓存的配置类

​ org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
  org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
  org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
  org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
  org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
  org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
  org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
  org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
  org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration
  org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration(默认使用)
  org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
3、默认生效的配置类:SimpleCacheConfiguration
4、SimpleCacheConfiguration给容器中注册了一个CacheManager:ConcurrentMapCacheManager

@Configuration
@ConditionalOnMissingBean({CacheManager.class})
@Conditional({CacheCondition.class})
class SimpleCacheConfiguration {
    private final CacheProperties cacheProperties;
    private final CacheManagerCustomizers customizerInvoker;
    
	SimpleCacheConfiguration(CacheProperties cacheProperties, CacheManagerCustomizers customizerInvoker) {
    this.cacheProperties = cacheProperties;
    this.customizerInvoker = customizerInvoker;
	}

	@Bean
	public ConcurrentMapCacheManager cacheManager() {
    ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
    List<String> cacheNames = this.cacheProperties.getCacheNames();
    if (!cacheNames.isEmpty()) {
        cacheManager.setCacheNames(cacheNames);
    	}

    return (ConcurrentMapCacheManager)this.customizerInvoker.customize(cacheManager);
	}
}

5、通过ConcurrentMapCacheManager可以获取和创建ConcurrentMapCache类型的缓存组件:ConcurrentMapCache的作用是数据保存在ConcurrentMap中
6、@Cacheable运行流程:
  ①方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取(CacheManager先获取相应的缓存,第一次获取缓存如果没有Cache组件会自动创建)
  ②去Cache中查找缓存的内容,使用的key默认就是方法的参数:
   key默认是使用keyGenerator生成的,默认使用的是SimpleKeyGenerator
   SimpleKeyGenerator生成key的默认策略:
    如果没有参数:key = new SimpleKey();
    如果有一个参数:key = 参数的值
    如果有多个参数:key = new SimpleKey(params);
  ③没有查到缓存就调用目标方法
  ④将目标方法返回的结果放进缓存中
 总结:@Cacheable标注的方法在执行之前会先检查缓存中有没有这个数据,默认按照参数的值为key查询缓存,如果没有就运行方法并将结果放入缓存,以后再来调用时直接使用缓存中的数据。
 核心:
  1️⃣使用CacheManager(ConcurrentMapCacheManager)按照名字得到Cache(ConcurrentMapCache)组件
  2️⃣key使用keyGenerator生成,默认使用SimpleKeyGenerator

有关SpringBoot之缓存篇的更多相关文章

  1. ruby - 如何在 Ubuntu 中清除 Ruby Phusion Passenger 的缓存? - 2

    我试过重新启动apache,缓存的页面仍然出现,所以一定有一个文件夹在某个地方。我没有“公共(public)/缓存”,那么我还应该查看哪些其他地方?是否有一个URL标志也可以触发此效果? 最佳答案 您需要触摸一个文件才能清除phusion,例如:touch/webapps/mycook/tmp/restart.txt参见docs 关于ruby-如何在Ubuntu中清除RubyPhusionPassenger的缓存?,我们在StackOverflow上找到一个类似的问题:

  2. ruby-on-rails - Ruby on Rails 计数器缓存错误 - 2

    尝试在我的RoR应用程序中实现计数器缓存列时出现错误Unknownkey(s):counter_cache。我在这个问题中实现了模型关联:Modelassociationquestion这是我的迁移:classAddVideoVotesCountToVideos0Video.reset_column_informationVideo.find(:all).eachdo|p|p.update_attributes:videos_votes_count,p.video_votes.lengthendenddefself.downremove_column:videos,:video_vot

  3. ruby-on-rails - bundle 安装尝试使用缓存文件 - 2

    当我尝试进行bundle安装时,我的gem_path和gem_home指向/usr/local/rvm/gems/我没有写入权限,并且由于权限无效而失败。因此,我已将两个路径都更改为我具有写入权限的本地目录。这样做时,我进行了bundle安装,我得到:bruno@test6:~$bundleinstallFetchinggemmetadatafromhttps://rubygems.org/.........Fetchinggemmetadatafromhttps://rubygems.org/..Bundler::GemspecError:Couldnotreadgemat/afs/

  4. ruby-on-rails - Heroku Action 缓存似乎不起作用 - 2

    我一直在Heroku上尝试不同的缓存策略,并添加了他们的memcached附加组件,目的是为我的应用程序添加Action缓存。但是,当我在我当前的应用程序上查看Rails.cache.stats时(安装了memcached并使用dalligem),在执行应该缓存的操作后,我得到current和total_items为0。在Controller的顶部,我想缓存我有的Action:caches_action:show此外,我修改了我的环境配置(对于在Heroku上运行的配置)config.cache_store=:dalli_store我是否可以查看其他一些统计数据,看看它是否有效或我做错

  5. ruby-on-rails - rails expire_page 没有删除缓存的文件 - 2

    我有一个具有页面缓存的ControllerAction,我制作了一个清扫程序,它使用Controller和指定的Action调用expire_page...Controller操作呈现一个js.erb模板,所以我试图确保expire_page删除public/javascripts中的.js文件,但它没有这样做。classJavascriptsController"javascripts",:action=>"lol",:format=>'js')endend...所以,我访问javascripts/lol.js并呈现我的模板。我验证了public/javascripts/lol.js

  6. ruby-on-rails - rails 3 缓存 : expire action for named route - 2

    我的Controller有这个:caches_action:render_ticker_for_channel,:expires_in=>30.seconds在我的路由文件中我有这个:match'/render_c_t/:channel_id'=>'render#render_ticker_for_channel',:as=>:render_channel_ticker在日志文件中我看到了这个:Writefragmentviews/mcr3.dev/render_c_t/63(11.6ms)我如何手动使它过期?我需要从与渲染Controller不同的Controller使它过期,但即使

  7. ruby - 更新 gem 时 Docker 包安装缓存问题 - 2

    我在开发和生产中都使用docker,真正困扰我的一件事是docker缓存的简单性。我的ruby​​应用程序需要bundleinstall来安装依赖项,因此我从以下Dockerfile开始:添加GemfileGemfile添加Gemfile.lockGemfile.lock运行bundleinstall--path/root/bundle所有依赖项都被缓存,并且在我添加新gem之前效果很好。即使我添加的gem只有0.5MB,从头开始安装所有应用程序gem仍然需要10-15分钟。由于依赖项文件夹的大小(大约300MB),然后再花10分钟来部署它。我在node_modules和npm上遇到了

  8. ruby-on-rails - 在缓存中找不到 StaleElementReference 错误元素 - 2

    我正在使用Capybara2.1和Ruby1.9.3,使用selenium驱动程序(带有Minitest和测试单元)来测试网络应用程序。我正在努力解决StaleElementReferenceException问题。我已经看到很多关于该主题的讨论,但我无法找到解决我所面临问题的方法。所以基本上,我试图使用以下代码在我的页面上找到所有分页元素:pagination_elements=page.all('.paginationa')然后我对这些元素做一些断言,例如:pagination_elements.first.must_have_content('1')在这些断言之后,我通过单击下一

  9. springboot定时任务 - 2

    如果您希望在Spring中启用定时任务功能,则需要在主类上添加 @EnableScheduling 注解。这样Spring才会扫描 @Scheduled 注解并执行定时任务。在大多数情况下,只需要在主类上添加 @EnableScheduling 注解即可,不需要在Service层或其他类中再次添加。以下是一个示例,演示如何在SpringBoot中启用定时任务功能:@SpringBootApplication@EnableSchedulingpublicclassApplication{publicstaticvoidmain(String[]args){SpringApplication.ru

  10. 基于SpringBoot的线上日志阅读器 - 2

    软件特点部署后能通过浏览器查看线上日志。支持Linux、Windows服务器。采用随机读取的方式,支持大文件的读取。支持实时打印新增的日志(类终端)。支持日志搜索。使用手册基本页面配置路径配置日志所在的目录,配置后按回车键生效,下拉框选择日志名称。选择日志后点击生效,即可加载日志。windows路径E:\java\project\log-view\logslinux路径/usr/local/XX历史模式历史模式下,不会读取新增的日志。针对历史文件可以分页读取,配置分页大小、跳转。历史模式下,支持根据关键词搜索。目前搜索引擎使用的是jdk自带类库,搜索速度相对较低,优点是比较简单。2G日志全文搜

随机推荐