Announcement Announcement Module
Collapse
No announcement yet.
Singleton class: unexpected field value Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Singleton class: unexpected field value

    I may be overlooking something fundamental here. I'm new to servlets/beans/Spring.

    I have an application context configuration file:

    Code:
    ...
      <!-- User manager -->
      <bean id="userManager" class="v025.client.domain.UserManager">
        ...
      </bean>
    
      <!-- Controller for user home page -->
      <bean id="userhomeController" class="v025.client.web.UserhomeController">
        <property name="userManager">
          <ref bean="userManager"/>
        </property>
      </bean>
    ...
    It defines a UserManager class - by default a singleton class. The class contains a HashMap to hold all active users of the application. A listener detects successful user authentication and puts a new user instance into the Map. A user is destroyed and removed from the map when their session ends. I tested this by outputting the contents of the map to a log file and it seemed to work fine.

    I added the UserHomeController, which is the controller for the page a user sees immediately after authentication (signin). It has a setter method for the UserManager, and the controller invokes the UserManager's getUser(String sessionId) method from within handleRequest(...).

    This is where my problem became evident. The getUser(...) method failed to return a user - it returned null. Debug output at this point showed that the map used my UserManager did not contain the user. According to my logs, the user was put into the map before the get method was called.

    To simplify the problem I have replaced the HashMap instance with a simple int, and the same problem can be seen. Here's my simplified UserManager class:

    Code:
    ...
      private int num;
    
      public UserManager()
      {
        this.num = 1;
      }
    
      ...
    
      private void initNum()
      {
        this.num = 56;
      }
    
      public int getNum()
      {
        return this.num;
      }
    ...
    initNum() is invoked before getNum().

    This is my UserHomeController:

    Code:
    ...
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
      {
        int num = this.userManager.getNum();
    
        // num is equal to 1
    
        return new ModelAndView("app/userhome");
      }
    ...
    My understanding is there is a single UserManager instance; if one of its fields is updated at time t, then at time t+1 the field should hold the updated value.

    So why does getNum() invoked from the controller return 1 instead of 56?

    Help much appreciated!

    Also, UserManager implements ApplicationListener. It's the onApplicationEvent(ApplicationEvent event) method that invokes initNum().
    Last edited by J-dev; Oct 3rd, 2007, 10:13 AM. Reason: More (possibly) relevant details.

  • #2
    A typical explanation for this is that you are creating a new instance of the UserManager using the new operator. But it sounds like you aren't doing this. Is it possible to see the rest of the code? Another thing to do would be add logging to the constructor to see when a new instance is created.

    Comment


    • #3
      Karl,

      Thanks for the reply. I've included all relevant code below.

      A typical explanation for this is that you are creating a new instance of the UserManager using the new operator.
      UserManager is instantiated as a bean by Spring and passed to the contoller as a property via a setter method, so I don't think that's the problem.

      Another thing to do would be add logging to the constructor to see when a new instance is created.
      The UserManager's construction is logged, and my log shows its construction prior to the put and get method invocations.

      application-servlet.xml:
      Code:
      <?xml version="1.0" encoding="UTF-8"?>
      
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:tx="http://www.springframework.org/schema/tx"
             xsi:schemaLocation="
             http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
             http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
      ">
      
        <!-- Mappings. -->
        <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
          <property name="mappings">
            <props>
              <prop key="home.html">homeController</prop>
              <prop key="signup.html">signupController</prop>
              <prop key="signin.html">signinController</prop>
              <prop key="userhome.html">userhomeController</prop>
              <prop key="accessdenied.html">accessDeniedController</prop>
            </props>
          </property>
        </bean>
      
      <!-- Controller for home page. -->
        <bean id="homeController" class="v025.client.web.HomeController"/>
      
        <!-- Controller for signup (form).  -->
        <bean id="signupController" class="v025.client.web.SignupController">
          <property name="sessionForm"><value>true</value></property>
          <property name="commandName"><value>signupForm</value></property>
          <property name="commandClass"><value>v025.client.web.SignupForm</value></property>
          <property name="validator"><ref bean="signupValidator"/></property>
          <property name="formView"><value>user/signup/form</value></property>
          <property name="successView"><value>userhome.html?w</value></property>
          <property name="failureView"><value>userhome.html?error</value></property>
        </bean>
      
        <!-- Controller for signup form validator. -->
        <bean id="signupValidator" class="v025.client.web.SignupValidator"/>
      
        <!-- Controller for user signin. -->
        <bean id="signinController" class="v025.client.web.SigninController"/>
      
        <bean id="userManager" class="v025.client.domain.UserManager">
          <property name="userManagerDao"><ref bean="userManagerDao"/></property>
        </bean>
      
        <!-- Controller for user home page. -->
        <bean id="userhomeController" class="v025.client.web.UserhomeController">
          <property name="userManager"><ref bean="userManager"/></property>
        </bean>
      
        <bean id="userManagerDao" class="v025.client.data.rest.UserManagerDaoRestlet"/>
      
        <!-- Controller for user access denied. -->
        <bean id="accessDeniedController" class="v025.client.web.AccessDeniedController"/>
      
        <!-- Views. -->
        <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
          <property name="viewClass"><value>org.springframework.web.servlet.view.JstlView</value></property>
          <property name="prefix"><value>/WEB-INF/jsp/</value></property>
          <property name="suffix"><value>.jsp</value></property>
        </bean>
      
       <!-- Messages. -->
        <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
          <property name="basenames">
            <list>
              <value>messages</value>
              <value>acegimessages</value>
            </list>
          </property>
        </bean>
      UserHomeController.java:
      Code:
      package v025.client.web;
      
      import v025.client.domain.UserManager;
      import v025.client.domain.User;
      
      // Spring Framework
      import org.springframework.web.servlet.mvc.Controller;
      import org.springframework.web.servlet.ModelAndView;
      import org.springframework.web.util.WebUtils;
      
      // Servlet
      import javax.servlet.ServletException;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      
      // IO and Util
      import java.io.IOException;
      
      // Logging
      import org.apache.commons.logging.Log;
      import org.apache.commons.logging.LogFactory;
      
      public class UserhomeController implements Controller
      {
        // Logger for this class and subclasses.
        protected final Log logger = LogFactory.getLog(getClass());
      
        private UserManager userManager;
      
        public void setUserManager(UserManager userManager)
        {
          this.userManager = userManager;
        }
      
        public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
          throws ServletException, IOException
        {
          logger.info("handleRequest()");
      
          User user = this.userManager.getUserBySessionId(WebUtils.getSessionId(request));
          logger.info("  user(" + user + ")");
          this.userManager.logAllUsers();
      
          logger.info("  Returning view:app/userhome, user, " + user + "");
      
          return new ModelAndView("app/userhome", "user", user);
        }
      }
      UserManager.java:
      Code:
      package v025.client.domain;
      
      import v025.client.domain.User;
      import v025.client.domain.UserMap;
      import v025.client.domain.security.AuthenticationDetails;
      import v025.client.data.UserManagerDao;
      import v025.client.data.DataAccessException;
      
      import org.springframework.context.ApplicationListener;
      import org.springframework.context.ApplicationEvent;
      import org.acegisecurity.ui.session.HttpSessionApplicationEvent;
      import org.acegisecurity.ui.session.HttpSessionDestroyedEvent;
      import org.acegisecurity.ui.session.HttpSessionCreatedEvent;
      import org.acegisecurity.event.authentication.AuthenticationSuccessEvent;
      import org.acegisecurity.Authentication;
      import org.acegisecurity.ui.WebAuthenticationDetails;
      
      // Logging
      import org.apache.commons.logging.Log;
      import org.apache.commons.logging.LogFactory;
      
      public class UserManager implements ApplicationListener
      {
        // Logger for this class and subclasses
        protected final Log logger = LogFactory.getLog(getClass());
      
        private UserManagerDao umDao;
      
        public void setUserManagerDao(UserManagerDao umDao)
        {
          this.umDao = umDao;
        }
      
        private UserMap userMap;
      
        /**
         * <p>
         *  This should normally be invoked once implicitly by
         *  Spring during application initialisation.
         * </p>
         */
        public UserManager()
        {
          logger.info("  UserManager ***CONSTRUCTED***");
          this.userMap = new UserMap();
        }
      
        public void onApplicationEvent(ApplicationEvent event)
        {
          // Session created.
          if(event instanceof HttpSessionCreatedEvent)
          {
            String sessionId = ((HttpSessionCreatedEvent)event).getSession().getId();
            logger.info("  HttpSessionCreatedEvent ***SESSION CREATED*** id(" + sessionId + ")");
      
            // Insert code here for pre-authentication (anonymous) user tracking.
          }
          // Authentication success.
          else if(event instanceof AuthenticationSuccessEvent)
          {
            Authentication auth = ((AuthenticationSuccessEvent)event).getAuthentication();
            AuthenticationDetails details = null;
            WebAuthenticationDetails webDetails = null;
      
            // Get authentication details.
            if(auth.getPrincipal() instanceof AuthenticationDetails)
              details = (AuthenticationDetails)auth.getPrincipal();
            else
              logger.error("Missing authentication details");
      
            // Get web authentication details.
            if(auth.getDetails() instanceof WebAuthenticationDetails)
              webDetails = (WebAuthenticationDetails)auth.getDetails();
            else
              logger.error("Missing web authentication details");
      
            logger.info("  AuthenticationSuccessEvent ***AUTHENTICATED*** (" + webDetails.getSessionId() + ")");
      
            initUser(details.getEmail(), webDetails.getSessionId());
            logAllUsers();
          }
          // Session Destroyed.
          else if(event instanceof HttpSessionDestroyedEvent)
          {
            String sessionId = ((HttpSessionDestroyedEvent)event).getSession().getId();
            logger.info("  HttpSessionDestroyedEvent ***SESSION DESTROYED*** id(" + sessionId + ")");
            destroyUser(sessionId);
            logAllUsers();
          }
          else if(event instanceof HttpSessionApplicationEvent)
          {
            String sessionId = ((HttpSessionApplicationEvent)event).getSession().getId();
            logger.info("  HttpSessionApplicationEvent id(" + sessionId + ")");
          }
        }
      
      
        private void initUser(String email, String sessionId)
        {
          logger.info("initUser(" + email + ", " + sessionId + ")");
      
          try
          {
            User user = umDao.getUser(email);
            if(user == null)
              throw new DataAccessException("Failed to get User via DAO.");
            this.userMap.put(sessionId, user);
          }
          catch(DataAccessException e)
          {
            logger.error("DataAccessException(" + e.getMessage() + ")");
          }
        }
      
        private void destroyUser(String sessionId)
        {
          logger.info("destroyUser(" + sessionId + ")");
      
          // do stuff
        }
      
        public User getUserBySessionId(String sessionId)
        {
          logger.info("getUserBySessionId(" + sessionId + ")");
      
          return this.userMap.get(sessionId);
        }
      
        // DEBUG
        public void logAllUsers()
        {
          String userString = "";
          java.util.ArrayList<User> userList = new java.util.ArrayList<User>(this.userMap.getAllUsers());
      
          for(User user: userList)
            userString = userString + user.getDisplayName() + " ";
      
          userString = userString.trim();
      
          logger.info("users[" + userString + "]");
        }
      }
      (Reached character limit - see my next post for the remainder of the code)

      Comment


      • #4
        UserMap.java:
        Code:
        package v025.client.domain;
        
        import v025.client.domain.User;
        
        import java.util.Map;
        import java.util.HashMap;
        import java.util.Collection;
        
        public class UserMap
        {
          private HashMap<String, User> userBySessionIdMap;
        
          public UserMap()
          {
            this.userBySessionIdMap = new HashMap<String, User>();
          }
        
          public void put(String sessionId, User user)
          {
            this.userBySessionIdMap.put(sessionId, user);
          }
        
          public User get(String sessionId)
          {
            return this.userBySessionIdMap.get(sessionId);
          }
        
          public void remove(String sessionId)
          {
            this.userBySessionIdMap.remove(sessionId);
          }
        
          public Collection<User> getAllUsers()
          {
            return (Collection<User>)this.userBySessionIdMap.values();
          }
        }
        log (from application initialization):
        Code:
        2007-10-03 18:30:44,894 INFO [org.springframework.web.context.ContextLoader] - Root WebApplicationContext: initialization started
        2007-10-03 18:30:44,951 INFO [org.springframework.web.context.support.XmlWebApplicationContext] - Refreshing o[email protected]cb560b: display name [Root WebApplicationContext]; startup date [Wed Oct 03 18:30:44 BST 2007]; root of context hierarchy
        2007-10-03 18:30:45,106 INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] - Loading XML bean definitions from ServletContext resource [/WEB-INF/app-servlet.xml]
        2007-10-03 18:30:45,262 INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] - Loading XML bean definitions from ServletContext resource [/WEB-INF/applicationContext-acegi-security.xml]
        2007-10-03 18:30:45,284 INFO [org.springframework.web.context.support.XmlWebApplicationContext] - Bean factory for application context [o[email protected]cb560b]: org.s[email protected]1575e30
        2007-10-03 18:30:45,522 INFO [v025.client.domain.UserManager] -   UserManager ***CONSTRUCTED***
        2007-10-03 18:30:45,537 INFO [org.springframework.beans.factory.support.DefaultListableBeanFactory] - Pre-instantiating singletons in org.springframework.beans.factor[email protected]: defining beans [urlMapping,homeController,signupController,signupValidator,signinController,userManager,userhomeController,userManagerDao,accessDeniedController,viewResolver,messageSource,filterChainProxy,httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor,rememberMeServices,authenticationManager,daoAuthenticationProvider,authenticationService,authenticationServiceDao,loggerListener]; root of factory hierarchy
        2007-10-03 18:30:45,627 INFO [v025.client.web.SignupValidator] - setMessageSource(o[email protected]cb560b: display name [Root WebApplicationContext]; startup date [Wed Oct 03 18:30:44 BST 2007]; root of context hierarchy)
        2007-10-03 18:30:45,857 INFO [org.springframework.cache.ehcache.EhCacheManagerFactoryBean] - Initializing EHCache CacheManager
        2007-10-03 18:30:45,873 WARN [net.sf.ehcache.config.ConfigurationFactory] - No configuration found. Configuring ehcache from ehcache-failsafe.xml  found in the classpath: jar:file:/home/wepogo/server/apache-tomcat-6.0.13/webapps/v025/WEB-INF/lib/ehcache-1.2.4.jar!/ehcache-failsafe.xml
        2007-10-03 18:30:46,041 INFO [org.acegisecurity.intercept.AbstractSecurityInterceptor] - Validated configuration attributes
        2007-10-03 18:30:46,043 INFO [org.springframework.web.context.ContextLoader] - Root WebApplicationContext: initialization completed in 1148 ms
        2007-10-03 18:30:46,093 INFO [org.springframework.web.servlet.DispatcherServlet] - FrameworkServlet 'app': initialization started
        2007-10-03 18:30:46,094 INFO [org.springframework.web.context.support.XmlWebApplicationContext] - Refreshing o[email protected]6e1eba: display name [WebApplicationContext for namespace 'app-servlet']; startup date [Wed Oct 03 18:30:46 BST 2007]; parent: o[email protected]cb560b
        2007-10-03 18:30:46,098 INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] - Loading XML bean definitions from ServletContext resource [/WEB-INF/app-servlet.xml]
        2007-10-03 18:30:46,124 INFO [org.springframework.web.context.support.XmlWebApplicationContext] - Bean factory for application context [o[email protected]6e1eba]: org.s[email protected]6ae507
        2007-10-03 18:30:46,128 INFO [v025.client.domain.UserManager] -   UserManager ***CONSTRUCTED***
        2007-10-03 18:30:46,128 INFO [org.springframework.beans.factory.support.DefaultListableBeanFactory] - Pre-instantiating singletons in org.s[email protected]6ae507: defining beans [urlMapping,homeController,signupController,signupValidator,signinController,userManager,userhomeController,userManagerDao,accessDeniedController,viewResolver,messageSource]; parent: org.s[email protected]1575e30
        2007-10-03 18:30:46,131 INFO [v025.client.web.SignupValidator] - setMessageSource(o[email protected]6e1eba: display name [WebApplicationContext for namespace 'app-servlet']; startup date [Wed Oct 03 18:30:46 BST 2007]; parent: o[email protected]cb560b)
        2007-10-03 18:30:46,171 INFO [org.springframework.web.servlet.DispatcherServlet] - FrameworkServlet 'app': initialization completed in 77 ms
        2007-10-03 18:31:03,852 INFO [v025.client.web.SigninController] - Returning view:user/signin/form
        2007-10-03 18:31:03,886 INFO [v025.client.domain.UserManager] -   HttpSessionCreatedEvent ***SESSION CREATED*** id(7EA1DB205A19229CAA24F522D613DC03)
        2007-10-03 18:31:07,858 INFO [v025.client.data.rest.AuthenticationServiceDaoRestlet] - getAuthenticationDetails([email protected])
        2007-10-03 18:31:08,015 INFO [v025.client.data.rest.AuthenticationServiceDaoRestlet] -   request status(OK (200)) for ref(http://jbox:8080/v025s/api/users/[email protected]/authentication)
        2007-10-03 18:31:08,075 INFO [v025.client.domain.UserManager] -   AuthenticationSuccessEvent ***AUTHENTICATED*** (7EA1DB205A19229CAA24F522D613DC03)
        2007-10-03 18:31:08,075 INFO [v025.client.domain.UserManager] - initUser([email protected], 7EA1DB205A19229CAA24F522D613DC03)
        2007-10-03 18:31:08,075 INFO [v025.client.data.rest.UserManagerDaoRestlet] - getUser([email protected])
        2007-10-03 18:31:08,097 INFO [v025.client.data.rest.UserManagerDaoRestlet] -   request status(OK (200)) for ref(http://jbox:8080/v025s/api/users/[email protected])
        2007-10-03 18:31:08,124 INFO [v025.client.domain.UserManager] - users[Paris]
        2007-10-03 18:31:08,125 WARN [org.acegisecurity.event.authentication.LoggerListener] - Authentication event AuthenticationSuccessEvent: [email protected]; details: [email protected]: RemoteIpAddress: 44.44.44.44; SessionId: 7EA1DB205A19229CAA24F522D613DC03
        2007-10-03 18:31:08,131 WARN [org.acegisecurity.event.authentication.LoggerListener] - Authentication event InteractiveAuthenticationSuccessEvent: [email protected]; details: [email protected]: RemoteIpAddress: 44.44.44.44; SessionId: 7EA1DB205A19229CAA24F522D613DC03
        2007-10-03 18:31:08,140 INFO [v025.client.web.UserhomeController] - handleRequest()
        2007-10-03 18:31:08,140 INFO [v025.client.domain.UserManager] - getUserBySessionId(7EA1DB205A19229CAA24F522D613DC03)
        2007-10-03 18:31:08,140 INFO [v025.client.web.UserhomeController] -   user(null)
        2007-10-03 18:31:08,141 INFO [v025.client.domain.UserManager] - users[]
        2007-10-03 18:31:08,141 INFO [v025.client.web.UserhomeController] -   Returning view:app/userhome, user, null
        2007-10-03 18:32:36,277 INFO [v025.client.domain.UserManager] -   HttpSessionDestroyedEvent ***SESSION DESTROYED*** id(7EA1DB205A19229CAA24F522D613DC03)
        2007-10-03 18:32:36,278 INFO [v025.client.domain.UserManager] - destroyUser(7EA1DB205A19229CAA24F522D613DC03)
        2007-10-03 18:32:36,278 INFO [v025.client.domain.UserManager] - users[Paris]

        Comment


        • #5
          And my web.xml too, just in case I've missed something there:
          Code:
          <?xml version="1.0" encoding="UTF-8"?>
          
          <!DOCTYPE web-app PUBLIC '-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN' 'http://java.sun.com/dtd/web-app_2_3.dtd'>
            
          <web-app xmlns="http://java.sun.com/xml/ns/javaee"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
              version="2.5">
            
            <!-- Spring. -->
            <servlet>
              <servlet-name>app</servlet-name>
              <display-name>Spring Dispatcher Servlet</display-name>
              <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
              <load-on-startup>1</load-on-startup>
            </servlet>
            
            <listener>
              <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
            </listener>
              
            <servlet-mapping>
              <servlet-name>app</servlet-name>
              <url-pattern>*.html</url-pattern>
              <url-pattern>*.actn</url-pattern>
            </servlet-mapping>
            
            <taglib>
              <taglib-uri>/spring</taglib-uri>
              <taglib-location>/WEB-INF/spring.tld</taglib-location>
            </taglib>
            <!-- DWR. -->
            <servlet>
              <servlet-name>dwr-invoker</servlet-name>
              <display-name>DWR Servlet</display-name>
              <servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
              <init-param>
                <param-name>debug</param-name>
                <param-value>true</param-value>
              </init-param>
            </servlet>
          
            <servlet-mapping>
              <servlet-name>dwr-invoker</servlet-name>
              <url-pattern>/dwr/*</url-pattern>
            </servlet-mapping>
          
            <context-param>
              <param-name>contextConfigLocation</param-name>
              <param-value>
                /WEB-INF/app-servlet.xml
                /WEB-INF/applicationContext-acegi-security.xml
              </param-value>
            </context-param>
            <listener>
              <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
            </listener>
          
            <!-- Acegi security. -->
            <filter>
              <filter-name>Acegi Filter Chain Proxy</filter-name>
              <filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
              <init-param>
                <param-name>targetClass</param-name>
                <param-value>org.acegisecurity.util.FilterChainProxy</param-value>
              </init-param>
            </filter>
          
            <filter-mapping>
              <filter-name>Acegi Filter Chain Proxy</filter-name>
              <url-pattern>/*</url-pattern>
            </filter-mapping>
          
            <!-- Timeout. -->
            <session-config>
              <session-timeout>1</session-timeout>
            </session-config>
          
            <!-- Welcome files. -->
            <welcome-file-list>
              <welcome-file>index.jsp</welcome-file>
            </welcome-file-list>
          
          </web-app>

          Comment


          • #6
            I missed the second UserManager constructor output in the log. Two separate UserManager instances are constructed, which explains my problem.

            This thread covers the double initialization case:

            http://forum.springframework.org/showthread.php?t=44550

            Comment

            Working...
            X