引言
使用缓存的目的就是提高性能,今天码哥带大家实践运用 spring-boot-starter-cache 抽象的缓存组件去集成本地缓存性能之王 Caffeine。
大家需要注意的是:in-memeory 缓存只适合在单体应用,不适合与分布式环境。
分布式环境的情况下需要将缓存修改同步到每个节点,需要一个同步机制保证每个节点缓存数据最终一致。
Spring Cache 是什么
不使用 Spring Cache 抽象的缓存接口,我们需要根据不同的缓存框架去实现缓存,需要在对应的代码里面去对应缓存加载、删除、更新等。
比如查询我们使用旁路缓存策略:先从缓存中查询数据,如果查不到则从数据库查询并写到缓存中。
伪代码如下:
public User getUser(long userId) { // 从缓存查询 User user = cache.get(userId); if (user != null) { return user; } // 从数据库加载 User dbUser = loadDataFromDB(userId); if (dbUser != null) { // 设置到缓存中 cache.put(userId, dbUser) } return dbUser; }
我们需要写大量的这种繁琐代码,Spring Cache 则对缓存进行了抽象,提供了如下几个注解实现了缓存管理:
@Cacheable:触发缓存读取操作,用于查询方法上,如果缓存中找到则直接取出缓存并返回,否则执行目标方法并将结果缓存。
@CachePut:触发缓存更新的方法上,与 Cacheable 相比,该注解的方法始终都会被执行,并且使用方法返回的结果去更新缓存,适用于 insert 和 update 行为的方法上。
@CacheEvict:触发缓存失效,删除缓存项或者清空缓存,适用于 delete 方法上。
除此之外,抽象的 CacheManager 既能集成基于本地内存的单体应用,也能集成 EhCache、Redis 等缓存服务器。
最方便的是通过一些简单配置和注解就能接入不同的缓存框架,无需修改任何代码。
集成 Caffeine
码哥带大家使用注解方式完成缓存操作的方式来集成,完整的代码请访问 github:在 pom.xml 文件添加如下依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> </dependency>
使用 JavaConfig 方式配置 CacheManager:
@Slf4j @EnableCaching @Configuration public class CacheConfig { @Autowired @Qualifier("cacheExecutor") private Executor cacheExecutor; @Bean public Caffeine<Object, Object> caffeineCache() { return Caffeine.newBuilder() // 设置最后一次写入或访问后经过固定时间过期 .expireAfterAccess(7, TimeUnit.DAYS) // 初始的缓存空间大小 .initialCapacity(500) // 使用自定义线程池 .executor(cacheExecutor) .removalListener(((key, value, cause) -> log.info("key:{} removed, removalCause:{}.", key, cause.name()))) // 缓存的最大条数 .maximumSize(1000); } @Bean public CacheManager cacheManager() { CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager(); caffeineCacheManager.setCaffeine(caffeineCache()); // 不缓存空值 caffeineCacheManager.setAllowNullValues(false); return caffeineCacheManager; } }
准备工作搞定,接下来就是如何使用了。
@Slf4j @Service public class AddressService { public static final String CACHE_NAME = "caffeine:address"; private static final AtomicLong ID_CREATOR = new AtomicLong(0); private Map<Long, AddressDTO> addressMap; public AddressService() { addressMap = new ConcurrentHashMap<>(); addressMap.put(ID_CREATOR.incrementAndGet(), AddressDTO.builder().customerId(ID_CREATOR.get()).address("地址1").build()); addressMap.put(ID_CREATOR.incrementAndGet(), AddressDTO.builder().customerId(ID_CREATOR.get()).address("地址2").build()); addressMap.put(ID_CREATOR.incrementAndGet(), AddressDTO.builder().customerId(ID_CREATOR.get()).address("地址3").build()); } @Cacheable(cacheNames = {CACHE_NAME}, key = "#customerId") public AddressDTO getAddress(long customerId) { log.info("customerId:{} 没有走缓存,开始从数据库查询", customerId); return addressMap.get(customerId); } @CachePut(cacheNames = {CACHE_NAME}, key = "#result.customerId") public AddressDTO create(String address) { long customerId = ID_CREATOR.incrementAndGet(); AddressDTO addressDTO = AddressDTO.builder().customerId(customerId).address(address).build(); addressMap.put(customerId, addressDTO); return addressDTO; } @CachePut(cacheNames = {CACHE_NAME}, key = "#result.customerId") public AddressDTO update(Long customerId, String address) { AddressDTO addressDTO = addressMap.get(customerId); if (addressDTO == null) { throw new RuntimeException("没有 customerId = " + customerId + "的地址"); } addressDTO.setAddress(address); return addressDTO; } @CacheEvict(cacheNames = {CACHE_NAME}, key = "#customerId") public boolean delete(long customerId) { log.info("缓存 {} 被删除", customerId); return true; } }
使用 CacheName 隔离不同业务场景的缓存,每个 Cache 内部持有一个 map 结构存储数据,key 可用使用 Spring 的 Spel 表达式。
单元测试走起:
@RunWith(SpringRunner.class) @SpringBootTest(classes = CaffeineApplication.class) @Slf4j public class CaffeineApplicationTests { @Autowired private AddressService addressService; @Autowired private CacheManager cacheManager; @Test public void testCache() { // 插入缓存 和数据库 AddressDTO newInsert = addressService.create("南山大道"); // 要走缓存 AddressDTO address = addressService.getAddress(newInsert.getCustomerId()); long customerId = 2; // 第一次未命中缓存,打印 customerId:{} 没有走缓存,开始从数据库查询 AddressDTO address2 = addressService.getAddress(customerId); // 命中缓存 AddressDTO cacheAddress2 = addressService.getAddress(customerId); // 更新数据库和缓存 addressService.update(customerId, "地址 2 被修改"); // 更新后查询,依然命中缓存 AddressDTO hitCache2 = addressService.getAddress(customerId); Assert.assertEquals(hitCache2.getAddress(), "地址 2 被修改"); // 删除缓存 addressService.delete(customerId); // 未命中缓存, 从数据库读取 AddressDTO hit = addressService.getAddress(customerId); System.out.println(hit.getCustomerId()); } }
大家发现没,只需要在对应的方法上加上注解,就能愉快的使用缓存了。需要注意的是, 设置的 cacheNames 一定要对应,每个业务场景使用对应的 cacheNames。
另外 key 可以使用 spel 表达式,大家重点可以关注 @CachePut(cacheNames = {CACHE_NAME}, key = "#result.customerId"),result 表示接口返回结果,Spring 提供了几个元数据直接使用。
名称 | 地点 | 描述 | 例子 |
---|---|---|---|
methodName | 根对象 | 被调用的方法的名称 | #root.methodName |
method | 根对象 | 被调用的方法 | #root.method.name |
target | 根对象 | 被调用的目标对象 | #root.target |
targetClass | 根对象 | 被调用的目标的类 | #root.targetClass |
args | 根对象 | 用于调用目标的参数(作为数组) | #root.args[0] |
caches | 根对象 | 运行当前方法的缓存集合 | #root.caches[0].name |
参数名称 | 评估上下文 | 任何方法参数的名称。如果名称不可用(可能是由于没有调试信息),则参数名称也可在#a where#arg代表参数索引(从 开始0)下获得。 | #iban或#a0(您也可以使用#p0或#p表示法作为别名)。 |
result | 评估上下文 | 方法调用的结果(要缓存的值)。仅在unless 表达式、cache put表达式(计算key)或cache evict 表达式(when beforeInvocationis false)中可用。对于支持的包装器(例如 Optional),#result指的是实际对象,而不是包装器。 | #result |
コア原則
Java キャッシュは、CachingProvider、CacheManager、Cache、Entry、および Expiry という 5 つのコア インターフェイスを定義します。
# コア クラス図:
- ##キャッシュ: get( などのキャッシュ操作を抽象化します。 ), put();
- CacheManager: Cache を管理します。これは Cache のコレクション管理として理解できます。Cache が複数ある理由は、異なるキャッシュに応じて異なるキャッシュを使用できるためです。有効期限と数量制限。
- CacheInterceptor、CacheAspectSupport、AbstractCacheInvoker: CacheInterceptor は、メソッドの前後で追加のロジック (クエリ操作など) を実行する AOP メソッド インターセプターであり、最初にキャッシュを確認し、問題があればメソッドを実行します。これは、CacheAspectSupport (キャッシュ操作のメイン ロジック) と AbstractCacheInvoker (キャッシュの読み取りと書き込みをカプセル化する) を継承します。
- CacheOperation、AnnotationCacheOperationSource、SpringCacheAnnotationParser: CacheOperationはキャッシュ操作のキャッシュ名、キャッシュキー、キャッシュ条件、CacheManagerなどを定義する AnnotationCacheOperationSourceは、に対応するキャッシュアノテーションを取得するクラスです。 CacheOperation、SpringCacheAnnotationParser アノテーションを解析するクラスで、解析後はAnnotationCacheOperationSourceが検索できるようにCacheOperationコレクションにカプセル化されます。
- CacheOperationSource を通じてすべての CacheOperation リストを取得します。
- @CacheEvict アノテーションがあり、マーク 呼び出す前に実行するには、キャッシュを削除/クリアします。
- @Cacheable アノテーションがある場合は、キャッシュをクエリします。
- キャッシュが見つからない場合は (クエリ結果は null)、cachePutRequests に追加され、その後の実行後に元のメソッドがキャッシュに書き込まれます。
- キャッシュがヒットすると、キャッシュ値が次のように使用されます。結果; キャッシュがミスするか、 @CachePut アノテーションが付けられている場合は、元のメソッドを呼び出し、元のメソッドの戻り値を結果として使用する必要があります。 @CachePut アノテーションを、cachePutRequests に追加します。
- キャッシュがミスした場合は、クエリ結果の値がキャッシュに書き込まれます。@CachePut アノテーションがある場合は、メソッドの実行結果も書き込まれます。キャッシュに
- @CacheEvict アノテーションがあり、呼び出し後に実行するようにマークされている場合は、キャッシュを削除/クリアします
以上がSpringBoot 統合ローカル キャッシュ パフォーマンスのカフェイン インスタンス分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

Javaはプラットフォーム固有の問題をどのように軽減しますか? Javaは、JVMおよび標準ライブラリを通じてプラットフォームに依存します。 1)bytecodeとjvmを使用して、オペレーティングシステムの違いを抽象化します。 2)標準のライブラリは、パスクラス処理ファイルパス、CHARSETクラス処理文字エンコードなど、クロスプラットフォームAPIを提供します。 3)最適化とデバッグのために、実際のプロジェクトで構成ファイルとマルチプラットフォームテストを使用します。

java'splatformentencentenhancesmicroservicesecturectureby byofferingdeploymentflexability、一貫性、スケーラビリティ、およびポート可能性。1)展開の展開の展開は、AllosmicRoserviThajvm.2)deploymentflexibility lowsmicroserviceSjvm.2)一貫性のあるAcrossServicessimplisimpligiessdevelisementand

Graalvmは、Javaのプラットフォームの独立性を3つの方法で強化します。1。言語間の相互運用性、Javaが他の言語とシームレスに相互運用できるようにします。 2。独立したランタイム環境、graalvmnativeimageを介してJavaプログラムをローカル実行可能ファイルにコンパイルします。 3.パフォーマンスの最適化、Graalコンパイラは、Javaプログラムのパフォーマンスと一貫性を改善するための効率的なマシンコードを生成します。

aeffectivelytestjavaapplicationsforformcompativity、followthesesteps:1)setupautomatedacrossmultipleplatformsusingsingcitoolslikejenkinsorgithubactions.2)divivisonmanualtingonrealhardwaretocatissusuessususus.3)

Javaコンパイラは、ソースコードをプラットフォームに依存しないバイトコードに変換することにより、Javaのプラットフォームの独立性を実現し、JVMがインストールされた任意のオペレーティングシステムでJavaプログラムを実行できるようにします。

bytecodeachievesplatformedentencedexedectedbyavirtualMachine(VM)、forexApplev.forexample、javabytecodecanrunrunrunnonydevicewithajvm、writeonce、runany "ferfuctionality.whilebytecodeOffersenhの可能性を承認します

Javaは100%のプラットフォームの独立性を達成することはできませんが、そのプラットフォームの独立性はJVMとBytecodeを通じて実装され、コードが異なるプラットフォームで実行されるようにします。具体的な実装には、次のものが含まれます。1。bytecodeへのコンパイル。 2。JVMの解釈と実行。 3。標準ライブラリの一貫性。ただし、JVMの実装の違い、オペレーティングシステムとハードウェアの違い、およびサードパーティライブラリの互換性は、プラットフォームの独立性に影響を与える可能性があります。

Javaは、「Write onse、Averywhere」を通じてプラットフォームの独立性を実現し、コードの保守性を向上させます。 2。メンテナンスコストが低いため、1つの変更のみが必要です。 3.チームのコラボレーション効率が高く、知識共有に便利です。


ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

Video Face Swap
完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

人気の記事

ホットツール

SublimeText3 Linux 新バージョン
SublimeText3 Linux 最新バージョン

WebStorm Mac版
便利なJavaScript開発ツール

Dreamweaver Mac版
ビジュアル Web 開発ツール

SublimeText3 英語版
推奨: Win バージョン、コードプロンプトをサポート!

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ホットトピック









