在使用异步 couchdatabase java驱动程序时,我遇到了 Play框架默认缓存( EHCache )的问题。在热重载时播放崩溃错误:
Error in custom provider, play.api.cache.EhCacheExistsException: An EhCache instance with name 'play' already exists.
我发现这不仅适用于 couchdatabase 驱动程序,还适用于其他一些情况,例如https://groups.google.com/forum/#!topic/pac4j-dev/2_EUOCrov7M。
答案 0 :(得分:2)
我找到了一个解决方案 - 在stop hook上强制缓存关闭。它可以在项目中的一个现有模块中完成,如:
lifecycle.addStopHook(() -> {
...
CacheManager.getInstance().shutdown();
...
});
特别"修复"也可以创建模块:
package fixes;
import java.util.concurrent.CompletableFuture;
import javax.inject.Inject;
import javax.inject.Singleton;
import com.google.inject.AbstractModule;
import net.sf.ehcache.CacheManager;
import play.Logger;
import play.Logger.ALogger;
import play.inject.ApplicationLifecycle;
/**
* Fix for the hot reloading cache issue.
* "Error in custom provider, play.api.cache.EhCacheExistsException: An EhCache instance with name 'play' already exists."
*
*/
public class CacheFix extends AbstractModule{
@Override
protected void configure() {
bind(CacheFixInstance.class).asEagerSingleton();
}
}
/**
* Only stop hook
*/
@Singleton
class CacheFixInstance {
private static ALogger logger = Logger.of(CacheFixInstance.class);
@Inject
public CacheFixInstance(ApplicationLifecycle lifecycle) {
lifecycle.addStopHook(() -> {
// Force cache to stop.
CacheManager.getInstance().shutdown();
logger.debug("Cache has been shutdown");
// Nothing to return.
return CompletableFuture.completedFuture(null);
});
}
}
在application.conf
:
enabled += fixes.CacheFix
答案 1 :(得分:1)
如果有人想看一个快速的copypaste修复,这里是我从Andriy Kuba的答案翻译的scala版本
package utils
import javax.inject.{Inject, Singleton}
import com.google.inject.AbstractModule
import net.sf.ehcache.CacheManager
import play.api.Logger
import play.api.inject.ApplicationLifecycle
import scala.concurrent.{ExecutionContext, Future}
/**
* Fix for the hot reloading cache issue.
* "Error in custom provider, play.api.cache.EhCacheExistsException: An EhCache instance with name 'play' already exists."
*
*/
class CacheHotReloadFix extends AbstractModule {
override protected def configure(): Unit = {
bind(classOf[CacheHotReloadFixInstance]).asEagerSingleton()
}
}
@Singleton
class CacheHotReloadFixInstance @Inject() (lifecycle: ApplicationLifecycle, implicit val executionContext: ExecutionContext) {
private val logger = Logger(this.getClass)
lifecycle.addStopHook { () =>
logger.debug("Forching ehcach to stop before play reloads")
// Force cache to stop.
Future(CacheManager.getInstance().shutdown())
}
}
答案 2 :(得分:0)
如果您在运行测试用例时遇到问题(将有多个Applicaiton
s),您可以使用SyncCacheApi
和AsyncCacheApi
的虚拟实现并覆盖绑定通过Application
provideApplication()
时
@Override
protected Application provideApplication() {
Application application = new GuiceApplicationBuilder().configure(testConfig)
.disable(EhCacheModule.class)
.overrides(bind(SyncCacheApi.class).to(FakeSyncCacheApi.class))
.bindings(new MyModule())
.in(new File(".")).build();
return application;
}
,示例FakeSyncCacheApi
就像
@Singleton
public class FakeSyncCacheApi implements SyncCacheApi {
private LRUMap cache = new LRUMap();
@Override
public <T> T get(String key) {
return (T) cache.get(key);
}
@Override
public <T> T getOrElseUpdate(String key, Callable<T> block, int expiration) {
return getOrElseUpdate(key, block);
}
@Override
public <T> T getOrElseUpdate(String key, Callable<T> block) {
T value = (T) cache.get(key);
if (value == null) {
try {
value = block.call();
} catch (Exception e) {
}
cache.put(key, value);
}
return value;
}
@Override
public void set(String key, Object value, int expiration) {
cache.put(key, value);
}
@Override
public void set(String key, Object value) {
cache.put(key, value);
}
@Override
public void remove(String key) {
cache.remove(key);
}
}
这里的想法是禁用EhCache
模块并拥有我们自己的虚拟实现。
答案 3 :(得分:0)
这是我的基于Andriy Kubas版本的Scala版本,适用于Play 2.5.6。请记住,Play 2.5.3有一个bug,所以stophooks不起作用。
package modules
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import com.google.inject._
import net.sf.ehcache.CacheManager;
import play.api.Logger
import play.api.inject.ApplicationLifecycle
import play.api.{ Configuration, Environment, Mode }
import play.api.inject.{ Module => PlayModule }
class CacheModule extends PlayModule {
def bindings(environment: Environment, configuration: Configuration) = Seq(
bind[CacheFixInstance].toSelf.eagerly
)
}
@Singleton
class CacheFixInstance @Inject()(lifecycle: ApplicationLifecycle) {
val logger = Logger(this.getClass)
lifecycle.addStopHook { () =>
logger.info("CacheInstance stopped")
Future.successful(CacheManager.getInstance().shutdown())
}
}
和当然play.modules.enabled += "modules.CacheModule"
答案 4 :(得分:0)
对我来说,添加一个关闭钩子是行不通的(也许速度不够快,我不知道)。相反,我只是在每个测试套件结束时关闭CacheManager并按顺序运行它们:
override def afterAll(): Unit = {
CacheManager.getInstance().shutdown()
}
答案 5 :(得分:0)
基于Andriy Kuba答案,我针对Scala和Play 2.6问题的解决方案。
首先,需要在javaCore
中包含build.sbt
模块才能导入play.api.inject.ApplicationLifecycle
libraryDependencies += javaCore
然后创建一个新的类CacheFixInstance.scala
:
package util
import javax.inject.Inject
import net.sf.ehcache.CacheManager
import play.api.Logger
import play.api.inject.ApplicationLifecycle
import scala.concurrent.Future
class CacheFixInstance @Inject()(lifecycle: ApplicationLifecycle) {
private val logger = Logger(getClass)
lifecycle.addStopHook { () =>
logger.info("CacheInstance stopped")
Future.successful(CacheManager.getInstance().shutdown())
}
logger.info(s"Hot reload EHCache fix initialized.")
}
,然后将其添加到您的模块配置中,或者对@singleton
类使用CacheFixInstance
批注:
bind(classOf[CacheFixInstance]).asEagerSingleton()