3

I have created a spring controller called RegistrationController using autowire @Controller. I have for my own curiosity sake created a default constructor as follows and added a logger statement:

public RegistrationController() {
    logger.info("Registration Controller (Constructor) called-->"+this);
}

What I found was when I start my tomcat server (v7) in spring source IDE (v2.9) in the log file I see the following:

INFO: Initializing Spring root WebApplicationContext
2012-08-15 15:12:28,808 [pool-2-thread-1] INFO  com.controllers.registration.RegistrationController - Registration Controller (Constructor) called-->com.controllers.registration.RegistrationController@78c0dc2
Aug 15, 2012 3:12:28 PM org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring FrameworkServlet 'main'
2012-08-15 15:12:29,256 [pool-2-thread-1] INFO  com.controllers.registration.RegistrationController - Registration Controller (Constructor) called-->com.controllers.registration.RegistrationController@773ba8d6

I understand that the RegistrationController by default should be singleton object and only one instance must be created. However, as you can see from the log file that two instances are created. Somehow I feel this is not correct. But I don't have the exact answer.

Some of the important lines from my web.xml are as follows:

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/classes/applicationContext*.xml</param-value>
</context-param>
.
.
 <servlet>
    <servlet-name>main</servlet-name>
    <servlet-class>
        org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <load-on-startup>2</load-on-startup>
</servlet>



<servlet-mapping>
    <servlet-name>main</servlet-name>
    <url-pattern>*.htm</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>main</servlet-name>
    <url-pattern>*.jspx</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>main</servlet-name>
    <url-pattern>*.do</url-pattern>
</servlet-mapping>
   .
   .
   </web-app>

As you must have guessed, I have a main-servlet.xml and applicationContext.xml as my spring configuration files.

Can you please help me understand what is happening here?

EDITED SINCE I CANNOT ANSWER MY QUESTION BEFORE 8 HRS HAS ELAPSED:

Thanks to @Andna for the suggestion to look for the <context:component-scan /> in applicationContext.xml and main-servlet.xml. I had that element in both the file and hence every spring bean was scanned twice.

However, removing the <context:component-scan /> from the applicationContext.xml caused my Spring security configuration to break. To go in more detail I had created a class that implemented org.springframework.security.core.userdetails.UserDetailsService

@Service("userDetailsService") 
public class DuncanUserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
}
}

To support this class I had the following in my applicationContext-security.xml (shortened version) file:

<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
                    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
                    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                    http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">

<beans:bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
      <beans:property name="userDetailsService" ref="userDetailsService"/>
</beans:bean>

<beans:bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager">
    <beans:property name="providers">
        <beans:list>
            <beans:ref local="daoAuthenticationProvider" />
        </beans:list>
    </beans:property>
</beans:bean>

<authentication-manager>
    <authentication-provider user-service-ref="userDetailsService">
        <password-encoder ref= "passwordEncoder">
            <salt-source user-property="userName"/>
        </password-encoder>
     </authentication-provider>
</authentication-manager>

<beans:bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.ShaPasswordEncoder" />

</beans:beans>

Therefore removing <context: component-scan /> from the applicationContext.xml caused the "userDetailsService" object instance to go missing and I got tonnes of errors in my log file.

So what I did was I kept my component scan in the main-servlet.xml as it is:

<context:component-scan base-package="com.some.org" >
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
    </context:component-scan>

However, I edited the component scan using the exclude filter in the applicationContext.xml as follows:

<context:component-scan base-package="com.bankofamerica" >
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
        <context:exclude-filter type="regex" expression="com.some.org.common.http.HTTPClient" />
    </context:component-scan>

This helped me in achieving both the thing:

  1. Making sure that the singleton object was indeed singleton
  2. Making sure that the Spring security work as before.

Thank you all for all the wonderful suggestions.

Raj
  • 1,119
  • 4
  • 21
  • 41

2 Answers2

4

Maybe you have component-scan twice in both applicationContext.xml and main-servlet.xml, so the Spring scans for the annotated classes twice?

Andna
  • 6,539
  • 13
  • 71
  • 120
  • Yes Andna, you are absolutely right!! However, if I remove the from the applicationContext.xml then I have issue with the Spring security configuration. – Raj Aug 15 '12 at 19:54
  • import your spring security configuration inside your applicationContext.xml and remove the one in your main-servlet.xml – rptmat57 Aug 15 '12 at 20:09
  • 1
    There are two contexts: application context and servlet (in your case dispatcher-servlet) context. In component scan you can specify which packages/classes to scan by filters, look here, for example you can specify that in application context Spring populates container with every bean that is not annotated with `@Controller` or exclude all classes from package that contains controller classes and in servlet-context you specify to add classes that were excluded in application context. http://static.springsource.org/spring/docs/3.0.0.M3/spring-framework-reference/html/ch04s12.html – Andna Aug 15 '12 at 20:19
  • @Andna I have posted my answer by editing the original question. The approach is same as you described. But thank you for your wonderful suggestions. – Raj Aug 15 '12 at 20:44
1

I ended up with the following configuration in src/main/webapp/WEB-INF/web.xml:

<web-app xmlns=... version="2.4">
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/application-context.xml,/WEB-INF/security.xml,/WEB-INF/db.xml,/WEB-INF/services.xml</param-value>
    </context-param>

    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>sitemesh</filter-name>
        <filter-class>com.opensymphony.sitemesh.webapp.SiteMeshFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>sitemesh</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>

I think the most common problem with Spring is misunderstanding of Spring contexts. Take closer look at DispatcherServlet. Spring will automatically search for dispatcher-servlet.xml which is web context. Beans from this context are not available to beans that are defined in root contexts, e.g. specified in contextConfigLocation parameter. To make it available people often include dispatcher-servlet.xml in contextConfigLocation which logically causes that Spring initializes web context twice. Then it will be a nightmare to configure spring-security (global-method-security) or component-scan or sitemesh and configuration will be "shaky".

ruruskyi
  • 2,018
  • 2
  • 26
  • 37