2

I am creating a Spring MVC application.

The Controller depends on one of the services [name: TestExecutionOrchestratorService.java] in the Service layer. The TestExecutionOrchestratorService depends on a couple of other services [names: JUnitTestExecutorService, QTPTestExecutorService].The configuration is set up to inject these services in a Map.

The Controller and the TestExecutionOrchestratorService are autowired using annotations. The TestExecutionOrchestratorService and its dependencies are wired using XML configuration.

Dependency Issue I am trying to solve:

The Controller is getting an object of TestExecutionOrchestratorService. The TestExecutionOrchestratorService is getting the Map of dependency services injected [I can tell by the log messages printed out]. I save this Map as an instance variable in the TestExecutionOrchestratorService. However, the instance of the TestExecutionOrchestratorService object in the Controller does not seem to have the Map set during dependency injection. In other words:

Controller---DEPENDS ON--->TestExecutionOrchestratorService---DEPENDS ON--->Map[of JUnitTestExecutorService, QTPTestExecutorService] The Map is empty in the instance of the TestExecutionOrchestratorService set in the Controller. I know from log messages that the Map is being injected during server startup.

The code and XML files are below:

Listing 1 - Controller

    /**
     * Handles requests for the application home page.
     */
    @Controller
    public class HomeController {

    private static final Logger logger = LoggerFactory.getLogger(HomeController.class);

    @Autowired
    private TestExecutionOrchestratorService testExecutionOrchestratorService;

    /**
     * Simply selects the home view to render by returning its name.
     */
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String home(Locale locale, Model model) {
        logger.info("Welcome home! the client locale is "+ locale.toString());

        Date date = new Date();
        DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);

        String formattedDate = dateFormat.format(date);

        model.addAttribute("serverTime", formattedDate );

        //ISSUE: Call to the service layer. The Map in the service is empty even though dependency injection is happening
                //there.
        if (testExecutionOrchestratorService != null) {
          logger.info("testExecutionOrchestratorService is not null. Executing it...");
          testExecutionOrchestratorService.runTests();
        }
        else {
          logger.info("testExecutionOrchestratorService is null. Why Why Why??");
        }


        return "home";
    }

}

Listing 2 - TestExecutionOrchestratorService

    /*
     * This class is the entry point into test execution. It takes the request to execute tests
     * and calls the appropriate test executors.
     */

    @Service("testExecutionOrchestratorService")
    public class TestExecutionOrchestratorService implements TestResultsReporter {
    /* List of executor services */
    private Map<String, TestExecutorService> testExecutors = new HashMap<String, TestExecutorService>();

  private static final Logger logger = LoggerFactory.getLogger(TestExecutionOrchestratorService.class);



  /*
   * For Spring's dependency injection - to inject all the test executors.
   */
  public void setExecutormap(Map<String, TestExecutorService> exMap) {
    if (exMap != null) {
      Set<Entry<String, TestExecutorService>> mapEntrySet = exMap.entrySet();
      logger.error("TestExecutionOrchestratorService [setExecutorMap]: Got a map of test executors. The entries are:");
      for (Entry<String, TestExecutorService> mapE: mapEntrySet) {
        logger.error("Key: " + mapE.getKey() + ", Value: " + mapE.getValue().getExecutorName());
      }

      //ISSUE: testExecutors is showing as null in the "runTests" method that is called by the Controller. Why??
      testExecutors.putAll(exMap);  
    }
    else {
      logger.error("TestExecutionOrchestratorService [setExecutorMap]: Got a null executors map");  
    }
  }


  /* runTests - Calls upon the various executors to run the tests.
   * ISSUE: The Controller is calling this method but the Map set in 'setExecutorMap' is not being found. */
  public void runTests() {
    logger.error("TestExecutionOrchestratorService [runTests]: Entering the method");
    /* Create a unique test run ID. This will be the current time stamp. */
    String testRunTimestamp = new Timestamp(new Date().getTime()).toString(); 
    logger.error("TestExecutionOrchestratorService [runTests]: Will execute executors with test run ID: " + testRunTimestamp);
        /* Call each executor and ask them to run their default tests. */

        if ((testExecutors != null) && (!testExecutors.isEmpty())) {
          logger.error("TestExecutionOrchestratorService [runTests]: test executors are available. Will execute them.");
          Collection<TestExecutorService> teServices = testExecutors.values();
          for (TestExecutorService teService: teServices) {

          teService.runTests(testRunTimestamp, null, this);

          }
        }
        else {
          /* ISSUE: THIS IS WHERE EXECUTION IS ALWAYS COMING */
          logger.error("TestExecutionOrchestratorService [runTests]: There are no test executors available.");
        }

    }
}

Listing 3 - Spring's Web context XML file (root-context.xml)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="..">


<!-- Root Context: defines shared resources visible to all other web components -->

<!-- Defining services -->
<!-- The following services are defined here: TestExecutionOrchestratorService,
JUnitTestExecutorService, QTPTestExecutorService -->
<!-- Definition of the JUnitTestExecutorService -->
<bean id="junitexecutor" name="junitexecutor" class="com.testing.autofwk.execution.JUnitTestExecutorService" />
<!-- Definition of the QTPTestExecutorService -->
<bean id="qtpexecutor" name="qtpexecutor" class="com.testing.autofwk.execution.QTPTestExecutorService" />
<!-- Definition of the TestExecutionOrchestratorService -->
<bean id="testexecutororchestrator" name="testexecutororchestrator" class="com.testing.autofwk.execution.TestExecutionOrchestratorService">
  <property name="executormap">
    <map>

       <entry key="junit">
         <ref local="junitexecutor"/>
       </entry>
       <entry key="qtp">
         <ref local="qtpexecutor"/>
       </entry>
    </map>
  </property>
</bean>


<context:component-scan base-package="com.testing.autofwk.execution" />

</beans>

Listing 4 - The MVC application dispatcher's context XML file (servlet-context.xml)

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="..">

<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->

<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />

<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />

<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <beans:property name="prefix" value="/WEB-INF/views/" />
    <beans:property name="suffix" value=".jsp" />
</beans:bean>

<context:component-scan base-package="com.testing.autofwk" />



</beans:beans>
user1949300
  • 21
  • 1
  • 2

1 Answers1

2

It seem that you are creating more than one TestExecutionOrchestratorService and the wrong one is injected in your controller. One object is created when the root-context.xml is loaded and since the TestExecutionOrchestratorService has the @Service annotation other beans will be create when the class will be scan.

On top of that, some package will be scan twice because of the dispatcher's context XML file.

It is a good practice to use something like this in the dispatcher's context XML file to avoid scanning the same class multiple times :

<context:component-scan base-package="com.testing.autofwk" use-default-filters="false">
    <context:include-filter expression="org.springframework.stereotype.Controller" type="annotation" />
</context:component-scan>

And in the root context :

<context:component-scan base-package="com.testing.autofwk">
    <context:exclude-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
</context:component-scan>
Jean-Philippe Bond
  • 10,089
  • 3
  • 34
  • 60
  • Thanks. I applied your solution but ran into the following exception: ERROR: org.springframework.web.context.ContextLoader - Context initialization failed org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Type filter class not found: org.springframework.stereotype.controller; nested exception is java.lang.ClassNotFoundException: org.springframework.stereotype.controller Offending resource: ServletContext resource [/WEB-INF/spring/root-context.xml]; nested exception is java.lang.ClassNotFoundException: org.springframework.stereotype.controller – user1949300 Jan 05 '13 at 00:01
  • I have the class in "spring-context-3.1.0.RELEASE.jar" file which is in my build path. I will debug this some more as I know it is (probably) related to the packaging of the app. If there any suggestions in the meantime, I'd appreciate them. Thanks! – user1949300 Jan 05 '13 at 00:06
  • It seem that you typed controller instead of Controller. – Jean-Philippe Bond Jan 05 '13 at 00:14
  • Thank you, that was the reason for the exception. However, after I corrected that, the original problem is still not solved. The application comes up without errors but still has the issue of the empty Map. – user1949300 Jan 06 '13 at 16:35
  • Did you removed the @Service annotation ? – Jean-Philippe Bond Jan 06 '13 at 16:54
  • I did and now it is working. Thanks a ton. To close this out with an explanation, I have two follow up questions:1. What are we doing in the solution recommended here? Are we defining beans in the service layer [TestExecutionOrchestratorService] via XML and Controller via Annotations? Could you give the complete logic for doing what we are doing? 2. How can I do away with annotations completely and tie the Controller layer with the Service layer with XML alone? Thanks again for your timely help. Appreciate it. – user1949300 Jan 06 '13 at 22:48
  • In Spring MVC, by default you will have 2 contexts, the servlet context and the root context. The servlet context is for your controllers and the root context for your business objects and your services. Now, both option are good (XML and Annotation) It depend on what you want to do. I do use both solution. You might want to read that post to learn about the advantage of each solution (http://stackoverflow.com/questions/8428439/spring-annotation-based-di-vs-xml-configuration) – Jean-Philippe Bond Jan 06 '13 at 23:02
  • IMO, you should use annotations for your controllers, i don't even know if it is possible to use XML for your controllers. Sorry but I don't really understand your second question... – Jean-Philippe Bond Jan 06 '13 at 23:10