spring-boot-devtools causing ClassCastException while getting from cache.
Asked Answered
W

4

10

I am facing issue while getting value from cache.

java.lang.RuntimeException: java.lang.ClassCastException: com.mycom.admin.domain.User cannot be cast to com.mycom.admin.domain.User

Cache Configuration

@Configuration
@EnableCaching
@AutoConfigureAfter(value = { MetricsConfiguration.class, DatabaseConfiguration.class })
@Profile("!" + Constants.SPRING_PROFILE_FAST)
public class MemcachedCacheConfiguration extends CachingConfigurerSupport {

    private final Logger log = LoggerFactory.getLogger(MemcachedCacheConfiguration.class);

    @Override
    @Bean
    public CacheManager cacheManager() {
        ExtendedSSMCacheManager cacheManager = new ExtendedSSMCacheManager();
        try {
            List<SSMCache> list = new ArrayList<>();
            list.add(new SSMCache(defaultCache("apiCache"), 86400, false));
            cacheManager.setCaches(list);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cacheManager;
    }


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

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

    private Cache defaultCache(String cacheName) throws Exception {
        CacheFactory cacheFactory = new CacheFactory();
        cacheFactory.setCacheName(cacheName);
        cacheFactory.setCacheClientFactory(new MemcacheClientFactoryImpl());
        String serverHost = "127.0.0.1:11211";
        cacheFactory.setAddressProvider(new DefaultAddressProvider(serverHost));
        cacheFactory.setConfiguration(cacheConfiguration());
        return cacheFactory.getObject();
    }

    @Bean
    public CacheConfiguration cacheConfiguration() {
        CacheConfiguration cacheConfiguration = new CacheConfiguration();
        cacheConfiguration.setConsistentHashing(true);
        return cacheConfiguration;
    }

}

And annotated with

@Cacheable(value = "apiCache#86400", key = "'User-'.concat(#login)")

I am using com.google.code.simple-spring-memcached 3.5.0

Value is getting cached but while getting application throws class cast error. What would be the possible issues.

Full stack trace

Wieland answered 3/1, 2016 at 15:4 Comment(7)
Do you have the same class on your classpath twice? Or do you have it loaded from multiple classloaders (or example in a WebApp environment). The usual cause for a class cannot be cast to itself issues is that the classes are loaded from different places...Casia
At a guess, it looks like some kind of ClassLoader issue. Looks like you've got two different classloaders which have loaded the same class.Scorekeeper
@Scorekeeper i use spring boot + devtools. I read some where devtools keeps one class loader for static jars and one for application code. Would this cause issue?Wieland
@BoristheSpider Yes that's it, i removed spring-boot-devtools and its working. Thanks GuysWieland
Should I raise an issue with spring-boot. as per the their guidelines they are watching this tag. Also I don't think this is fixable, please suggest.Wieland
I just did. This isn't an issue we're not aware of so it does not need a new issue.Igenia
See below I posted an answer, please see if it might help you in your caseLoyal
I
14

This is a known limitation of Devtools. When the cache entry is deserialized, the object is not attached to the proper classloader.

There are various ways you can fix this issue:

  1. Disable cache when you're running your application in development
  2. Use a different cache manager (if you're using Spring Boot 1.3, you could force a simple cache manager using the spring.cache.type property in application-dev.properties and enable the dev profile in your IDE)
  3. Configure memcached (and things that are cached) to run in the application classloader. I wouldn't recommend that option since the two first above are much easier to implement
Igenia answered 3/1, 2016 at 17:9 Comment(0)
P
1

Well I got the same error, but the caching was not the reason. Actually I was using caching, but the commenting the caching out didn't help.

Based on the hints here and there I just introduced additional serialization/derialization of my object. It's definatelly the best way (the performance issue), but it's working.

So, just for the others I changed my code from:

@Cacheable("tests")
public MyDTO loadData(String testID) {
    // add file extension to match XML file
    return (MyDTO) this.xmlMarshaller.loadXML(String.format("%s/%s.xml", xmlPath, testID));
}

to:

@Cacheable("tests")
public MyDTO loadData(String testID) {
    // add file extension to match XML file
    Object dtoObject = this.xmlMarshaller.loadXML(String.format("%s/%s.xml", xmlPath, testID));
    byte[] data = serializeDTO(dtoObject);
    MyDTO dto = deserializeDTO(data);
    return dto;
}

private MyDTO deserializeDTO(byte[] data) {
    MyDTO dto = null;
    try {
        ByteArrayInputStream fileIn = new ByteArrayInputStream(data);
        ObjectInputStream in = new ConfigurableObjectInputStream(fileIn,
                Thread.currentThread().getContextClassLoader());
        dto = (MyDTO) in.readObject();
        in.close();
        fileIn.close();
    } catch (Exception e) {
        String msg = "Deserialization of marshalled XML failed!";
        LOG.error(msg, e);
        throw new RuntimeException(msg, e);
    }
    return dto;
}

private byte[] serializeDTO(Object dtoObject) {
    byte[] result = null;
    try {
        ByteArrayOutputStream data = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(data);
        out.writeObject(dtoObject);
        out.close();
        result = data.toByteArray();
        data.close();
    } catch (IOException e) {
        String msg = "Serialization of marshalled XML failed!";
        LOG.error(msg, e);
        throw new RuntimeException(msg, e);
    }

    return result;
}

Note: this is not any sofisticated solution, but just the hint of usage ConfigurableObjectInputStream class.

Profession answered 20/1, 2017 at 14:31 Comment(0)
H
0

I was running into this same issue when running a project in eclipse with the STS plugin enabled. Even though I removed the devtools dependency completely from the project. It was still enabled in eclipse. To fix this, I had to disable devtools.

enter image description here

Huygens answered 21/12, 2018 at 21:35 Comment(0)
L
0

The problem is that when you are using the devtools the ClassLoader get set to a different one org.springframework.boot.devtools.restart.classloader.RestartClassLoader then the Cache Provider / Framework you are using.

In case you are using JCache or EhCache you simple initialize the Cache Manager by making sure that you are using the same class loader.

More information and background can be found here

import javax.cache.CacheManager;
import javax.cache.Caching;
import javax.cache.spi.CachingProvider;

@Configuration
@EnableCaching
@Slf4j
public class CachingConfiguration {

    @Bean
    @Primary
    public CacheManager myCacheManager() {
     
    // The trick is to use "Thread.currentThread().getContextClassLoader()"
    // which overwrites the default class loader set internally by the cache provider
    // with this you ensure both Class loaders are the same when you are using the devtools 

        CachingProvider provider = Caching.getCachingProvider();
        CacheManager cacheManager = provider.getCacheManager(provider.getDefaultURI(), Thread.currentThread().getContextClassLoader());
        return cacheManager;
    }


    // Optional - in the same class or separate class you can verify the 
    // reload which gets initiated by the devtools. So you can check if the 
    // Class loader of your cache Manager and the one currently used by the
    // Spring Framework is the same
    
    @Autowired
    @Lazy
    CacheManager cacheManager;

    @EventListener
    public void handleContextRefreshEvent(ContextRefreshedEvent ctxEvt) {           
        ClassLoader loader1 = Thread.currentThread().getContextClassLoader();
        ClassLoader loader2 = cacheManager.getClassLoader();
        log.debug("{} = {}", loader1, loader2);
    }
}

The expected output should then be

org.springframework.boot.devtools.restart.classloader.RestartClassLoader@3478e262 = org.springframework.boot.devtools.restart.classloader.RestartClassLoader@3478e262
Loyal answered 1/10, 2023 at 13:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.