首页 > 解决方案 > Java Spring 4.2.5 Guava Cache 无法添加额外的@Cacheable 注解

问题描述

我正在开发一个现有的 Java 8/Spring 4.2.5 Web 应用程序,并尝试将其使用 Spring 对 Guava Cache 的支持的已经工作的缓存扩展到更多功能,以期提高性能。该应用程序使用 Spring 4 的内置缓存,如在其 CacheConfig 文件中设置的那样:

CacheConfig.java

import com.google.common.cache.CacheBuilder;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.guava.GuavaCache;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableCaching
public class CacheConfig implements CachingConfigurer {

    @Bean
    @Override
    public CacheManager cacheManager() {
        SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
        GuavaCache cache1 = new GuavaCache("hourCache", CacheBuilder.newBuilder()
                .expireAfterWrite(60, TimeUnit.MINUTES)
                .build());
        simpleCacheManager.setCaches(Arrays.asList(cache1));
        return simpleCacheManager;
    }

    @Override
    public CacheResolver cacheResolver() {
        return null;
    }

    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object o, Method method, Object... params) {
                StringBuilder sb = new StringBuilder();
                sb.append(o.getClass().getName());
                sb.append(method.getName());
                for (Object param : params) {
                    sb.append(param.toString());
                }
                return sb.toString();
            }
        };
    }

    @Override
    public CacheErrorHandler errorHandler() {
        return null;
    }
}

先前的缓存内容声明是通过注释完成的:

调度响应服务.java

@Cacheable(value = "hourCache", unless="#result == null")
public ScheduleResponse getYearsTypeSummary(HttpServletRequest request) {

        ScheduleResponse scheduleResponse = new ScheduleResponse();
        YearsType yearsType = null;

        String exceptionMsg ="Could not retrieve list of summary years for Schedule.";
        scheduleResponse.setMessageText(exceptionMsg);
        try {
            yearsType = scheduleApiClient.getYears(authenticator.getAuthorizationToken(request), SUMMARY);
        } catch (ClientResponseFailure ex) {
            handleClientResponseFailure(scheduleResponse, ex.getResponse(), exceptionMsg);
        } catch (PrivilegedActionException | GSSException ex) {
            handleGenericException(scheduleResponse, ex.getMessage(), exceptionMsg);
        } catch (Exception ex) {
            handleGenericResponseException(scheduleResponse, ex.getMessage(), exceptionMsg, request);
        }

        scheduleResponse.setYearsType(yearsType);
        return scheduleResponse;
    }

但是,当我尝试为以前未缓存的同一个 Java 文件中的另一个函数扩展相同的基于注释的缓存声明时,即:

@Cacheable(value = "hourCache")
public ScheduleResponse getTermTypeSummary(HttpServletRequest request, String year, String term, String sess) {

        ScheduleResponse scheduleResponse = new ScheduleResponse();
        TermType termType = null;

        String exceptionMsg = "Sorry! Could not retrieve the " + term.toUpperCase() + " " + year + " term.";
        scheduleResponse.setMessageText(exceptionMsg);
        try {
            termType = scheduleApiClient.getTerm(authenticator.getAuthorizationToken(request), year, term, SUMMARY, sess);
        } catch (ClientResponseFailure ex) {
            handleClientResponseFailure(scheduleResponse, ex.getResponse(), exceptionMsg);
        } catch (PrivilegedActionException | GSSException ex) {
            handleGenericException(scheduleResponse, ex.getMessage(), exceptionMsg);
        } catch (Exception ex) {
            handleGenericResponseException(scheduleResponse, ex.getMessage(), exceptionMsg, request);
        }

        scheduleResponse.setTermType(termType);
        return scheduleResponse;
    }

我收到 NullPointerException 错误:

webmvc.config.CacheConfig$1.generate(CacheConfig.java:48) org.springframework.cache.interceptor.CacheAspectSupport$CacheOperationContext.generateKey(CacheAspectSupport.java:637)
org.springframework.cache.interceptor.CacheAspectSupport.generateKey(CacheAspectSupport.java:490)
org.springframework.cache.interceptor.CacheAspectSupport.findCachedItem(CacheAspectSupport.java:434)
org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:336)
org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:302)
org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:61)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:655)
webmvc.response.ScheduleResponseService$$EnhancerBySpringCGLIB$$952eed68.getTermTypeSummary()
webmvc.courses.controller.ScheduleController.displayScheduleTerm(ScheduleController.java:79)

我可以将@Cacheable... 注释添加到任何函数以接收NullPointerException。我不明白为什么我无法将缓存扩展到新函数,因为它没有出现额外的声明或注释来支持代码中其他任何地方的缓存。

有人可以指出我在以这种方式扩展缓存时可能做错了什么,以及如何纠正它?

标签: javaspringcachingguava

解决方案


正如 M. Deinum 所说,自定义密钥生成器是问题所在。当其中一个参数为空时,keygenerator 会触发空指针异常。为了纠正这个问题,我将代码修改为:

    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object o, Method method, Object... params) {
                StringBuilder sb = new StringBuilder();
                sb.append(o.getClass().getName());
                sb.append(method.getName());
                for (Object param : params) {
                    if (param != null) {
                        sb.append(param.toString());
                    }
                }
                return sb.toString();
            }
        };
    }

它解决了迄今为止我测试过的所有情况的空指针异常。


推荐阅读