Как кэшируемая аннотация Spring может работать для класса, инициализированного с помощью нового ключевого слова. (В конструкторе класса, инициализируемом с помощью компонента)

#java #spring #caching

#java #spring #кэширование

Вопрос:

В нашем сервисе мы инициализируем компонент (скажем, «A»), который внутренне создает CacheableService объект с помощью — new CacheableService() . И, как я знаю, @Cacheable аннотации spring не будут работать в методе класса, если класс инициализирован с использованием ключевого слова «new».

Тогда какова альтернатива или способ кэширования ответа метода?

Сценарий :

 <bean class="com.package.src.A"/>
  
 public class A {
    
    Map<String, CacheableService> map;
    public CacheableService2() {
        map = new HashedMap();
        map.put("a", new CacheableService());
    }
}
  
 import org.springframework.cache.annotation.Cacheable;
    
public class CacheableService {
    
    
    @Cacheable(value = "entityCount", key = "#criteria.toString()")
    public int someEntityCount(final String criteria) {
        System.out.println("Inside function : "   criteria);
        return 5;
    }
}
  

Комментарии:

1. Используйте полномасштабный aspectj со временем компиляции, или создайте его самостоятельно, или просто используйте правильную инъекцию зависимостей.

2. Спасибо Deinum. Не могли бы вы привести какой-либо пример того, как я могу это сделать, используя — aspectj с переплетением времени компиляции. И что вы подразумеваете под «создайте это самостоятельно»?

3. Что сказал М. Дейнум, используйте внедрение зависимостей. Другими словами, сделайте ваш CacheableService управляемым компонентом Spring, а затем автоматически подключите его к вашему классу A. Если вы создаете свой собственный класс, вызывая «new», вы помещаете этот экземпляр за пределы жизненного цикла Spring, поэтому Spring не сможет управлять этим компонентом для вас.

4. Чтобы использовать AspectJ во время компиляции, вам нужно настроить другой компилятор, переключить режим AOP для всего приложения на использование AspectJ, а не прокси. Здесь, в stackoverflow, есть несколько руководств, а также ответы о том, как это сделать.

Ответ №1:

Вот минимальный пример, который демонстрирует кэширование с использованием Spring Boot. Код для приведенных ниже примеров можно найти здесь.

Перейдите к https://start.spring.io / и создающем новый проект Spring Boot. Обязательно включите «Абстракцию Spring cache», в результате чего эта запись будет добавлена в ваш pom:

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

Добавьте аннотацию @EnableCaching в ваше приложение:

 package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

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

Ваш сервис:

 package com.example;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class CacheableService {
    @Cacheable(value = "entityCount")
    public int someEntityCount(final String criteria) {
        System.out.print(String.format("Inside function: %s", criteria));
        return 5;
    }
}
  

Класс A:

 package com.example;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class A {
    private CacheableService cacheableService;

    public A(@Autowired CacheableService cacheableService) {
        this.cacheableService = cacheableService;
    }

    public int getEntityCount(String criteria) {
        return cacheableService.someEntityCount(criteria);
    }
}
  

И затем вот тест, который демонстрирует, что кэширование работает. Как вы можете видеть в тесте, a.getEntityCount(«foo») вызывается дважды, но в стандартном out мы видим, что «Внутренняя функция: foo» печатается только один раз. Поэтому мы проверили, что второй вызов привел к использованию кэша для получения результата.

 package com.example;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

import static org.junit.jupiter.api.Assertions.assertEquals;

@SpringBootTest
class CacheableTest {
    private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();

    @Autowired
    private A a;

    @BeforeEach
    public void init() {
        System.setOut(new PrintStream(outContent));
    }

    @Test
    public void testCaching() {
        a.getEntityCount("foo");
        a.getEntityCount("foo");

        assertEquals("Inside function: foo", outContent.toString());
    }
}
  

Редактировать:
Если вы хотите переместить кеш за пределы жизненного цикла Spring и вручную управлять им, я бы рекомендовал использовать Caffeine. Вот тот же пример, но теперь без участия Spring.

Ваш сервис:

 package com.example.withoutspring;

import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;

import java.util.concurrent.TimeUnit;

public class CaffeineCachingService {
    private LoadingCache<String, Integer> entityCountCache = Caffeine.newBuilder()
            .expireAfterAccess(5, TimeUnit.MINUTES)
            .build(key -> someEntityCount(key));

    public int cachedEntityCount(final String criteria) {
        return entityCountCache.get(criteria);
    }

    private int someEntityCount(final String criteria) {
        System.out.print(String.format("Inside function: %s", criteria));
        return 5;
    }
}
  

Класс B:

 package com.example.withoutspring;

public class B {
    private CaffeineCachingService cacheableService;

    public B() {
        cacheableService = new CaffeineCachingService();
    }

    public int getEntityCount(String criteria) {
        return cacheableService.cachedEntityCount(criteria);
    }
}
  

И тот же тест, но без Spring:

 package com.example.withoutspring;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class CaffeineCacheableTest {
    private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();

    private B b = new B();

    @BeforeEach
    public void init() {
        System.setOut(new PrintStream(outContent));
    }

    @Test
    public void testCaching() {
        b.getEntityCount("foo");
        b.getEntityCount("foo");

        assertEquals("Inside function: foo", outContent.toString());
    }
}
  

Очевидно, вам нужно настроить кеш, чтобы он выполнялся так, как вы хотите, поэтому, вероятно, удаление кэшированных значений через 5 минут — это не то, что вы хотите, но если вы посетите страницу Caffeine Github, вы увидите множество подробных примеров того, как настроить кеш в соответствии с вашим вариантом использования.

Надеюсь, это поможет!