How Spring's Cacheable Annotation can work for class initailized through new Keyword. (In a Class Constructor, initialized through Bean)

huangapple go评论76阅读模式
英文:

How Spring's Cacheable Annotation can work for class initailized through new Keyword. (In a Class Constructor, initialized through Bean)

问题

在我们的服务中,我们正在初始化一个 bean(称为 "A"),并在内部使用 new CacheableService() 构造一个 CacheableService 对象。据我所知,如果使用 "new" 关键字初始化类,则 Spring 的 @Cacheable 注解不会对类方法起作用。

那么有什么替代方法或方式可以缓存方法的响应呢?

场景:

<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;
    }
}
英文:

In our service, we are initializing a bean (say "A") and that internally constructing a CacheableService Object by using - new CacheableService(). And as I know spring's @Cacheable annotations won't work on class method if the class is initialized using "new" Keyword.

Then what is an alternative or a way to cache method response?

Scenario :

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

答案1

得分: 2

以下是使用Spring Boot演示缓存的最小示例。下面的示例代码可以在这里找到。

前往 https://start.spring.io/ 并创建一个新的Spring Boot项目。确保包含“Spring缓存抽象”,这将导致将以下内容添加到您的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")被调用了两次,但在标准输出中,我们只看到 "Inside function: 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页面,您将看到许多详细的示例,以配置缓存以满足您的用例。

希望这可以帮助您!
1: https://github.com/jonckvanderkogel/caching-demo
2: https://github.com/ben-manes/caffeine

英文:

Here is a minimum example which demonstrates caching using Spring Boot. The code for the examples below can be found here.

Go to https://start.spring.io/ and create a new Spring Boot project. Make sure to include "Spring cache abstraction" which results in this entry being added to your pom:

&lt;dependency&gt;
	&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
	&lt;artifactId&gt;spring-boot-starter-cache&lt;/artifactId&gt;
&lt;/dependency&gt;

Add the @EnableCaching annotation to your application:

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);
	}
}

Your service:

package com.example;

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

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

Class 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);
    }
}

And then here is a test that demonstrates that the caching is working. As you can see in the test a.getEntityCount("foo") is being called twice, but in standard out we only see "Inside function: foo" being printed once. Therefore we have verified that the second call resulted in the cache being used to produce the result.

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(&quot;foo&quot;);
		a.getEntityCount(&quot;foo&quot;);

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

EDIT:
If you want to move the cache outside of the Spring lifecycle and manually manage it then I would recommend using Caffeine. Here is the same example but now without any Spring involved.

Your service:

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&lt;String, Integer&gt; entityCountCache = Caffeine.newBuilder()
            .expireAfterAccess(5, TimeUnit.MINUTES)
            .build(key -&gt; someEntityCount(key));

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

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

Class 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);
    }
}

And the same test but without 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(&quot;foo&quot;);
        b.getEntityCount(&quot;foo&quot;);

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

Obviously you need to tune the cache to perform how you want it so probably evicting the cached values after 5 minutes is not what you want but if you visit the Caffeine Github page you will see a lot of detailed examples how to configure the cache to meet your use-case.

Hope this helps!

huangapple
  • 本文由 发表于 2020年9月15日 16:08:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/63897674.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定