欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot?Cache?二級緩存的使用

 更新時間:2023年06月19日 08:57:13   作者:CadeCode  
本文主要介紹了SpringBoot?Cache?二級緩存的使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

二級緩存介紹

二級緩存分為本地緩存和遠(yuǎn)程緩存,也可稱為內(nèi)存緩存和網(wǎng)絡(luò)緩存

常見的流行緩存框架

  • 本地緩存:Caffeine,Guava Cache
  • 遠(yuǎn)程緩存:Redis,MemCache

二級緩存的訪問流程

image-20230618110334830

二級緩存的優(yōu)勢與問題

  • 優(yōu)勢:二級緩存優(yōu)先使用本地緩存,訪問數(shù)據(jù)非常快,有效減少和遠(yuǎn)程緩存之間的數(shù)據(jù)交換,節(jié)約網(wǎng)絡(luò)開銷
  • 問題:分布式環(huán)境下本地緩存存在一致性問題,本地緩存變更后需要通知其他節(jié)點(diǎn)刷新本地緩存,這對一致性要求高的場景可能不能很好的適應(yīng)

SpringBoot Cache 組件

SpringBoot Cache 組件提供了一套緩存管理的接口以及聲明式使用的緩存的注解

引入 SpringBoot Cache

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

如何集成第三方緩存框架到 Cache 組件

實現(xiàn) Cache 接口,適配第三方緩存框架的操作,實現(xiàn) CacheManager 接口,提供緩存管理器的 Bean

SpringBoot Cache 默認(rèn)提供了 Caffeine、ehcache 等常見緩存框架的管理器,引入相關(guān)依賴后即可使用

引入 Caffeine

<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
</dependency>

SpringBoot Redis 提供了 Redis 緩存的實現(xiàn)及管理器

引入 Redis 緩存、RedisTemplate

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

SpringBoot Cache 聲明式緩存注解

@Cacheable:執(zhí)行方法前,先從緩存中獲取,沒有獲取到才執(zhí)行方法,并將其結(jié)果更新到緩存

@CachePut:執(zhí)行方法后,將其結(jié)果更新到緩存

@CacheEvict:執(zhí)行方法后,清除緩存

@Caching:組合前三個注解

@Cacheable 注解的常用屬性:

  • cacheNames/value:緩存名稱
  • key:緩存數(shù)據(jù)的 key,默認(rèn)使用方法參數(shù)值,支持 SpEL
  • keyGenerator:指定 key 的生成器,和 key 屬性二選一
  • cacheManager:指定使用的緩存管理器。
  • condition:在方法執(zhí)行開始前檢查,在符合 condition 時,進(jìn)行緩存操作
  • unless:在方法執(zhí)行完成后檢查,在符合 unless 時,不進(jìn)行緩存操作
  • sync:是否使用同步模式,同步模式下,多個線程同時未命中一個 key 的數(shù)據(jù),將阻塞競爭執(zhí)行方法

SpEL 支持的表達(dá)式

image-20230618142309771

本地緩存 Caffeine

Caffeine 介紹

Caffeine 是繼 Guava Cache 之后,在 SpringBoot 2.x 中默認(rèn)集成的緩存框架

Caffeine 使用了 Window TinyLFU 淘汰策略,緩存命中率極佳,被稱為現(xiàn)代高性能緩存庫之王

創(chuàng)建一個 Caffeine Cache

Cache<String, Object> cache = Caffeine.newBuilder().build();

Caffeine 內(nèi)存淘汰策略

  • FIFO:先進(jìn)先出,命中率低
  • LRU:最近最久未使用,不能應(yīng)對冷門突發(fā)流量,會導(dǎo)致熱點(diǎn)數(shù)據(jù)被淘汰
  • LFU:最近最少使用,需要維護(hù)使用頻率,占用內(nèi)存空間,
  • W-TinyLFU:LFU 的變種,綜合了 LRU LFU 的長處,高命中率,低內(nèi)存占用

Caffeine 緩存失效策略

1.基于容量大小

根據(jù)最大容量

Cache<String, Object> cache = Caffeine.newBuilder()
               	.maximumSize(10000)
                .build();

根據(jù)權(quán)重

Cache<String, Object> cache = Caffeine.newBuilder()
                .maximumWeight(10000)
                .weigher((Weigher<String, Object>) (s, o) -> {
                    // 根據(jù)不同對象計算權(quán)重
                    return 0;
                })
                .build();

2.基于引用類型

基于弱引用,當(dāng)不存在強(qiáng)引用時淘汰

Cache<String, Object> cache = Caffeine.newBuilder()
                .weakKeys()
                .weakValues()
                .build();

基于軟引用,當(dāng)不存在強(qiáng)引用且內(nèi)存不足時淘汰

Cache<String, Object> cache = Caffeine.newBuilder()
                .softValues()
                .build();

3.基于過期時間

expireAfterWrite,寫入后一定時間后過期

Cache<String, Object> cache = Caffeine.newBuilder()
                .expireAfterWrite(5, TimeUnit.SECONDS)
                .build();

expireAfterAccess(long, TimeUnit),訪問后一定時間后過期,一直訪問則一直不過期

expireAfter(Expiry),自定義時間的計算方式

Caffeine 線程池

  • Caffeine 默認(rèn)使用 ForkJoinPool.commonPool()
  • Caffeine 線程池可通過 executor 方法設(shè)置

Caffeine 指標(biāo)統(tǒng)計

  • Caffeine 通過配置 recordStats 方法開啟指標(biāo)統(tǒng)計,通過緩存的 stats 方法獲取信息
  • Caffeine 指標(biāo)統(tǒng)計的內(nèi)容有:命中率,加載數(shù)據(jù)耗時,緩存數(shù)量相關(guān)等Caffeine Cache 的種類

1.普通 Cache

Cache<String, Object> cache = Caffeine.newBuilder()
                .expireAfterWrite(5, TimeUnit.SECONDS)
                .build();
// 存入
cache.put("key1", "123");
// 取出
Object key1Obj = cache.getIfPresent("key1");
// 清除
cache.invalidate("key1");
// 清除全部
cache.invalidateAll();

2.異步 Cache

響應(yīng)結(jié)果通過 CompletableFuture 包裝,利用線程池異步執(zhí)行

AsyncCache<String, Object> asyncCache = Caffeine.newBuilder()
        .expireAfterWrite(5, TimeUnit.SECONDS)
        .buildAsync();
// 存入
asyncCache.put("key1", CompletableFuture.supplyAsync(() -> "123"));
// 取出
CompletableFuture<Object> key1Future = asyncCache.getIfPresent("key1");
try {
    Object key1Obj = key1Future.get();
} catch (InterruptedException | ExecutionException e) {
    //
}
// 清除
asyncCache.synchronous().invalidate("key1");
// 清除全部
asyncCache.synchronous().invalidateAll();

3.Loading Cache

和普通緩存使用方式一致

在緩存未命中時,自動加載數(shù)據(jù)到緩存,需要設(shè)置加載數(shù)據(jù)的回調(diào),比如從數(shù)據(jù)庫查詢數(shù)據(jù)

LoadingCache<String, Object> cache = Caffeine.newBuilder()
        .expireAfterWrite(5, TimeUnit.SECONDS)
        .build(key -> {
            // 獲取業(yè)務(wù)數(shù)據(jù)
            return "Data From DB";
        });

4.異步 Loading Cache

和異步緩存使用方式一致

在緩存未命中時,自動加載數(shù)據(jù)到緩存,與 Loading Cache 不同的是,加載數(shù)據(jù)是異步的

// 使用 AsyncCache 的線程池異步加載
AsyncLoadingCache<String, Object> asyncCache0 = Caffeine.newBuilder()
        .expireAfterWrite(5, TimeUnit.SECONDS)
        .buildAsync(key -> {
            // 獲取業(yè)務(wù)數(shù)據(jù)
            return "Data From DB";
        });
// 指定加載使用的線程池
AsyncLoadingCache<String, Object> asyncCache1 = Caffeine.newBuilder()
        .expireAfterWrite(5, TimeUnit.SECONDS)
        .buildAsync((key, executor) -> CompletableFuture.supplyAsync(() -> {
            // 異步獲取業(yè)務(wù)數(shù)據(jù)
            return "Data From DB";
        }, otherExecutor));

注意:AsyncLoadingCache 不支持弱引用和軟引用相關(guān)淘汰策略

Caffeine 自動刷新機(jī)制

Caffeine 可通過 refreshAfterWrite 設(shè)置定時刷新

必須是指定了 CacheLoader 的緩存,即 LoadingCache 和 AsyncLoadingCache

LoadingCache<String, Object> cache = Caffeine.newBuilder()
                .expireAfterWrite(5, TimeUnit.SECONDS)
                .refreshAfterWrite(3, TimeUnit.SECONDS)
                .build(key -> {
                    // 獲取業(yè)務(wù)數(shù)據(jù)
                    return "Data From DB";
                });

refreshAfterWrite 是一種定時刷新,key 過期時并不一定會立即刷新

實現(xiàn)二級緩存

配置類 DLCacheProperties

@Data
@ConfigurationProperties(prefix = "uni-boot.cache.dl")
public class DLCacheProperties {
    /**
     * 是否存儲 null 值
     */
    private boolean allowNullValues = true;
    /**
     * 過期時間,為 0 表示不過期,默認(rèn) 30 分鐘
     * 單位:毫秒
     */
    private long defaultExpiration = 30 * 60 * 1000;
    /**
     * 針對 cacheName 設(shè)置過期時間,為 0 表示不過期
     * 單位:毫秒
     */
    private Map<String, Long> cacheExpirationMap;
    /**
     * 本地緩存 caffeine 配置
     */
    private LocalConfig local = new LocalConfig();
    /**
     * 遠(yuǎn)程緩存 redis 配置
     */
    private RemoteConfig remote = new RemoteConfig();
    @Data
    public static class LocalConfig {
        /**
         * 初始化大小,為 0 表示默認(rèn)
         */
        private int initialCapacity;
        /**
         * 最大緩存?zhèn)€數(shù),為 0 表示默認(rèn)
         * 默認(rèn)最多 5 萬條
         */
        private long maximumSize = 10000L;
    }
    @Data
    public static class RemoteConfig {
        /**
         * Redis pub/sub 緩存刷新通知主題
         */
        private String syncTopic = "cache:dl:refresh:topic";
    }
}

緩存實現(xiàn) DLCache

本地緩存基于 Caffeine,遠(yuǎn)程緩存使用 Redis

實現(xiàn) SpringBoot Cache 的抽象類,AbstractValueAdaptingCache

@Slf4j
@Getter
public class DLCache extends AbstractValueAdaptingCache {
    private final String name;
    private final long expiration;
    private final DLCacheProperties cacheProperties;
    private final Cache<String, Object> caffeineCache;
    private final RedisTemplate<String, Object> redisTemplate;
    public DLCache(String name, long expiration, DLCacheProperties cacheProperties,
                   Cache<String, Object> caffeineCache, RedisTemplate<String, Object> redisTemplate) {
        super(cacheProperties.isAllowNullValues());
        this.name = name;
        this.expiration = expiration;
        this.cacheProperties = cacheProperties;
        this.caffeineCache = caffeineCache;
        this.redisTemplate = redisTemplate;
    }
    @Override
    public String getName() {
        return name;
    }
    @Override
    public Object getNativeCache() {
        return this;
    }
    @Override
    protected Object lookup(Object key) {
        String redisKey = getRedisKey(key);
        Object val;
        val = caffeineCache.getIfPresent(key);
        // val 是 toStoreValue 包裝過的值,為 null 則 key 不存在
        // 因為存儲的 null 值被包裝成了 DLCacheNullVal.INSTANCE
        if (ObjectUtil.isNotNull(val)) {
            log.debug("DLCache local get cache, key:{}, value:{}", key, val);
            return val;
        }
        val = redisTemplate.opsForValue().get(redisKey);
        if (ObjectUtil.isNotNull(val)) {
            log.debug("DLCache remote get cache, key:{}, value:{}", key, val);
            caffeineCache.put(key.toString(), val);
            return val;
        }
        return val;
    }
    @SuppressWarnings("unchecked")
    @Override
    public <T> T get(Object key, Callable<T> valueLoader) {
        T val;
        val = (T) lookup(key);
        if (ObjectUtil.isNotNull(val)) {
            return val;
        }
        // 雙檢鎖
        synchronized (key.toString().intern()) {
            val = (T) lookup(key);
            if (ObjectUtil.isNotNull(val)) {
                return val;
            }
            try {
                // 攔截的業(yè)務(wù)方法
                val = valueLoader.call();
                // 加入緩存
                put(key, val);
            } catch (Exception e) {
                throw new DLCacheException("DLCache valueLoader fail", e);
            }
            return val;
        }
    }
    @Override
    public void put(Object key, Object value) {
        putRemote(key, value);
        sendSyncMsg(key);
        putLocal(key, value);
    }
    @Override
    public void evict(Object key) {
        // 先清理 redis 再清理 caffeine
        clearRemote(key);
        sendSyncMsg(key);
        clearLocal(key);
    }
    @Override
    public void clear() {
        // 先清理 redis 再清理 caffeine
        clearRemote(null);
        sendSyncMsg(null);
        clearLocal(null);
    }
    private void sendSyncMsg(Object key) {
        String syncTopic = cacheProperties.getRemote().getSyncTopic();
        DLCacheRefreshMsg refreshMsg = new DLCacheRefreshMsg(name, key);
        // 加入 SELF_MSG_MAP 防止自身節(jié)點(diǎn)重復(fù)處理
        DLCacheRefreshListener.SELF_MSG_MAP.add(refreshMsg);
        redisTemplate.convertAndSend(syncTopic, refreshMsg);
    }
    private void putLocal(Object key, Object value) {
        // toStoreValue 包裝 null 值
        caffeineCache.put(key.toString(), toStoreValue(value));
    }
    private void putRemote(Object key, Object value) {
        if (expiration > 0) {
            // toStoreValue 包裝 null 值
            redisTemplate.opsForValue().set(getRedisKey(key), toStoreValue(value), expiration, TimeUnit.MILLISECONDS);
            return;
        }
        redisTemplate.opsForValue().set(getRedisKey(key), toStoreValue(value));
    }
    public void clearRemote(Object key) {
        if (ObjectUtil.isNull(key)) {
            Set<String> keys = redisTemplate.keys(getRedisKey("*"));
            if (ObjectUtil.isNotEmpty(keys)) {
                keys.forEach(redisTemplate::delete);
            }
            return;
        }
        redisTemplate.delete(getRedisKey(key));
    }
    public void clearLocal(Object key) {
        if (ObjectUtil.isNull(key)) {
            caffeineCache.invalidateAll();
            return;
        }
        caffeineCache.invalidate(key);
    }
    /**
     * 檢查是否允許緩存 null
     *
     * @param value 緩存值
     * @return 不為空則 true,為空但允許則 false,否則異常
     */
    private boolean checkValNotNull(Object value) {
        if (ObjectUtil.isNotNull(value)) {
            return true;
        }
        if (isAllowNullValues() && ObjectUtil.isNull(value)) {
            return false;
        }
        // val 不能為空,但傳了空
        throw new DLCacheException("Check null val is not allowed");
    }
    @Override
    protected Object fromStoreValue(Object storeValue) {
        if (isAllowNullValues() && DLCacheNullVal.INSTANCE.equals(storeValue)) {
            return null;
        }
        return storeValue;
    }
    @Override
    protected Object toStoreValue(Object userValue) {
        if (!checkValNotNull(userValue)) {
            return DLCacheNullVal.INSTANCE;
        }
        return userValue;
    }
    /**
     * 獲取 redis 完整 key
     */
    private String getRedisKey(Object key) {
        // 雙冒號,與 spring cache 默認(rèn)一致
        return this.name.concat("::").concat(key.toString());
    }
    /**
     * 在緩存時代替 null 值,以區(qū)分是 key 不存在還是 val 為 null
     */
    @Data
    public static class DLCacheNullVal {
        public static final DLCacheNullVal INSTANCE = new DLCacheNullVal();
        private String desc = "nullVal";
    }
}

注意:需要區(qū)分緩存 get 到 null 值和 key 不存在,因此使用了 DLCacheNullVal 來代替 null 值

緩存管理器 DLCacheManager

緩存管理器

實現(xiàn) SpringBoot Cache 的 CacheManager 接口

@Slf4j
@RequiredArgsConstructor
public class DLCacheManager implements CacheManager {
    private final ConcurrentHashMap<String, DLCache> cacheMap = new ConcurrentHashMap<>();
    private final DLCacheProperties cacheProperties;
    private final RedisTemplate<String, Object> redisTemplate;
    @Override
    public DLCache getCache(String name) {
        return cacheMap.computeIfAbsent(name, (o) -> {
            DLCache dlCache = buildCache(o);
            log.debug("Create DLCache instance, name:{}", o);
            return dlCache;
        });
    }
    private DLCache buildCache(String name) {
        Caffeine<Object, Object> caffeine = Caffeine.newBuilder();
        // 設(shè)置過期時間 expireAfterWrite
        long expiration = 0;
        // 獲取針對 cache name 設(shè)置的過期時間
        Map<String, Long> cacheExpirationMap = cacheProperties.getCacheExpirationMap();
        if (ObjectUtil.isNotEmpty(cacheExpirationMap) && cacheExpirationMap.get(name) > 0) {
            expiration = cacheExpirationMap.get(name);
        } else if (cacheProperties.getDefaultExpiration() > 0) {
            expiration = cacheProperties.getDefaultExpiration();
        }
        if (expiration > 0) {
            caffeine.expireAfterWrite(expiration, TimeUnit.MILLISECONDS);
        }
        // 設(shè)置參數(shù)
        LocalConfig localConfig = cacheProperties.getLocal();
        if (ObjectUtil.isNotNull(localConfig.getInitialCapacity()) && localConfig.getInitialCapacity() > 0) {
            caffeine.initialCapacity(localConfig.getInitialCapacity());
        }
        if (ObjectUtil.isNotNull(localConfig.getMaximumSize()) && localConfig.getMaximumSize() > 0) {
            caffeine.maximumSize(localConfig.getMaximumSize());
        }
        return new DLCache(name, expiration, cacheProperties, caffeine.build(), redisTemplate);
    }
    @Override
    public Collection<String> getCacheNames() {
        return Collections.unmodifiableSet(cacheMap.keySet());
    }
}

緩存刷新監(jiān)聽器

緩存消息

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class DLCacheRefreshMsg {
    private String cacheName;
    private Object key;
}

緩存刷新消息監(jiān)聽

@Slf4j
@RequiredArgsConstructor
@Component
public class DLCacheRefreshListener implements MessageListener, InitializingBean {
    public static final ConcurrentHashSet<DLCacheRefreshMsg> SELF_MSG_MAP = new ConcurrentHashSet<>();
    private final DLCacheManager dlCacheManager;
    private final DLCacheProperties cacheProperties;
    private final RedisMessageListenerContainer listenerContainer;
    @Override
    public void onMessage(Message message, byte[] pattern) {
        // 序列化出刷新消息
        DLCacheRefreshMsg refreshMsg = (DLCacheRefreshMsg) RedisUtil.getTemplate().getValueSerializer().deserialize(message.getBody());
        if (ObjectUtil.isNull(refreshMsg)) {
            return;
        }
        // 判斷是不是自身節(jié)點(diǎn)發(fā)出
        if (SELF_MSG_MAP.contains(refreshMsg)) {
            SELF_MSG_MAP.remove(refreshMsg);
            return;
        }
        log.debug("DLCache refresh local, cache name:{}, key:{}", refreshMsg.getCacheName(), refreshMsg.getKey());
        // 清理本地緩存
        dlCacheManager.getCache(refreshMsg.getCacheName()).clearLocal(refreshMsg.getKey());
    }
    @Override
    public void afterPropertiesSet() {
        // 注冊到 RedisMessageListenerContainer
        listenerContainer.addMessageListener(this, new ChannelTopic(cacheProperties.getRemote().getSyncTopic()));
    }
}

使用二級緩存

注入 DLCacheManager

@Bean(name = "dlCacheManager")
    public DLCacheManager dlCacheManager(DLCacheProperties cacheProperties, RedisTemplate<String, Object> redisTemplate) {
        return new DLCacheManager(cacheProperties, redisTemplate);
    }

使用 @Cacheable 配合 DLCacheManager

@ApiOperation("測試 @Cacheable")
@Cacheable(cacheNames = "test", key = "'dl'", cacheManager = "dlCacheManager")
@PostMapping("test_cacheable")
public String testCacheable() {
    log.info("testCacheable 執(zhí)行");
    return "Cacheable";
}
@ApiOperation("測試 @Cacheable null 值")
@Cacheable(cacheNames = "test", key = "'dl'", cacheManager = "dlCacheManager")
@PostMapping("test_cacheable_null")
public String testCacheableNull() {
    log.info("testCacheableNull 執(zhí)行");
    return null;
}
@ApiOperation("測試 @CachePut")
@CachePut(cacheNames = "test", key = "'dl'", cacheManager = "dlCacheManager")
@PostMapping("test_put")
public String testPut() {
    return "Put";
}
@ApiOperation("測試 @CacheEvict")
@CacheEvict(cacheNames = "test", key = "'dl'", cacheManager = "dlCacheManager")
@PostMapping("test_evict")
public String testEvict() {
    return "Evict";
}

到此這篇關(guān)于SpringBoot Cache 二級緩存的使用的文章就介紹到這了,更多相關(guān)SpringBoot Cache 二級緩存內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 微信小程序登錄狀態(tài)java后臺解密

    微信小程序登錄狀態(tài)java后臺解密

    這篇文章主要為大家詳細(xì)介紹了微信小程序登錄狀態(tài)java后臺解密,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-12-12
  • Springboot如何使用mybatis實現(xiàn)攔截SQL分頁

    Springboot如何使用mybatis實現(xiàn)攔截SQL分頁

    這篇文章主要介紹了Springboot使用mybatis實現(xiàn)攔截SQL分頁,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-06-06
  • SpringBoot+Thymeleaf靜態(tài)資源的映射規(guī)則說明

    SpringBoot+Thymeleaf靜態(tài)資源的映射規(guī)則說明

    這篇文章主要介紹了SpringBoot+Thymeleaf靜態(tài)資源的映射規(guī)則說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • java用靜態(tài)工廠代替構(gòu)造函數(shù)使用方法和優(yōu)缺點(diǎn)

    java用靜態(tài)工廠代替構(gòu)造函數(shù)使用方法和優(yōu)缺點(diǎn)

    這篇文章主要介紹了java用靜態(tài)工廠代替構(gòu)造函數(shù)使用方法和優(yōu)缺點(diǎn),需要的朋友可以參考下
    2014-02-02
  • mybatis-plus分頁查詢的實現(xiàn)實例

    mybatis-plus分頁查詢的實現(xiàn)實例

    頁查詢是一項常用的數(shù)據(jù)庫查詢方法,本文主要介紹了mybatis-plus分頁查詢的實現(xiàn)實例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-06-06
  • elasticsearch集群cluster示例詳解

    elasticsearch集群cluster示例詳解

    這篇文章主要為大家介紹了elasticsearch集群cluster示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-04-04
  • SpringBoot Application注解原理及代碼詳解

    SpringBoot Application注解原理及代碼詳解

    這篇文章主要介紹了SpringBoot Application注解原理及代碼詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-06-06
  • redis scan命令導(dǎo)致redis連接耗盡,線程上鎖的解決

    redis scan命令導(dǎo)致redis連接耗盡,線程上鎖的解決

    這篇文章主要介紹了redis scan命令導(dǎo)致redis連接耗盡,線程上鎖的解決,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-11-11
  • sqlserver的jdbc配置方法

    sqlserver的jdbc配置方法

    這篇文章主要介紹了sqlserver的jdbc配置方法,需要的朋友可以參考下
    2014-04-04
  • java制作仿微信視頻播放控件

    java制作仿微信視頻播放控件

    這篇文章主要介紹了java制作仿微信視頻播放控件的方法和代碼分享,控件繼承自SurfaceView,十分的實用,小伙伴們可以自由擴(kuò)展。
    2015-04-04

最新評論