0

我试图在 @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 类型转换” 7. 验证、数据绑定和类型转换中所述,这是非常直接地完成的

转换器类:

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

}

弹簧配置:

<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并采用了一些默认值。

解决方案: 感谢 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();
    }
}
4

2 回答 2

0

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

然而,这只是一个猜测,诚然不是一个非常有见识的猜测,因为我真的不知道您的应用程序是如何引导的。

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

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

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

希望这可以帮助。

于 2013-08-06T12:16:19.777 回答
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();
}

}

似乎它永远不会被调用。

于 2013-08-08T13:31:45.473 回答