Spring Cache Abstraction&定制转换器

时间:2013-08-05 08:12:01

标签: spring caching converter abstraction

我试图在@Cacheable注释中使用“自己创建的”对象作为键的一部分:

@Cacheable(value = "tecdocData", key = "'TCDD:stos::'.concat(#stos)")
List<TecDocData> getTecDocData(Collection<SimpleTecDocObject> stos);

这会导致跟踪堆栈跟踪(相关部分)出错:

Caused by: org.springframework.expression.AccessException: Problem invoking method: public java.lang.String java.lang.String.concat(java.lang.String)
    at org.springframework.expression.spel.support.ReflectiveMethodExecutor.execute(ReflectiveMethodExecutor.java:73)
    at org.springframework.expression.spel.ast.MethodReference$MethodValueRef.getValue(MethodReference.java:97)
    ... 100 more
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1001E:(pos 0): Type conversion problem, cannot convert from java.util.HashSet<?> to java.lang.String
    at org.springframework.expression.spel.support.StandardTypeConverter.convertValue(StandardTypeConverter.java:72)
    at org.springframework.expression.spel.support.ReflectionHelper.convertArguments(ReflectionHelper.java:281)
    at org.springframework.expression.spel.support.ReflectiveMethodExecutor.execute(ReflectiveMethodExecutor.java:61)
    ... 101 more
Caused by: org.springframework.core.convert.ConversionFailedException: Failed to convert from type java.util.HashSet<?> to type java.lang.String for value '[SimpleTecDocObject [guid=GUID-C2DBD976-79F0-42FB-94CE-9352DE43A184, locale=de, version=1]]'; nested exception is org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type com.sxp.setix.api.SimpleTecDocObject to type java.lang.String
    at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:41)
    at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:169)
    at org.springframework.expression.spel.support.StandardTypeConverter.convertValue(StandardTypeConverter.java:66)
    ... 103 more
Caused by: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type com.sxp.setix.api.SimpleTecDocObject to type java.lang.String
    at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:276)
    at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:172)
    at org.springframework.core.convert.support.CollectionToStringConverter.convert(CollectionToStringConverter.java:65)
    at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:35)
    ... 105 more

据我所知,spring会尝试转换HashSet,但无法转换包含的“SimpleTecDocObject”值。

为了解决这个问题,我定义了一个转换器并在我的Spring配置中注册了它。如“7.5 Spring 3 Type Conversion”7. Validation, Data Binding, and Type Conversion

中所述,这是完全正确的

转换器类:

public class SimpleTecdocObjectConverter implements Converter<SimpleTecDocObject, String> {
    @Override
    public String convert(final SimpleTecDocObject source) {
        StringBuilder result = new StringBuilder();

        result.append(source.getGuid()).append(source.getLocale()).append(source.getVersion());

        return result.toString();
    }

}

Spring配置:

<bean id="conversionService"
    class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <list>
            <bean id="tdConverter" class="com.kion.lsg.service.tecdoc.converter.SimpleTecdocObjectConverter" />
        </list>
    </property>
</bean>

不幸的是,这似乎不起作用。我得到与上面完全相同的例外。 Spring抽象似乎忽略了上面定义的conversionService并采用了一些默认的。

解决方案: 的 Lotsa感谢Akshay Singhal

不使用转换器,而是可以在SpEL表达式中使用静态密钥生成器:

@Cacheable(value = "tecdocData", key = "T(com.kion.lsg.service.tecdoc.converter.SimpleTecDocObjectCollectionKeyGenerator).generateKey(#stos)")
List<TecDocData> getTecDocData(Collection<SimpleTecDocObject> stos);

例如KeyGenerator定义如下:

public class SimpleTecDocObjectCollectionKeyGenerator {
    public static String generateKey(final Collection<SimpleTecDocObject> stos){
        List<String> tecdocIdsList = new ArrayList<>();

        for (SimpleTecDocObject sto : stos){
            tecdocIdsList.add(sto.toString());
        }

        Collections.sort(tecdocIdsList);

        return tecdocIdsList.toString();
    }
}

2 个答案:

答案 0 :(得分:0)

我认为转换器被忽略,因为你有多个上下文被加载,比如由ContextLoaderListener加载的ROOT上下文和为servlet加载的web应用程序上下文。并且您的配置将被稍后加载的上下文覆盖。

然而,这只是一个猜测,并且无可否认,因为我真的不知道你的应用程序是如何被引导的。

无论如何,查看您的问题,自定义转换器可能不是最简单的解决方案。缓存Key的整个要点是从参数中创建唯一标识符,以便可以从缓存中提供具有相同参数的后续请求。

因为"'TCDD:stos::'.concat(#stos)"表达式为key,所以Spring不得不使用转换器来计算#stos的字符串值。一个更简单的解决方案是简单地将表达式更改为"'TCDD:stos::'.concat(#stos.toString())",并依靠Collections toString()方法委托您的类的toString()方法。 - 从而在参数中创建一个字符串,对于每组参数都是唯一的。

如果您不想实现toString(),另一个选择是创建一个Class,其唯一责任是将Collection<SimpleTecDocObject> stos转换为String。在此实用程序类中创建一个静态方法,在此方法中使用您想要的任何键创建逻辑,并从spel表达式调用它。像T(SimpleTecDocObjectCollectionKeyGenerator).generateKey(stos)这样的东西。使用此方法,您甚至可以将TCDD:stos::抽象到密钥生成器实用程序类中。

希望这有帮助。

答案 1 :(得分:0)

谢谢,“#stos.toString()”运行正常。不幸的是,我无法使用KeyGenerator替代方案。

这个不起作用:

"@Cacheable(value = "tecdocData", key =  T(SimpleTecDocObjectCollectionKeyGenerator).generateKey(stos)")" 

这就是生成器的样子:

public class SimpleTecDocObjectCollectionKeyGenerator {
public static String generateKey(final Collection<SimpleTecDocObject> stos){
    List<String> tecdocIdsList = new ArrayList<>();

    for (SimpleTecDocObject sto : stos){
        tecdocIdsList.add(sto.toString());
    }

    Collections.sort(tecdocIdsList);

    return tecdocIdsList.toString();
}

}

似乎永远不会被召唤。