Announcement Announcement Module
Collapse
No announcement yet.
Scoped Proxy, Session Bean and DAO access Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Scoped Proxy, Session Bean and DAO access

    Hi All,

    I am quite new to Spring and facing a big problem with my configuration of session beans. I guess I miss something fundamental.

    I try to create a LoginController which retrieves the current username via NTLM, afterwards all other user related data should be loaded from a database and stored in a session scoped bean.


    sampleapp-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:aop="http://www.springframework.org/schema/aop"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans 
               http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
               http://www.springframework.org/schema/aop 
               http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
               http://www.springframework.org/schema/context
               http://www.springframework.org/schema/context/spring-context-3.0.xsd"
               default-autowire="byName">
    
         <context:component-scan base-package="com.ltech.org.sample"/>
    
        <bean id="currentUser" class="com.ltech.org.sample.bean.User" scope="session">
              <aop:scoped-proxy/>
        </bean>
    
        <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
               <property name="driverClassName"><value>oracle.jdbc.driver.OracleDriver</value></property>
               <property name="url"><value>jdbc:oracle:thin:@host:port:database</value></property>
               <property name="username"><value>yyy</value></property>
               <property name="password"><value>xxx</value></property>
        </bean> 
    
        <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
            <property name="configLocation">
                <value>/WEB-INF/classes/sqlMap-config.xml</value>
            </property>
        </bean>
        
    	<bean id="userDAO" class="com.ltech.org.sample.dao.UserDAOImpl">
            <property name="dataSource"><ref local="dataSource"/></property>
            <property name="sqlMapClient"><ref local="sqlMapClient"/></property>
        </bean>
      
        <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>
     
    </beans>
    web.xml
    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>
    	<filter>
    		<filter-name>NTLM_HTTP_Authentication_Filter</filter-name>
    		<filter-class>com.xxx.ntlm.NtlmHttpFilter</filter-class>
    		<init-param>
    			<param-name>jcifs.http.domainController</param-name>
    			<param-value>xx.xxx.xx.xx</param-value>
    		</init-param>
    		<init-param>
    			<param-name>jcifs.http.insecureBasic</param-name>
    			<param-value>false</param-value>
    		</init-param>
    		<init-param>
    			<param-name>jcifs.http.enableBasic</param-name>
    			<param-value>false</param-value>
    		</init-param>
    	</filter>
    	
    	<filter>
    		<filter-name>springFilter</filter-name>
    		<filter-class>
    			org.springframework.web.filter.RequestContextFilter
    		</filter-class>
    	</filter>
    
    	<filter-mapping>
    		<filter-name>NTLM_HTTP_Authentication_Filter</filter-name>
    		<url-pattern>/*</url-pattern>
    	</filter-mapping>
    
    	<filter-mapping>
    		<filter-name>springFilter</filter-name>
    		<url-pattern>/*</url-pattern>
    	</filter-mapping>
    
    	<listener>
    		<listener-class>
    			org.springframework.web.context.request.RequestContextListener
    		</listener-class>
    	</listener>
    
    	<servlet>
    		<servlet-name>sampleapp</servlet-name>
    		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    		<load-on-startup>1</load-on-startup>
    	</servlet>
    
    	<servlet-mapping>
    		<servlet-name>sampleapp</servlet-name>
    		<url-pattern>*.htm</url-pattern>
    	</servlet-mapping>
    
    	<welcome-file-list>
    		<welcome-file>
    			index.jsp
        	</welcome-file>
    	</welcome-file-list>
    
    </web-app>
    LoginController.java
    Code:
    package com.ltech.org.sample.web;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.servlet.ModelAndView;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import org.springframework.web.bind.annotation.RequestMapping;
    import java.io.IOException;
    import java.util.Map;
    import java.util.HashMap;
    import com.ltech.org.sample.bean.User;
    import com.ltech.org.sample.bi.UserManager;
    import com.ltech.org.sample.dao.UserDAO;
    
    @Controller
    public class LoginController {
        protected final Logger logger = LoggerFactory.getLogger(getClass());
    
        private UserManager userManager;
         
        @RequestMapping(value = "/login.htm")    
        public ModelAndView doLogin(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
    
            String isid = request.getRemoteUser();
    
            logger.info("login " + isid);
            
            userManager.populate(isid);  
            
            Map myModel = new HashMap();
            myModel.put("currentUser", userManager.getCurrentUser());
            
            return new ModelAndView("login", "model", myModel);
        }
        
        public void setUserManager(UserManager pm) {
        	userManager = pm;
        }
    
        public UserManager getUserManager() {
            return userManager;
        }
    	
    }
    UserManagerImpl.java
    Code:
    package com.ltech.org.sample.bi;
    
    import com.ltech.org.sample.bean.User;
    import com.ltech.org.sample.dao.UserDAO;
    import org.springframework.stereotype.Service;
    
    @Service("userManager")
    public class UserManagerImpl implements UserManager {
        private User currentUser;
        private UserDAO userDAO;
        
        public User getCurrentUser() {
            return currentUser;
        }
        
        public void setCurrentUser(User currentUser) {
            this.currentUser = currentUser;
        }
        
    	public UserDAO getUserDAO() {
    		return userDAO;
    	}
    	public void setUserDAO(UserDAO userDAO) {
    		this.userDAO = userDAO;
    	}
    	
    // NOT WORKING !!! every user shares the same session bean
    	public void populateNotWorking(String isid)
    	{
    		currentUser  = userDAO.selectUserById(isid);
    	}
    
    // WORKING !!! every user has its own user session bean
    	public void populate(String isid)
    	{
    		User tmp = userDAO.selectUserById(isid);
    		currentUser.setLastName(tmp.getLastName());
    		currentUser.setFirstName(tmp.getFirstName());
    		currentUser.setIsid(tmp.getIsid());
    		currentUser.setEmail(tmp.getEmail());		
    	}
    }
    Is there another way to pass data from the DAO to the bean instead of explicitly calling the set methods?

    If I use the populateNotWorking() method, the session bean is overwritten with another user data on login. So everybody shares the same bean.

    I guess the "currentUser = userDAO.selectUserById(isid);" statement is not referencing the "proxied" bean. Maybe someone could help me with this.

    Thanks a lot in advance.

  • #2
    Hi,

    maybe you didn't post your full configuration files, but I don't see how you link your context to your sampleapp-servlet.xml in web xml (don't se neither ContextLoaderListener & context-param contextConfigLocation, nor init-param contextConfigLocation in DispatcherServlet definition), and I'm also missing where you register your LoginController and your UserManagerImpl.

    To try to help you, I'll need full configuration.

    Comment


    • #3
      Thanks a lot for your reply.

      Below is the part in the web.xml where the sampleapp-servlet.xml is bound.
      Afaik when using DispatcherServlet config there is no need for a ContextLoaderListener.

      Code:
      	
              <servlet>
      		<servlet-name>sampleapp</servlet-name>
      		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      		<load-on-startup>1</load-on-startup>
      	</servlet>
      
      	<servlet-mapping>
      		<servlet-name>sampleapp</servlet-name>
      		<url-pattern>*.htm</url-pattern>
      	</servlet-mapping>

      I am using auto wire by name and annotations (@Controller in LoginController.java; and @Service in UserManagerImpl.java), so there is no specific xml definition for this.

      But I also tried it with a explicit definition of all controllers and services in the xml configuration file, unfortunately this doesn't change anything (problem still exists).

      Comment


      • #4
        Hi,

        annotations don't substitute the fact that Spring beans must be defined with <bean class="mypackage.MyClass"/> tag in xml. In fact, context:component-scan will only scan for annotated classes which are registered as Spring beans, ignoring everything else because it's not part of the context. What puzzles me is I don't see those definitions in your files.
        Also, you didn't post your UserDAOImpl class which is an important part of the puzzle.

        Comment


        • #5
          Thats the UserDAOImpl.java.

          Code:
          package com.ltech.org.sample.dao;
          
          import java.util.List;
          import org.springframework.orm.ibatis.SqlMapClientTemplate;
          import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport;
          import com.ltech.org.sample.bean.User;
          
          public class UserDAOImpl extends SqlMapClientDaoSupport 
          implements UserDAO 
          {
              public User selectUserById(String userId)
              {
                  SqlMapClientTemplate template = getSqlMapClientTemplate();
                  Object objectUser = template.queryForObject("select-user-by-isid", userId);
                  return objectUser instanceof User ? ((User)objectUser) : null;
              }
          }
          I also added the definition for LoginController and UserMangerImpl (shown below) to the sampleapp-servlet.xml, but it doesn't change anything

          Code:
          <bean id="loginController" class="com.ltech.org.sample.web.LoginController">
                  <property name="userManager">
                      <ref bean="userManager"/>
                  </property>
          </bean> 
          
              
          <bean id="userManager" class="com.ltech.org.sample.bi.UserManagerImpl">
             		<property name="currentUser" ref="currentUser"/>
                         <property name="userDAO" ref="userDAO"/>
          </bean>
          The controller is called correctly and the application is running (except the strange behaviour when trying to populate the proxied bean in the UserManger).


          EDIT:
          this was the template for my basic approach, maybe it helps.

          http://wheelersoftware.com/articles/...ped-beans.html
          Last edited by derZonk; Aug 5th, 2010, 07:25 AM. Reason: additional information

          Comment


          • #6
            Ok,

            I think the problem might be the fact that you are using UserManagerImpl as a service but you are also giving it a state (i.e. private class variable currentUser), but services should be stateless (since you haven't specified a bean instantiation strategy, Spring uses the default which is singleton, so your service bean will only be instantiated once and each request for the class will return that singleton: thus, you see that putting state in it is quite illogical). I suggest you change your controller like this:
            Code:
            package com.ltech.org.sample.web;
            
            import org.slf4j.Logger;
            import org.slf4j.LoggerFactory;
            import org.springframework.stereotype.Controller;
            import org.springframework.ui.ModelMap;
            
            import javax.servlet.ServletException;
            import javax.servlet.http.HttpServletRequest;
            import org.springframework.web.bind.annotation.RequestMapping;
            import java.io.IOException;
            import java.util.Map;
            import java.util.HashMap;
            import com.ltech.org.sample.bean.User;
            import com.ltech.org.sample.bi.UserManager;
            import com.ltech.org.sample.dao.UserDAO;
            
            @Controller
            public class LoginController {
                protected final Logger logger = LoggerFactory.getLogger(getClass());
            
                private UserManager userManager;
                 
                @RequestMapping(value = "/login.htm")    
                public String doLogin(HttpServletRequest request, ModelMap model)
                        throws ServletException, IOException {
            
                    String isid = request.getRemoteUser();
                    logger.info("login " + isid);
                    model.addAttribute("currentUser", userManager.getCurrentUser(isid));
                    return "login";
                }
                
                public void setUserManager(UserManager pm) {
                	userManager = pm;
                }
            	
            }
            and your service like this

            Code:
            package com.ltech.org.sample.bi;
            
            import com.ltech.org.sample.bean.User;
            import com.ltech.org.sample.dao.UserDAO;
            import org.springframework.stereotype.Service;
            
            @Service("userManager")
            public class UserManagerImpl implements UserManager {
            
                private UserDAO userDAO;
                
                public User getCurrentUser(String isid) {
                    return userDAO.selectUserById(isid);
                }
                
                public void setUserDAO(UserDAO userDAO) {
            		this.userDAO = userDAO;
                }
            
            }
            and then in your /WEB-INF/jsp/login.jsp access the User object returned with ${currentUser}.

            This should work, but I'd also point out that I find your configuration a bit odd/non standard (you should separate Spring webmvc configuration from general Spring core config by using init-param in DispatcherServlet config for the former and listener + context-param for the latter; you should always define beans if you want them to be part of Spring; you are not using mvc:annotation-driven and the mvc namespace; I'd also suggest using Spring jdbc support instead of what you are using for your dao; etc.). Since you yourself stated you're new to Spring, I'd suggest you start learning with official documentation and examples and not external articles (for example take a look at the PetClinic webapp sample).

            Hope I was clear enough...

            Comment


            • #7
              Thanks for your answer, but thats unfortunately not what I want.

              If I have to access the database for every call of getCurrentUser (which you suggested), there is no sense in creating a session scoped bean.

              I'd like to load all user data just once on login, and access this information within all other controllers without doing a database query every time.

              Coming from struts I normaly use:

              Code:
              request.getSession().setAttribute("currentUser", user);
              and
              Code:
              request.getSession().getAttribute("currentUser");
              which works also in Spring. But I thougt it would be a good idea to try Spring session scoping. Which turns out to be not.

              Also have a look at the official spring documentation

              http://static.springsource.org/sprin...ection-proxies

              <?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:aop="http://www.springframework.org/schema/aop"
              xsi:schemaLocation="http://www.springframework.org/schema/beans
              http://www.springframework.org/schem...-beans-3.0.xsd
              http://www.springframework.org/schema/aop
              http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

              <!-- an HTTP Session-scoped bean exposed as a proxy -->
              <bean id="userPreferences" class="com.foo.UserPreferences" scope="session">

              <!-- this next element effects the proxying of the surrounding bean -->
              <aop:scoped-proxy/>
              </bean>

              <!-- a singleton-scoped bean injected with a proxy to the above bean -->
              <bean id="userService" class="com.foo.SimpleUserService">

              <!-- a reference to the proxied userPreferences bean -->
              <property name="userPreferences" ref="userPreferences"/>

              </bean>
              </beans>
              Which is exactly what I am doing - passing a session bean via proxy into a service class. So its not a non-standard thing.
              Have you ever worked with the <aop:scoped-proxy/> functionality? Because it seems we are talking at cross purpose.



              BTW:
              iBatis is a great database framework (and is supported by spring), especially if you have to use the full functionality of SQL (not that hibernate hql .....). But thats another topic
              Last edited by derZonk; Aug 5th, 2010, 08:32 AM.

              Comment


              • #8
                Sorry, my attention was catched by some oddities in config and I lost focus on the main matter. You correctly declared currentUser as a session scoped bean using the aop:scoped-proxy so that injection into services is session-based and not application-based. With
                Code:
                <bean id="userManager" class="com.ltech.org.sample.bi.UserManagerImpl">
                   		<property name="currentUser" ref="currentUser"/>
                               <property name="userDAO" ref="userDAO"/>
                </bean>
                you correctly inject it into your UserManager service (you don't need to do this if you use bean name autoproxy, but you always need at least to define the UserManagerImpl as a bean to make it work).

                Problem is, in your service when you do
                Code:
                currentUser  = userDAO.selectUserById(isid);
                what you are doing is changing the reference stored in the service to another object. This object is one you create from scratch using dao and knows nothing of session or of Spring itself. So, by issuing that command, you are effectively disrupting the "link" between your session-scoped bean and your service. Thus, from that moment on, every time every user in every session access the object from the service, it will get the object you created and not Spring session bean you configured before (and that object, since you created it yourself and then never change it, is of course the same for every session).
                Using the setters, you correctly modify the session-scoped bean and that modification is session-scoped (because you declared your bean to be so); that's why every session will correctly have its instance.
                To answer you, I don't think you have any alternative but set the properties of the session-scoped bean in the service, but I hope at least WHY the first method doesn't work is fully clarified.

                Comment


                • #9
                  Thank you very much, I was assuming this too, but I wasn't aware of the reason. You made that quite clear.

                  Now regarding my initial intention (loading user data once and store it in a session), is there another better way to do that?

                  For example I could add the currentUser bean as a parameter for the selectUserByID method in the DAO, instead of creating a new reference?
                  Doing so the reference should persist I guess.

                  Or should I just use the request.getSession().set/getAttribute() approach?

                  I will consider your point regarding separation/splitting of the configuration files in the future. Thanks for that too.


                  EDIT:

                  this works finally.

                  in UserManagerImpl.java
                  Code:
                  	
                          public void populate(String isid)
                  	{
                  		userDAO.selectUserById(isid, currentUser);		
                  	}
                  and in UserDAOImpl.java

                  Code:
                      
                      public void selectUserById(String userId, User result)
                      {
                          SqlMapClientTemplate template = getSqlMapClientTemplate();
                          template.queryForObject("select-user-by-isid", userId, result);
                      }

                  Again thanks for your explanation which led me into the right direction
                  Last edited by derZonk; Aug 5th, 2010, 09:08 AM.

                  Comment


                  • #10
                    I don't think the method you are using now is wrong, in fact I think it's the best way to go. I'd only make it slightly different in this:
                    assume you have a User session-scoped bean defined as you did, but this object is made this way: instead of having filelds like String name, String password, String role, long id, String[] authorizedActions etc, it has a single field of a custom type, say Profile. The profile object contains all the fields name, password, role etc. In your dao, you fill up and return not the User object, but the Profile object. And then, in your service, you do
                    Code:
                    public void populate(String isid)
                    	{
                                currentUser.setProfile(userDAO.selectUserById(isid));
                            }
                    this way, with a single set, you obtain what you want: you'll have a User session scoped bean which is different for every user (since you don't disrupt the link) and you populate it in one line by calling a single setter.
                    Hope this is clear, and helps.

                    EDIT

                    sorry didn't see your edit, of course this is also a nice & neat solution, happy to have contributed.
                    Last edited by Enrico Pizzi; Aug 5th, 2010, 09:22 AM.

                    Comment

                    Working...
                    X