How can I disable serialization in Wicket 1.5?

前端 未结 5 429
一生所求
一生所求 2021-01-03 00:50

I want to turn off serialization in my Wicket app and store all page/session information in RAM. My application has a very small number of users (generally 1); I do not need

相关标签:
5条回答
  • 2021-01-03 00:51

    I can't comment about anything specific to Wicket, but speaking generally the entire point of an Http Session is to store Serializable state between requests (and in clustered environments, to allow that state to be replicated to multiple nodes in the cluster to provide redundancy in the event of a node failure). Putting something that is not Serializable into it is generally considered an error, as shown by your stack trace. I'd be somewhat surprised if there is any sort of configuration option that would change this (though perhaps there is; as I said I can't really comment on the Wicket side of things).

    A simple alternative, if you do not require true persistence and if the data is not exceptionally large/complex, is to just use hidden form fields on your page to keep track of the relevant state.

    But if what you want is an in-memory cache, why not implement your own? It's simple enough to do:

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

    Note that you'll want to hook that into Wicket's session lifecycle so that old sessions are removed when they expire. Otherwise you'll have a gradual memory leak on your hands. From the docs it looks like you can accomplish this using registerUnboundListener() on the HttpSessionStore class.

    0 讨论(0)
  • 2021-01-03 01:03

    You can implement your own IPageStore that keeps pages in memory.

    0 讨论(0)
  • 2021-01-03 01:09

    Here is the solution that I came up with, based on svenmeier's answers. I'm sure that this is not 100% correct, but it's working fine in my testing:

    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;
                }
            };
        }
    }
    
    0 讨论(0)
  • 2021-01-03 01:10

    I want to improve on Johnny's answer who improves on Jesse's answer :)

    • this is the whole IPageManagerProvider, not only the IPageStore
    • frees memory by calling store.unbind(sessionId) when the session unbinds
    • works also with Wicket 7.x
    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;
            }
        }
    }
    
    0 讨论(0)
  • 2021-01-03 01:18

    I want to improve Jesse's answer. Below is a thread-safe implementation of IPageStore with internal "Least Recently Inserted" cache (keeps at most 5 recently accessed stateful pages per session):

    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;
        }
    }
    }
    
    0 讨论(0)
提交回复
热议问题