Chapter 25. xml-di (optional)

25.1. xml-di (optional): XML Dependency Injection

25.2. Introduction

What you will learn:

  1. Configuring a Spring application using classic XML configuration.

Specific subjects you will gain experience with:

  1. How to configure beans and dependency injection in XML
  2. How to use XML namespaces

Estimated time to complete: 30 minutes

25.3. Quick Instructions

Quick instructions for this exercise have been embedded within the lab materials in the form of TODO comments. To display them, open the Tasks view (Window >> Show View >> Tasks (not Task List)). Use the view’s small down arrow to select a Configure Contents…​ menu, you’ll find the instructions are easy to follow if you configure TODOs to display On any element in the same project.

Occasionally, TODO’S defined within XML files disappear from the Tasks view (i.e. gaps in the number sequence). To correct this, go to Preferences >> General >> Editors >> Structured Text Editor >> Task Tags pane. Check Enable searching for Task Tags and click Clean and Redetect Tasks. On the Filters tab, ensure XML content type is checked.

25.4. Detailed Instructions

25.4.1. First verify that everything works

(TODO-01) The project features an integration test that verifies the system’s behavior. It’s called RewardNetworkTests and lives in the rewards package. Run this test by right-clicking on it and selecting 'Run As…​' followed by 'JUnit Test'. The test should run successfully.

This test illustrates a great advantage of using automated tests to verify that the refactoring our application is successful. We will run this test again after we make changes to the application to verify that our system functions the same as it did originally.

25.4.2. Convert to XML configuration / Component Scanning

(TODO 02) Open the rewards-config.xml file located in the src/main/resources/config folder. This will serve as our main application configuration file, and will replace the configuration instructions currently in the RewardsConfig.java class.

Our first step will be to add the context namespace to this configuration file. This can be done manually via copy / paste, but STS provides a quicker alternative. On the bottom of the editor you should see a "Namespaces" tab. Within this tab you will see a set of checkboxes, each one represents a namespace that you can add to the XML root element. Check the "context" box (you may be prompted that this will add an element to your configuration file, which is exactly what we want, so click OK). Return to the "Source" tab and note that the XML namespace "context" has been added to your XML root element. This means you can now take advantage of the context: namespace.

(TODO 03) Now that we have added the context: namespace, we can add the element to do component scanning. If you enter "context:" and press CTRL+Space, the editor will prompt you for the possible entries in the context namespace that can be used. Select "context:component-scan".

Within the component-scan tag it is important to set the "base-package" element; this tells Spring which packages and sub-packages should be included in the scanning process. If you look at the RewardsConfig.java class, you can see the value presently used by the JavaConfiguration: "rewards". Use this same value.

(TODO 04) At this point, we have an XML configuration file equivalent of our RewardsConfig.java class, and we also have a test-infrastructure-config.xml that is already prepared for us. Our test class is still coded to load the TestInfrastructureConfig.java Java configuration class. We need to change this configuration class to import our XML files.

Open TestInfrastructureConfig.java. Remove/comment out the existing @Import annotation and the entire dataSource() bean method. Instead add an @ImportResource annotation, and pass it an array of Strings indicating the XML configuration files to load. The first is the file you just modified, rewards-config.xml located in the src/main/resources/config folder. The second is an existing XML file already prepared for you, test-infrastructure-config.xml located in the src/test/resources/rewards folder. Note that both of these are classpath resources, so @ImportResource should find these as long as we indicate the correct folder locations.

Once you have finished this modification, save all your work and rerun the RewardNetworkTests. The test should pass at this point. If it does not, take a look at the test output to see if you can determine why. The most likely issue is the file/path literals of the configuration files.

25.4.3. Switch to XML-based Configuration

At this point, we are using Annotation-based configuration (via component scanning) to define the application components (RewardNetwork and the three repositories) and XML configuration to define the DataSource. In this next section, we will demonstrate how to use a 100% XML configuration. Return to rewards-config.xml and perform the following.

(TODO 05) Define a bean element to instantiate the JdbcAccountRepository. It is good practice to give beans an ID using the "id" attribute, so give this bean the ID "accountRepository", or any other ID you like. Use the "class" attribute to specify the the fully-qualified classname of what we want to instantiate, the JdbcAccountRepository. STS provides a great feature here to quickly determine the packaging: within the class attribute value type "JAR" (all caps) and press CTRL+Space. STS will prompt you with all known classes that match the camel-case pattern. Simply choose JdbcAccountRepository from the list.

Next, within the bean element start and end tags, place a property sub-element to set the dataSource property. The autocomplete feature is very useful here, using it you can discover that the "name" of the property we want to set is called "dataSource". We want to set this to a "ref" to another bean named "dataSource". Note that this other bean is defined elsewhere, so in this case the autocomplete feature can’t help us. Also note that the editor may give you a warning that this bean is unknown for the same reason; you can safely ignore this warning for now.

(TODO 06) Define a bean element to instantiate the JdbcRestaurantRepository. The procedure is exactly the same as the last step, except you should select a different ID value (suggest: "restaurantRepository") and the fully-qualified classname.

You may remember that this class has a special method within it that must be called at startup time in order to pre-populate its cache. The method is named populateRestaurantCache and you should use the init-method attribute to specify it.

(TODO 07) Define a bean element to instantiate the JdbcRewardRepository. The procedure is exactly the same as the the previous two steps, except a different ID should be used (suggest: "rewardRepository") and the fully-qualified classname should be different. Note there is no need for any init-method on this bean.

(TODO 08) Define a bean element to instantiate the RewardNetworkImpl. The ID for this bean should be "rewardNetwork" to allow our existing test code to work. This bean has three constructor arguments that must be populated: an AccountRepository, a RestaurantRepository, and a RewardRepository. These happen to be the beans defined in the previous three steps so use the constructor-arg sub-elements with ref attributes to specify these dependencies.

Now that we have defined XML bean definitions for our beans, we can remove the annotations on the classes themselves:

(TODO 09) Open RewardNetworkImpl and remove the @Service and @Autowired annotations.

(TODO 10) Open JdbcAccountRepository and remove the @Repository and @Autowired annotations.

(TODO 11) Open JdbcRestaurantRepository and remove the @Repository and @Autowired annotations.

(TODO 12) While in the JdbcRestaurantRepository remove the @PostConstruct annotation from the populateRestaurantCache method. Our XML configuration instructions will ensure that this method is called during startup.

(TODO 13) Open JdbcRewardRepository and remove the @Repository and @Autowired annotations.

At this point, we have removed all of the annotation-based configuration. Save all your work, and re-run the RewardNetworkTests. It should pass - Spring is now using XML-based bean definitions. Congratulations, you have completed this lab.

25.4.4. Bonus - Remove Component Scanning

(TODO 14) Now that we are using XML configuration and have removed all the stereotype and DI annotations, is there any reason for the component-scanning element to remain? Remove this element and rerun the test, It should pass. You can also experiment with removing the RewardsConfig class since it is no longer used.