Ръководство за кеширане през пролетта

1. Абстракцията на кеша?

В тази статия ще покажем как да използвате абстракцията за кеширане през пролетта - и като цяло ще подобрим производителността на вашата система.

Ще активираме просто кеширане за някои примери от реални методи и ще обсъдим как можем на практика да подобрим ефективността на тези повиквания чрез интелигентно управление на кеша.

2. Първи стъпки

Абстракцията за кеширане на ядрото, предоставена от Spring, се намира в модула spring-context . Така че, когато използваме Maven, нашият pom.xml трябва да съдържа следната зависимост:

 org.springframework spring-context 5.2.8.RELEASE 

Интересното е, че има още един модул, наречен spring-context-support, който се намира на върха на модула spring-context и осигурява още няколко CacheManagers, подкрепени от подобни на EhCache или кофеин. Ако ще ги използвате като кеш памет, използвайте вместо това модула spring-context-support :

 org.springframework spring-context-support 5.2.8.RELEASE 

Тъй като модулът spring-context-support преходно зависи от модула spring-context , няма нужда от отделна декларация за зависимост за spring-context.

2.1. Пролетно зареждане

Ако сте потребител на Spring Boot, използвайте пакета spring-boot-starter-cache starter, за да добавите лесно кеширащите зависимости:

 org.springframework.boot spring-boot-starter-cache 2.3.3.RELEASE 

Под капака стартерът носи модула за поддръжка на пружината .

3. Активирайте кеширането

За да активира кеширането, Spring използва добре анотациите, подобно на активирането на всяка друга функция на ниво конфигурация в рамката.

Функцията за кеширане може да бъде декларативно активирана чрез просто добавяне на анотацията @EnableCaching към някой от класовете за конфигуриране:

@Configuration @EnableCaching public class CachingConfig { @Bean public CacheManager cacheManager() { return new ConcurrentMapCacheManager("addresses"); } }

Можете, разбира се, да активирате управлението на кеша и с XML конфигурация:

Забележка: След като активираме кеширането - за минимална настройка - трябва да регистрираме cacheManager .

3.1. Използване на Spring Boot

Когато използвате Spring Boot, самото присъствие на стартовия пакет в пътя на класа заедно с анотацията EnableCaching ще регистрира същия ConcurrentMapCacheManager. Така че, няма нужда от отделна декларация за боб.

Също така можем да персонализираме автоматично конфигурирания CacheManager, като използваме един или повече зърна CacheManagerCustomizer :

@Component public class SimpleCacheCustomizer implements CacheManagerCustomizer { @Override public void customize(ConcurrentMapCacheManager cacheManager) { cacheManager.setCacheNames(asList("users", "transactions")); } }

В CacheAutoConfiguration авто-конфигурация улавя тези персонализиране и да ги прилага и за настоящата CacheManager преди пълното му инициализация.

4. Използвайте кеширане с анотации

След като активираме кеширането, следващата стъпка е да обвържем поведението на кеширането с методите с декларативни анотации.

4.1. @ Може да се кешира

Най-простият начин за активиране на поведението на кеширане за метод е да го разграничите с @Cacheable и да го параметризирате с името на кеша, където ще се съхраняват резултатите:

@Cacheable("addresses") public String getAddress(Customer customer) {...} 

В getAddress () повикване първо проверява кеш адресите , преди всъщност да се позовава на метода и след това кеширане на резултата.

Докато в повечето случаи е достатъчен един кеш, Spring структурата също така поддържа множество кешове, които трябва да бъдат предадени като параметри:

@Cacheable({"addresses", "directory"}) public String getAddress(Customer customer) {...}

В този случай, ако някой от кешовете съдържа необходимия резултат, резултатът се връща и методът не се извиква.

4.2. @ CacheEvict

Сега какъв би бил проблемът с превръщането на всички методи в @Cacheable ?

Проблемът е в размера - не искаме да попълваме кеша със стойности, които не ни трябват често . Кеш паметта може да нарасне доста голяма, доста бърза и бихме могли да задържим много остарели или неизползвани данни.

В @CacheEvict анотацията се използва за обозначаване отстраняването на един или повече / всички стойности - така че прясна стойности могат да бъдат заредени в кеш отново:

@CacheEvict(value="addresses", allEntries=true) public String getAddress(Customer customer) {...}

Тук използваме допълнителния параметър allEntries заедно с кеша, който трябва да се изпразни - за да изчистим всички записи в адресите на кеша и да го подготвим за нови данни.

4.3. @ CachePut

While @CacheEvict reduces the overhead of looking up entries in a large cache by removing stale and unused entries, ideally, you want to avoid evicting too much data out of the cache.

Instead, you'd want to selectively and intelligently update the entries whenever they're altered.

With the @CachePut annotation, you can update the content of the cache without interfering the method execution. That is, the method would always be executed and the result cached.

@CachePut(value="addresses") public String getAddress(Customer customer) {...}

The difference between @Cacheable and @CachePut is that @Cacheable will skip running the method, whereas @CachePut will actually run the method and then put its results in the cache.

4.4. @Caching

What if you want to use multiple annotations of the same type for caching a method. Look at the incorrect example below:

@CacheEvict("addresses") @CacheEvict(value="directory", key=customer.name) public String getAddress(Customer customer) {...}

The above code would fail to compile since Java does not allow multiple annotations of the same type to be declared for a given method.

The workaround to the above issue would be:

@Caching(evict = { @CacheEvict("addresses"), @CacheEvict(value="directory", key="#customer.name") }) public String getAddress(Customer customer) {...}

As shown in the code snippet above, you can group multiple caching annotations with @Caching, and use it to implement your own customized caching logic.

4.5. @CacheConfig

With the @CacheConfig annotation, you can streamline some of the cache configuration into a single place – at the class level – so that you don't have to declare things multiple times:

@CacheConfig(cacheNames={"addresses"}) public class CustomerDataService { @Cacheable public String getAddress(Customer customer) {...}

5. Conditional Caching

Sometimes, caching might not work well for a method in all situations.

For example – reusing our example from the @CachePut annotation – this will both execute the method as well as cache the results each and every time:

@CachePut(value="addresses") public String getAddress(Customer customer) {...} 

5.1. Condition Parameter

Now – if we want more control over when the annotation is active – @CachePut can be parametrized with a condition parameter that takes a SpEL expression to ensure that the results are cached based on evaluating that expression:

@CachePut(value="addresses", condition="#customer.name=='Tom'") public String getAddress(Customer customer) {...}

5.2. Unless Parameter

We can also control the caching based on the output of the method rather than the input – via the unless parameter:

@CachePut(value="addresses", unless="#result.length()<64") public String getAddress(Customer customer) {...}

The above annotation would cache addresses unless they are shorter than 64 characters.

It's important to know that the condition and unless parameters can be used in conjunction with all the caching annotations.

Този вид условно кеширане може да се окаже доста полезен за управление на големи резултати и персонализиране на поведението въз основа на входните параметри, вместо да налага общото поведение на всички операции.

6. Декларативно базирано на XML кеширане

В случай че нямате достъп до изходния код на приложението си или искате да инжектирате поведението на кеширането външно, можете също да използвате декларативно базирано на XML кеширане.

Ето нашата XML конфигурация:

7. Кеширането, базирано на Java

И тук е еквивалентната Java конфигурация:

@Configuration @EnableCaching public class CachingConfig { @Bean public CacheManager cacheManager() { SimpleCacheManager cacheManager = new SimpleCacheManager(); cacheManager.setCaches(Arrays.asList( new ConcurrentMapCache("directory"), new ConcurrentMapCache("addresses"))); return cacheManager; } }

И тук е нашата CustomerDataService :

@Component public class CustomerDataService { @Cacheable(value = "addresses", key = "#customer.name") public String getAddress(Customer customer) { return customer.getAddress(); } }

8. Обобщение

В тази статия обсъдихме основите на кеширането през пролетта и как да използваме добре тази абстракция с пояснения.

Пълното изпълнение на тази статия може да бъде намерено в проекта GitHub.