2016-01-03 40 views
6

Sono di fronte a problemi durante l'acquisizione di valore dalla cache.spring-boot-devtools che causa ClassCastException durante il recupero dalla cache.

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

di configurazione della cache

@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; 
    } 

} 

e annotato con

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

Sto usando com.google.code.simple-primavera-memcached 3.5.0

valore è sempre memorizzato nella cache ma durante l'applicazione genera un errore di cast di classe. Quali sarebbero i possibili problemi.

Full stack trace

+2

Avete lo stesso 'class' sul classpath due volte? Oppure lo hai caricato da più classloader (o da un esempio in un ambiente WebApp). La solita causa per cui una classe non può essere lanciata su se stessa è che le classi sono caricate da luoghi diversi ... –

+1

Ad una ipotesi, sembra un qualche tipo di problema ClassLoader. Sembra che tu abbia due diversi classloader che hanno caricato la stessa classe. – sisyphus

+0

@sisyphus utilizzo spring boot + devtools. Ho letto alcuni in cui devtools mantiene un programma di caricamento classi per i barattoli statici e uno per il codice dell'applicazione. Questo potrebbe causare problemi? – titogeo

risposta

7

Questo è a known limitation of Devtools. Quando la voce della cache è deserializzata, l'oggetto non è collegato al classloader corretto.

Ci sono vari modi per risolvere questo problema:

  1. Disabilita cache quando si esegue l'applicazione in fase di sviluppo
  2. utilizzare un gestore di cache diversa (se si sta utilizzando Primavera Boot 1.3, è potrebbe forzare un gestore di cache utilizzando la proprietà spring.cache.type in application-dev.properties e abilitare il profilo dev nel proprio IDE)
  3. Configurare memcached (e cose che sono memorizzate nella cache) su run in the application classloader. Non consiglierei questa opzione poiché i primi due sopra sono molto più facili da implementare
1

Bene, ho ottenuto lo stesso errore, ma il caching non era il motivo. In realtà stavo usando il caching, ma il commento sul caching non mi è stato d'aiuto.

Sulla base dei suggerimenti qui e là ho appena introdotto ulteriore serializzazione/derializzazione del mio oggetto. È sicuramente il modo migliore (il problema delle prestazioni), ma funziona.

Così, giusto per gli altri ho cambiato il mio codice da:

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

a:

@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; 
} 

Nota: questa non è una soluzione sofisticata, ma solo l'accenno di classe di utilizzo ConfigurableObjectInputStream .