tomcat config causes 2 copies of @Service to run

Viewed 300

I have a ScheduledExecutor that runs on @PostConstruct of some of my @Service. however, this tomcat config causes 2 copies of @Service to run and removing either one would cause the @Controller in the wars to not work.

how to resolve this? i need the @Service to be singleton so that there is only one Executor that runs some code.

setting the Executor to static would make it one Executor only, but is there a way to achieve this at the config level?

web.xml

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>WEB-INF/classes/applicationContext.xml</param-value>
</context-param>
<servlet>
  <servlet-name>servlet</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:servlet-context.xml</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
  <async-supported>true</async-supported>
</servlet>

servlet-context.xml

<import resource="applicationContext.xml" />

applicationContext.xml

<bean id="properties" class="my.package.common.properties.MyPropertyPlaceholderConfigurer">
  <property name="locations">
    <list>
      <value>WEB-INF/classes/system.properties</value>
      <value>WEB-INF/classes/db.properties</value>
      <value>WEB-INF/classes/applicationContext.properties</value>
    </list>
  </property>
</bean>

<context:annotation-config />
<context:component-scan base-package="my.package" />
2 Answers

The problem is very likely related with the following import in your servlet-context.xml:

<import resource="applicationContext.xml" />

Probably in your web.xml file you are including the following web application context listener:

<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

In a typical Spring MVC application, this listener is responsible for initializing the Spring root application context based on the information provided in the XML files indicated in the web application context param contextConfigLocation:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>WEB-INF/classes/applicationContext.xml</param-value>
</context-param>

On the other hand, you are initiating a second child application context when configuring the Spring DispatcherServlet:

<servlet>
    <servlet-name>servlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:servlet-context.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    <async-supported>true</async-supported>
</servlet>

In your example, this second context is based on the configuration provided in servlet-context.xml.

It is very important to understand that both contexts are related in such a way that all the beans defined in the root application context are accessible to the child context associated with the DispatcherServlet (the contrary is not true).

This allows you to organize and provide the right hierarchy for your beans: for example, the root application context usually defines services and common components and configuration (database, security, async support, cache, etcetera) whereas the child context defines beans related to MVC stuff.

This context hierarchy is very well explained in the Spring documentation:

mvc spring contexts hierarchy

As you told, when you include the following import in your servlet-context.xml file:

<import resource="applicationContext.xml" />

you are in fact duplicating all the stuff defined in the root context.

To solve the problem, please, remove that line from servlet-context.xml.

According to your comments after doing that all your controllers report an error 404. Probably the cause is that after removing applicationContext.xml Spring is unable to find any Controllers: please, be sure that you include component-scan in servlet-context.xml as well, pointing to the packages where your controllers are defined. In servlet-context.xml include:

<context:component-scan base-package="my.package.controllers" />`

Include in this way in servlet-context.xml any other MVC resource you need (view resolvers, etcetera) as well.

Ideally, in applicationContext.xml only is necessary to scan for components like services, repositories, and similar stuff.

Seems like you have initialized application context twice. First of all org.springframework.web.servlet.DispatcherServlet servlet creates its own internal web application context, you can check it in java docs

/**
 * Create a new {@code DispatcherServlet} that will create its own internal web
 * application context based on defaults and values provided through servlet
 * init-params. Typically used in Servlet 2.5 or earlier environments, where the only
 * option for servlet registration is through {@code web.xml} which requires the use
 * of a no-arg constructor.
 * ...
 */
public DispatcherServlet() {
     super();
     setDispatchOptionsRequest(true);
}

And the second initialization is processing by following config:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>WEB-INF/classes/applicationContext.xml</param-value>
</context-param>

<listener>
   <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

I think you have ContextLoaderListener in web.xml like on example above.

As solution you can remove context params for DispatcherServlet.

Related