加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Java > 正文

java – 通过SpringCache缓存嵌套的可缓存操作

发布时间:2020-12-15 04:26:39 所属栏目:Java 来源:网络整理
导读:我被赋予了将 SpringCache用于我们的一项服务以减少数据库查找次数的任务.在测试实现时,我注意到一些可缓存的操作是通过log-statements多次调用的.调查显示,如果在可缓存方法中调用可缓存操作,则根本不缓存嵌套操作.因此,稍后调用嵌套操作会导致进一步查找.
我被赋予了将 SpringCache用于我们的一项服务以减少数据库查找次数的任务.在测试实现时,我注意到一些可缓存的操作是通过log-statements多次调用的.调查显示,如果在可缓存方法中调用可缓存操作,则根本不缓存嵌套操作.因此,稍后调用嵌套操作会导致进一步查找.

下面列出了一个描述问题的简单单元测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringCacheTest.Config.class} )
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class SpringCacheTest {

  private final static String CACHE_NAME = "testCache";
  private final static Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
  private final static AtomicInteger methodInvocations = new AtomicInteger(0);

  public interface ICacheableService {

    String methodA(int length);
    String methodB(String name);
  }

  @Resource
  private ICacheableService cache;

  @Test
  public void testNestedCaching() {

    String name = "test";
    cache.methodB(name);
    assertThat(methodInvocations.get(),is(equalTo(2)));

    cache.methodA(name.length());
    // should only be 2 as methodA for this length was already invoked before
    assertThat(methodInvocations.get(),is(equalTo(3)));
  }

  @Configuration
  public static class Config {

    @Bean
    public CacheManager getCacheManager() {
      SimpleCacheManager cacheManager = new SimpleCacheManager();
      cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache(CACHE_NAME)));
      return cacheManager;
    }

    @Bean
    public ICacheableService getMockedEntityService() {
      return new ICacheableService() {
        private final Random random = new Random();

        @Cacheable(value = CACHE_NAME,key = "#root.methodName.concat('_').concat(#p0)")
        public String methodA(int length) {
          methodInvocations.incrementAndGet();
          LOG.debug("Invoking methodA");
          char[] chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray();
          StringBuilder sb = new StringBuilder();
          for (int i=0; i<length; i++) {
            sb.append(chars[random.nextInt(chars.length)]);
          }
          String result = sb.toString();
          LOG.debug("Returning {} for length: {}",result,length);
          return result;
        }

        @Cacheable(value = CACHE_NAME,key = "#root.methodName.concat('_').concat(#p0)")
        public String methodB(String name) {
          methodInvocations.incrementAndGet();
          LOG.debug("Invoking methodB");

          String rand = methodA(name.length());
          String result = name+"_"+rand;
          LOG.debug("Returning {} for name: {}",name);
          return result;
        }
      };
    }
  }
}

两种方法的实际工作对于测试用例本身并不重要,因为只应测试高速缓存.

我以某种方式理解嵌套操作的结果没有被缓存的原因,但我想知道是否有可用的配置,我还没想到,为嵌套可缓存操作的返回值启用缓存.

我知道通过重构并提供嵌套操作的返回值作为外部操作的参数将起作用,但是这可能涉及更改一些操作(以及单元测试它们)配置或其他解决方法(如果在我们的具体案例中,这将是更可取的.

解决方法

问题是您直接从methodB访问methodA,因此这会阻止通过处理缓存机制的Java代理.此外,您没有添加@EnableCaching批注,因此在您的测试中实际上根本没有缓存.

以下测试表明,如果您正确浏览Spring创建的代理,嵌套缓存模式将按预期工作:

import static org.junit.Assert.*;

import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { SpringCacheTest.Config.class })
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class SpringCacheTest {

    private final static String CACHE_NAME = "testCache";
    private final static AtomicInteger methodInvocations = new AtomicInteger(0);

    public interface ICacheableService {

        String methodA(int length);

        String methodB(String name);
    }

    @Resource
    private ICacheableService cache;

    @Test
    public void testNestedCaching() {

        String name = "test";
        cache.methodB(name);
        assertEquals(methodInvocations.get(),2);

        cache.methodA(name.length());
        // should only be 2 as methodA for this length was already invoked before
        assertEquals(methodInvocations.get(),2);
    }

    @Configuration
    @EnableCaching
    public static class Config {

        @Bean
        public CacheManager getCacheManager() {
            SimpleCacheManager cacheManager = new SimpleCacheManager();
            cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache(CACHE_NAME)));
            return cacheManager;
        }

        @Bean
        public ICacheableService getMockedEntityService() {
            return new ICacheableService() {
                private final Random random = new Random();

                @Autowired
                ApplicationContext context;

                @Override
                @Cacheable(value = CACHE_NAME,key = "#root.methodName.concat('_').concat(#p0)")
                public String methodA(int length) {
                    methodInvocations.incrementAndGet();
                    System.out.println("Invoking methodA");
                    char[] chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray();
                    StringBuilder sb = new StringBuilder();
                    for (int i = 0; i < length; i++) {
                        sb.append(chars[random.nextInt(chars.length)]);
                    }
                    String result = sb.toString();
                    System.out.println("Returning " + result + " for length: " + length);
                    return result;
                }

                @Override
                @Cacheable(value = CACHE_NAME,key = "#root.methodName.concat('_').concat(#p0)")
                public String methodB(String name) {
                    methodInvocations.incrementAndGet();
                    System.out.println("Invoking methodB");
                    ICacheableService cache = context.getBean(ICacheableService.class);
                    String rand = cache.methodA(name.length());
                    String result = name + "_" + rand;
                    System.out.println("Returning " + result + " for name: " + name);
                    return result;
                }
            };
        }
    }
}

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读