我想在我的Wicket应用程序中关闭序列化并将所有页面/会话信息存储在RAM中。我的应用程序用户数量非常少(一般为1);我不需要集群部署;我需要在请求之间缓存一些不可序列化的数据。
有没有办法让Wicket不会自动尝试序列化我的页面/会话?我尝试了在https://cwiki.apache.org/confluence/display/WICKET/Page+Storage使用HttpSessionDataStore的建议,但它没有效果。我仍然得到这样的堆栈跟踪:
SEVERE: Error serializing object class com.prosc.safetynet.Administer [object=[Page class = com.prosc.safetynet.Administer, id = 0, render count = 1]]
org.apache.wicket.util.io.SerializableChecker$WicketNotSerializableException: Unable to serialize class: com.prosc.safetynet.SafetyNetSession$1
Field hierarchy is:
0 [class=com.prosc.safetynet.Administer, path=0]
java.lang.Object org.apache.wicket.Component.data [class=org.apache.wicket.model.CompoundPropertyModel]
private java.lang.Object org.apache.wicket.model.CompoundPropertyModel.target [class=com.prosc.safetynet.SafetyNetSession$2]
final com.prosc.safetynet.SafetyNetSession com.prosc.safetynet.SafetyNetSession$2.this$0 [class=com.prosc.safetynet.SafetyNetSession]
private java.lang.Object com.prosc.safetynet.SafetyNetSession.tryAndSerializeMeBitch [class=com.prosc.safetynet.SafetyNetSession$1] <----- field that is not serializable
at org.apache.wicket.util.io.SerializableChecker.internalCheck(SerializableChecker.java:395)
at org.apache.wicket.util.io.SerializableChecker.check(SerializableChecker.java:374)
at org.apache.wicket.util.io.SerializableChecker.checkFields(SerializableChecker.java:655)
at org.apache.wicket.util.io.SerializableChecker.internalCheck(SerializableChecker.java:578)
at org.apache.wicket.util.io.SerializableChecker.check(SerializableChecker.java:374)
at org.apache.wicket.util.io.SerializableChecker.checkFields(SerializableChecker.java:655)
at org.apache.wicket.util.io.SerializableChecker.internalCheck(SerializableChecker.java:578)
at org.apache.wicket.util.io.SerializableChecker.check(SerializableChecker.java:374)
at org.apache.wicket.util.io.SerializableChecker.checkFields(SerializableChecker.java:655)
at org.apache.wicket.util.io.SerializableChecker.internalCheck(SerializableChecker.java:578)
at org.apache.wicket.util.io.SerializableChecker.check(SerializableChecker.java:374)
at org.apache.wicket.util.io.SerializableChecker.checkFields(SerializableChecker.java:655)
at org.apache.wicket.util.io.SerializableChecker.internalCheck(SerializableChecker.java:578)
at org.apache.wicket.util.io.SerializableChecker.check(SerializableChecker.java:374)
at org.apache.wicket.util.io.SerializableChecker.writeObjectOverride(SerializableChecker.java:724)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:326)
at org.apache.wicket.serialize.java.JavaSerializer$CheckerObjectOutputStream.writeObjectOverride(JavaSerializer.java:258)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:326)
at org.apache.wicket.serialize.java.JavaSerializer.serialize(JavaSerializer.java:77)
at org.apache.wicket.pageStore.DefaultPageStore.serializePage(DefaultPageStore.java:368)
at org.apache.wicket.pageStore.DefaultPageStore.storePage(DefaultPageStore.java:146)
at org.apache.wicket.page.PageStoreManager$PersistentRequestAdapter.storeTouchedPages(PageStoreManager.java:383)
at org.apache.wicket.page.RequestAdapter.commitRequest(RequestAdapter.java:171)
at org.apache.wicket.page.AbstractPageManager.commitRequest(AbstractPageManager.java:94)
at org.apache.wicket.page.PageManagerDecorator.commitRequest(PageManagerDecorator.java:68)
at org.apache.wicket.page.PageAccessSynchronizer$2.commitRequest(PageAccessSynchronizer.java:281)
at org.apache.wicket.Application$2.onDetach(Application.java:1598)
at org.apache.wicket.request.cycle.RequestCycleListenerCollection$3.notify(RequestCycleListenerCollection.java:99)
at org.apache.wicket.request.cycle.RequestCycleListenerCollection$3.notify(RequestCycleListenerCollection.java:97)
at org.apache.wicket.util.listener.ListenerCollection$1.notify(ListenerCollection.java:119)
at org.apache.wicket.util.listener.ListenerCollection.reversedNotify(ListenerCollection.java:143)
at org.apache.wicket.util.listener.ListenerCollection.reversedNotifyIgnoringExceptions(ListenerCollection.java:113)
at org.apache.wicket.request.cycle.RequestCycleListenerCollection.onDetach(RequestCycleListenerCollection.java:95)
at org.apache.wicket.request.cycle.RequestCycle.onDetach(RequestCycle.java:603)
at org.apache.wicket.request.cycle.RequestCycle.detach(RequestCycle.java:542)
at org.apache.wicket.request.cycle.RequestCycle.processRequestAndDetach(RequestCycle.java:287)
at org.apache.wicket.protocol.http.WicketFilter.processRequest(WicketFilter.java:188)
at org.apache.wicket.protocol.http.WicketFilter.doFilter(WicketFilter.java:244)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:215)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:188)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:210)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:174)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:117)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:108)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:151)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:870)
at org.apache.coyote.http11.Http11BaseProtocol$Http11ConnectionHandler.processConnection(Http11BaseProtocol.java:665)
at org.apache.tomcat.util.net.PoolTcpEndpoint.processSocket(PoolTcpEndpoint.java:528)
at org.apache.tomcat.util.net.LeaderFollowerWorkerThread.runIt(LeaderFollowerWorkerThread.java:81)
at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:685)
at java.lang.Thread.run(Thread.java:680)
答案 0 :(得分:5)
您可以实现自己的IPageStore,将页面保留在内存中。
答案 1 :(得分:1)
我不能对Wicket的任何特定内容发表评论,但一般来说,Http Session
的全部意义是在请求之间存储Serializable
状态(并且在集群环境中,允许该状态为复制到群集中的多个节点,以便在节点发生故障时提供冗余)。将 not Serializable
中的内容放入其中通常被视为错误,如堆栈跟踪所示。如果有任何类型的配置选项可以改变这一点,我会感到有些惊讶(尽管可能存在;正如我所说,我无法真正评论Wicket方面的事情)。
一个简单的替代方案,如果您不需要真正的持久性,并且数据不是特别大/复杂,那么只需使用页面上的隐藏表单字段来跟踪相关状态。
但是,如果您想要的是内存缓存,为什么不实现自己的?这很简单:
public class SessionCache {
private static final Map<String, Map<String, Object>> CACHE = Collections.synchronizedMap(new HashMap<String, Map<String, Object>>());
public static Object getAttribute(String sessionId, String attribName) {
Map<String, Object> attribs = CACHE.get(sessionId);
if (attribs != null) {
synchronized(attribs) {
return attribs.get(attribName);
}
}
return null;
}
public static void setAttribute(String sessionId, String attribName, Object attribValue) {
Map<String, Object> attribs = CACHE.get(sessionId);
if (attribs == null) {
attribs = new HashMap<String, Object>();
CACHE.put(sessionId, attribs);
}
synchronized(attribs) {
attribs.put(attribName, attribValue);
}
}
public static void destroySession(String sessionId) {
CACHE.remove(sessionId);
}
public static void createSession(String sessionId, boolean force) {
if (force || ! CACHE.containsKey(sessionId)) {
CACHE.put(sessionId, new HashMap<String, Object>());
}
}
}
请注意,您需要将其挂钩到Wicket的会话生命周期中,以便旧会话在到期时被删除。否则你手上会有逐渐的内存泄漏。从文档中看,您可以使用HttpSessionStore类上的registerUnboundListener()
来完成此操作。
答案 2 :(得分:1)
根据svenmeier的回答,这是我提出的解决方案。我确信这不是100%正确,但它在我的测试中运行良好:
package com.prosc.wicket;
import org.apache.wicket.Application;
import org.apache.wicket.DefaultPageManagerProvider;
import org.apache.wicket.page.IManageablePage;
import org.apache.wicket.page.IPageManagerContext;
import org.apache.wicket.pageStore.IDataStore;
import org.apache.wicket.pageStore.IPageStore;
import org.apache.wicket.pageStore.memory.HttpSessionDataStore;
import org.apache.wicket.pageStore.memory.PageNumberEvictionStrategy;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
/**
* This class disables Wicket's serialization behavior, while still retaining session and page data in memory (so back button will work).
* This will run out of memory under heavy load; but it's very convenient for low volume web applications.
* To disable serialization in your application, call this code:
* <pre>
* setPageManagerProvider( new NoSerializePageManagerProvider( this, getPageManagerContext() ) );
* </pre>
*/
public class NoSerializePageManagerProvider extends DefaultPageManagerProvider {
private IPageManagerContext pageManagerContext;
public NoSerializePageManagerProvider( Application application, IPageManagerContext pageManagerContext ) {
super( application );
this.pageManagerContext = pageManagerContext;
}
@Override
protected IDataStore newDataStore() {
return new HttpSessionDataStore( pageManagerContext, new PageNumberEvictionStrategy( 20 ) );
}
@Override
protected IPageStore newPageStore( IDataStore dataStore ) {
return new IPageStore() {
Map<String,Map<Integer,IManageablePage>> cache = new HashMap<String, Map<Integer, IManageablePage>>();
public void destroy() {
cache = null;
}
public IManageablePage getPage( String sessionId, int pageId ) {
Map<Integer, IManageablePage> sessionCache = getSessionCache( sessionId, false );
IManageablePage page = sessionCache.get( pageId );
if( page == null ) {
throw new IllegalArgumentException( "Found this session, but there is no page with id " + pageId );
}
return page;
}
public void removePage( String sessionId, int pageId ) {
getSessionCache( sessionId, false ).remove( pageId );
}
public void storePage( String sessionId, IManageablePage page ) {
getSessionCache( sessionId, true ).put( page.getPageId(), page );
}
public void unbind( String sessionId ) {
cache.remove( sessionId );
}
public Serializable prepareForSerialization( String sessionId, Object page ) {
return null;
}
public Object restoreAfterSerialization( Serializable serializable ) {
return null;
}
public IManageablePage convertToPage( Object page ) {
return (IManageablePage)page;
}
private Map<Integer, IManageablePage> getSessionCache( String sessionId, boolean create ) {
Map<Integer, IManageablePage> sessionCache = cache.get( sessionId );
if( sessionCache == null ) {
if( create ) {
sessionCache = new HashMap<Integer, IManageablePage>();
cache.put( sessionId, sessionCache );
} else {
throw new IllegalArgumentException( "There are no pages stored for session id " + sessionId );
}
}
return sessionCache;
}
};
}
}
答案 3 :(得分:0)
我想改善Jesse的答案。 下面是一个线程安全的IPageStore实现,内部&#34;最近最少插入&#34;缓存(每个会话最多保留5个最近访问的有状态页面):
public class CustomPageStore implements IPageStore {
private static final Logger logger = LoggerFactory.getLogger(CustomPageStore.class);
private static final int MEDIAN_OF_NUMBER_OF_SESSIONS = 6000;
private ConcurrentMap<String, CustomLinkedHashMap<Integer, IManageablePage>> cache = new ConcurrentHashMap<>(MEDIAN_OF_NUMBER_OF_SESSIONS);
@Override
public void destroy() {
cache.clear();
}
@Override
public IManageablePage getPage(final String sessionId, int pageId) {
final Map<Integer, IManageablePage> sessionCache = getSessionCache(sessionId);
final RequestCycle requestCycle = RequestCycle.get();
if (sessionCache == null) {
logger.warn("Missing cache. SessionId: {}, pageId: {}, URL: {}", sessionId, pageId, requestCycle == null ? StringUtils.EMPTY : requestCycle.getRequest().getUrl());
return null;
}
final IManageablePage page;
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (sessionCache) {
page = sessionCache.get(pageId);
}
if (page == null && logger.isDebugEnabled()) {
logger.debug("Missed page. SessionId: {}, pageId: {}, URL: {}", sessionId, pageId, requestCycle == null ? StringUtils.EMPTY : requestCycle.getRequest().getUrl());
}
return page;
}
@Override
public void removePage(final String sessionId, int pageId) {
final Map<Integer, IManageablePage> sessionCache = getSessionCache(sessionId);
if (sessionCache != null) {
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (sessionCache) {
sessionCache.remove(pageId);
}
}
}
@Override
public void storePage(final String sessionId, IManageablePage page) {
final LinkedHashMap<Integer, IManageablePage> sessionCache = getOrCreateSessionCache(sessionId);
final int pageId = page.getPageId();
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (sessionCache) {
if (sessionCache.containsKey(pageId)) {
// do this to change insertion order and update least inserted entry
sessionCache.remove(pageId);
sessionCache.put(pageId, page);
} else {
sessionCache.put(pageId, page);
}
}
}
@Override
public void unbind(final String sessionId) {
cache.remove(sessionId);
}
@Override
public Serializable prepareForSerialization(String sessionId, Object page) {
return null;
}
@Override
public Object restoreAfterSerialization(Serializable serializable) {
return null;
}
@Override
public IManageablePage convertToPage(final Object page) {
return (IManageablePage) page;
}
@Nullable
private Map<Integer, IManageablePage> getSessionCache(final String sessionId) {
return cache.get(sessionId);
}
@Nonnull
private CustomLinkedHashMap<Integer, IManageablePage> getOrCreateSessionCache(final String sessionId) {
return cache.computeIfAbsent(sessionId, s -> new CustomLinkedHashMap<>());
}
/** Mimics "least recently inserted" cache */
private static class CustomLinkedHashMap<K, V> extends LinkedHashMap<K, V> {
/** use this parameter to control memory consumption and frequency of appearance of PageExpiredException */
private static final int MAX_PAGES_PER_SESSION = 5;
@Override
protected boolean removeEldestEntry(final Map.Entry<K, V> eldest) {
return size() > MAX_PAGES_PER_SESSION;
}
}
}
答案 4 :(得分:0)
我想改善约翰尼的答案,谁要改善杰西的答案:)
IPageManagerProvider
,而不仅仅是IPageStore
store.unbind(sessionId)
释放内存public class NoSerializationButCachingPageManagerProvider implements IPageManagerProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(NoSerializationButCachingPageManagerProvider.class);
private final Application application;
public NoSerializationButCachingPageManagerProvider(final Application application) {
this.application = Args.notNull(application, "application");
LOGGER.info("Pages don't get serialized, but in-memory cached.");
}
@Override
public IPageManager get(IPageManagerContext pageManagerContext) {
final IPageStore store = new NoSerializationButCachingPageStore();
final IPageManager manager = new PageStoreManager(application.getName(), store, pageManagerContext);
/*
* session unbind must call store.unbind() to free memory (prevents memory leak)
*/
application.getSessionStore().registerUnboundListener((String sessionId) -> store.unbind(sessionId));
return manager;
}
}
class NoSerializationButCachingPageStore implements IPageStore {
private static final Logger LOGGER = LoggerFactory.getLogger(NoSerializationButCachingPageStore.class);
private static final int MEDIAN_OF_NUMBER_OF_SESSIONS = 100;
private final ConcurrentMap<String, CustomLinkedHashMap<Integer, IManageablePage>> cache = new ConcurrentHashMap<>(MEDIAN_OF_NUMBER_OF_SESSIONS);
@Override
public void destroy() {
cache.clear();
}
@Override
public IManageablePage getPage(final String sessionId, final int pageId) {
LOGGER.info("getPage. SessionId: {}, pageId: {}", sessionId, pageId);
final Map<Integer, IManageablePage> sessionCache = getSessionCache(sessionId);
final RequestCycle requestCycle = RequestCycle.get();
if (sessionCache == null) {
LOGGER.warn("Missing cache. SessionId: {}, pageId: {}, URL: {}", sessionId, pageId, requestCycle == null ? "" : requestCycle.getRequest().getUrl());
return null;
}
IManageablePage page;
// noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (sessionCache) {
page = sessionCache.get(pageId);
}
if (page == null && LOGGER.isDebugEnabled()) {
LOGGER.debug("Missed page. SessionId: {}, pageId: {}, URL: {}", sessionId, pageId, requestCycle == null ? "" : requestCycle.getRequest().getUrl());
}
return page;
}
@Override
public void removePage(final String sessionId, final int pageId) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("removePage. SessionId: {}, pageId: {}", sessionId, pageId);
}
final Map<Integer, IManageablePage> sessionCache = getSessionCache(sessionId);
if (sessionCache != null) {
// noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (sessionCache) {
sessionCache.remove(pageId);
}
}
}
@Override
public void storePage(final String sessionId, final IManageablePage page) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("storePage. SessionId: {}, pageId: {}, cache-size: {}", sessionId, page.getPageId(), cache.size());
}
final LinkedHashMap<Integer, IManageablePage> sessionCache = getOrCreateSessionCache(sessionId);
final int pageId = page.getPageId();
// noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (sessionCache) {
if (sessionCache.containsKey(pageId)) {
// do this to change insertion order and update least inserted entry
sessionCache.remove(pageId);
sessionCache.put(pageId, page);
} else {
sessionCache.put(pageId, page);
}
}
}
/**
* @param sessionId
*/
@Override
public void unbind(final String sessionId) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("unbind/cache-remove. SessionId: {}", sessionId);
}
cache.remove(sessionId);
}
@Override
public Serializable prepareForSerialization(final String sessionId, final Serializable page) {
return null;
}
@Override
public Object restoreAfterSerialization(final Serializable serializable) {
return null;
}
@Override
public IManageablePage convertToPage(final Object page) {
return (IManageablePage) page;
}
private Map<Integer, IManageablePage> getSessionCache(final String sessionId) {
return cache.get(sessionId);
}
private CustomLinkedHashMap<Integer, IManageablePage> getOrCreateSessionCache(final String sessionId) {
return cache.computeIfAbsent(sessionId, (final String s) -> new CustomLinkedHashMap<>());
}
/**
* Mimics "least recently inserted" cache
*/
private static class CustomLinkedHashMap<K, V> extends LinkedHashMap<K, V> {
private static final long serialVersionUID = 1L;
/**
* use this parameter to control memory consumption and frequency of appearance of PageExpiredException
*/
private static final int MAX_PAGES_PER_SESSION = 3;
@Override
protected boolean removeEldestEntry(final Map.Entry<K, V> eldest) {
return size() > MAX_PAGES_PER_SESSION;
}
}
}