Chapter 2. javaconfig-dependency-injection

2.1. Introduction

In this lab you will gain experience using Spring to configure the completed Reward Dining: The Course Reference Domain. You’ll use Spring to configure the pieces of the application, then run a top-down system test to verify application behavior.

What you will learn:

  1. The Big Picture: how Spring "fits" into the architecture of a typical Enterprise Java application
  2. How to use Spring to configure plain Java objects (POJOs)
  3. How to organize Spring configuration files effectively
  4. How to create a Spring ApplicationContext and get a bean from it
  5. How Spring, combined with modern development tools, facilitates a test-driven development (TDD) process

Specific subjects you will gain experience with:

  1. Spring Java configuration syntax
  2. Spring embedded database support
  3. Spring Tool Suite

Estimated time to complete: 45 minutes

2.2. Quick Instructions

If you feel you have a good understanding of the material, follow the steps listed here. However, if you would like more detailed guidance, the next section contains more detailed step-by-step instructions.

If you aren’t sure, try the quick instructions first and refer to the detailed instructions if you need more help. Each quick-instruction has a link to the corresponding detailed instructions.

[Note] Note

There are TODO tasks in this lab so make sure you have opened the Tasks view in STS (this was covered in the previous Lab). .

  1. Creating application configuration file (details)

    (TODO-01 - TODO-04) Go to the config package and open the file RewardsConfig. Note that the class does not need to extend any other classes or implement any interfaces.

    Annotate the RewardsConfig class to mark it as a special class for providing configuration instructions. Within this class, define your four @Bean methods as shown below, in the RewardsConfig.java box. Each method should contain the code needed to instantiate the object and set its dependencies. Since each repository has a DataSource property to set, and since the DataSource will be defined elsewhere (TestInfrastructureConfig.java), you will need to define a DataSource field / instance variable set by Spring using the @Autowired annotation. For consistency with the rest of the lab, give your RewardNetworkImpl @Bean method the name rewardNetwork.

    Rewards Application

    Figure 2.1. Rewards Application - Spring Configuration



    (TODO-05) Go to src/test/java and open RewardsConfigTests. Uncomment the code as instructed. If it doesn’t compile - fix RewardsConfig. Run the test and make sure it passes before moving on.

  2. Infrastructure configuration (details)

    (TODO-06) Next review the infrastructure configuration necessary to test your application. We need a datasource for your application to use to acquire database connections in a test environment. Open TestInfrastructureConfig.java file and verify the datasource and database connection code - there is no need to modify the @Bean method.

    (TODO-07) This TestInfrastructureConfig.java will also serve as the master configuration class for our upcoming test. To have it serve in this role, add an @Import to the class to reference your new RewardsConfig.class.

  3. Create the system test class (details)

    (TODO-08) Create a new JUnit Jupiter (JUnit 5) test called RewardNetworkTests in the rewards package inside the src/test/java source folder. Other than using @BeforeEach instead of @Before, it will be just like a JUnit 4 test.

  4. Implement test setup logic (details)

    (TODO-09) Add the @BeforeEach test setup logic required to run your system test. In this method, you will need to create an application context using the master configuration class from the last step (TestInfrastructureConfig.java). In addition, you need to obtain a reference to the the rewardNetwork bean from the application context. Assign this as a private field that you can reference in your test methods.

  5. Implement test logic (details)

    (TODO-10) Copy the unit test (the @Test method) from RewardNetworkImplTests.testRewardForDining() - we are testing the same code, but using a different setup.

  6. Run the test

    (TODO-11) With the test setup logic implemented, you’re ready to test your application. Run your new unit test. It will invoke RewardNetwork.rewardAccountFor(Dining) method to verify all pieces of your application work together to carry out a successful reward operation. You should get a green bar in the JUnit view.

    Congratulations the lab is finished.

2.3. Detailed Instructions

2.3.1. Creating the application configuration

In the previous exercise you’ve coded your RewardNetworkImpl, the central piece of this reward application. You’ve unit tested it and verified it works in isolation with dummy (stub) repositories. Now it is time to tie all the real pieces of the application together, integrating your code with supporting services that have been provided for you. In the following steps you’ll use Spring to configure the complete rewards application from its parts. This includes plugging in repository implementations that use a JDBC data source to access a relational database.

Below is a configuration diagram showing the parts of the rewards application you will configure and how they should be wired together:

Rewards Application (Annotated)

Figure 2.2. Rewards application - system diagram



The system diagram shows the configuration split into two categories: Application Configuration and Infrastructure Configuration. The components in the Application Configuration box are written by you and makeup the application logic. The components in the Infrastructure Configuration box are not written by you and are lower-level services used by the application. In the next few steps you’ll focus on the application configuration piece. You’ll define the infrastructure piece later.

In your project, you’ll find your familiar RewardNetworkImpl in the rewards.internal package. You’ll find each of the JDBC-based repository implementations it needs, located with their domain objects, inside the rewards.internal package. Each repository uses the JDBC API to execute SQL statements against a DataSource that is part of the application infrastructure. The DataSource implementation you will use is not important for this exercise but will become important later.

2.3.1.1. Implement the application configuration class

Spring configuration information can be externalized from the main Java code, partitioned across one or more Java configuration files. In this step you’ll create a single configuration file that tells Spring how to configure your application components.

Under the src/main/java folder, find the config package and open the class RewardsConfig. Note that the class does not need to extend any other classes or implement any interfaces.

We will however add some code and Spring annotations to it to create the result illustrated in the RewardsConfig box below:

Rewards Application (Annotated)

Figure 2.3. Rewards application - Spring configuration



First, place a @Configuration annotation on the RewardsConfig class. This tells Spring to treat this class as a set of configuration instructions to be used when the application is starting up.

[Tip] Tip

When typing in any classname in STS, use CTRL+Space before completing the full name, this will cause the IDE to prompt you for possible completions AND correctly add the relevant import statement. To add import statements after the fact, you can use CTRL+SHIFT+O (upper-case letter O) to Organize Imports (use COMMAND+SHIFT+O on MacOS).

Next, within this RewardsConfig.java class, define four methods annotated with the @Bean annotation. Each method should instantiate and return one of the beans in the illustration, accountRepository, restaurantRepository, rewardRepository, and rewardNetwork. For example, you should create an accountRepository() method that instantiates JdbcAccountRepository and returns it.

Looking back at the illustration, you can see that each of the three repositories has a dependency on a DataSource that will be defined elsewhere. This means in each repository method we must make a call to the repository’s setDataSource(), passing in a reference to the dataSource. But where will we get the DataSource from when it is defined in another file (in our case in TestInfrastructureConfig.java)?

The class already has a DataSource defined for you, but we need to give it a value. To make the class easy to test, define a constructor that accepts a DataSource and sets dataSource.

[Note] Note

As this is the only constructor on the class @Autowired is not needed, Spring will call it automatically when asked to create an instance of RewardsConfig. It will automatically provide a reference to the DataSource defined in TestInfrastructureConfig.java, assuming both configuration files are specified at startup.

Finally, you should be aware that Spring will assign each bean an ID based on the @Bean method name. The instructions below will assume that the ID for the RewardNetwork bean is rewardNetwork. Therefore, for consistency with the rest of the lab, give your RewardNetworkImpl @Bean method the name rewardNetwork.

[Tip] Tip

As you define each bean, follow bean naming conventions. The arrows in the configuration diagram representing bean references follow the recommended naming convention.

For best practices, a bean’s name should describe the service it provides. It should not describe implementation details. For this reason, a bean’s name often corresponds to its interface. For example, the class JdbcAccountRepository implements the AccountRepository interface. This interface is what callers work with. By convention, then, the bean name should be accountRepository.

Similarly each bean method should return an interface not a concrete class. So the accountRepository() method should return AccountRepository not JdbcAccountRepository.

[Tip] Tip

As you define each bean, have Eclipse auto-suggest for you. Press CTRL+Space when defining a return type and Eclipse will suggest what’s legal based on types available in the classpath. In-line documentation of each tag will also be displayed.

2.3.1.2. Test the Configuration class

As RewardsConfig is a Java class we can unit test it. Once you have the four beans defined and referenced as shown in the configuration diagram, open RewardsConfigTests - in the config package under src/test/java.

Note that a mock dataSource has been defined already.

Uncomment the code between /* and */. This should compile if you implemented RewardsConfig correctly. If not, fix RewardsConfig as necessary. Make sure your @Bean methods have the right names, return the right implementations and that their signatures define interfaces as return types.

Run the test (in STS, right click anywhere in the code window, select Run As …​ then JUnit Test).

Once the test passes, move on to the next step!

2.3.1.3. The infrastructure configuration needed to test the application

In the previous step you visualized bean definitions for your application components. In this step we’ll investigate the infrastructure configuration necessary to test your application.

To test your application, each JDBC-based repository needs a DataSource to work. For example, the JdbcRestaurantRepository needs a DataSource to load Restaurant objects by their merchant numbers from rows in the T_RESTAURANT table. So far, though, you have not defined any DataSource implementation. In this step you’ll see how to setup a DataSource in a separate configuration file in your test tree. It’s in the test area, because it is only for testing - it is not the one you would use in production.

In the src/test/java source folder, navigate to the root rewards package. There you will find a file named TestInfrastructureConfig.java. Open it.

You will see that a DataSource is already configured for you. You don’t need to make any changes to this bean, but you do need to understand what we have defined here for you. This TestInfrastructureConfig.java will also serve as the master configuration class for our upcoming test. To have it serve in this role, add an @Import to the class to reference the RewardsConfig.class.

[Note] Note

To import RewardsConfig, Spring will create it as a Spring bean and automatically call its constructor passing in the DataSource created by TestInfrastructureConfig.

Spring ships with decent support for creating a DataSource based on in-memory databases such as H2, HSQLDB and Derby. The code you see is a quick way to create such a database.

Notice how the Builder references external files that contain SQL statements. These SQL scripts will be executed when the application starts. Both scripts are on the classpath, so you can use Spring’s resource loading mechanism and prefix both of the paths with classpath:. Note that the scripts will be run in the order specified (top to bottom) so the order matters in this case.

2.3.2. System testing the application with Spring and JUnit

In this final section you will test your rewards application with Spring and JUnit. You’ll first implement the test setup logic to create a Spring ApplicationContext that bootstraps your application. Then you’ll implement the test logic to exercise and verify application behavior.

2.3.2.1. Create the system test class

Start by creating a new JUnit Test Case called RewardNetworkTests in the rewards package inside the src/test/java source folder.

  • Use New >> Other >> Java >> JUnit Test Case wizard to help you.
  • Check the box next to setUp to automatically create a setup method, you will need this on the next step.
  • Also note that you might need to change the version of JUnit that will be used to JUnit Jupiter
RewardNetworkTests TestCase

Figure 2.4. Creating RewardNetworkTests TestCase using JUnit Test Case wizard



Once you have your RewardNetworkTests class created, move on to the next step!

2.3.2.2. Implement the test setup logic

In this step you’ll implement the setup logic needed to run your system test. You’ll first create a Spring ApplicationContext that bootstraps your application, then lookup the bean you’ll use to invoke the application.

First, notice that you have a public void setUp() method annotated with @org.junit.jupiter.api.BeforeEach - this was done for you when you selected the setUp() checkbox in JUnit test case wizard. @BeforeEach is the JUnit 5 equivalent of JUnit 4’s @Before.

Within setUp() call SpringApplication.run, providing it the TestInfrastructureConfig.class that you want to load. Doing this will bootstrap your application by having Spring create, configure, and assemble all beans defined in the two configuration files (since one imports the other).

Next, ask the context to get the rewardNetwork bean for you, which represents the entry-point into the rewards application. Assign the bean to a private field of type RewardNetwork you can reference from your test methods.

[Tip] Tip

Be sure to assign the reference to the rewardNetwork bean to a field of type RewardNetwork and not RewardNetworkImpl. A Spring ApplicationContext encapsulates the knowledge about which component implementations have been selected for a given environment. By working with a bean through its interface you decouple yourself from implementation complexity and volatility.

Don’t ask the context for beans "internal" to the application. The RewardNetwork is the application’s entry-point, setting the boundary for the application defined by an easy-to-use public interface. Asking the context for an internal bean such as a repository or data source is questionable.

Now verify that Spring can successfully create your application on test setup. To do this, modify the test() method:

  • Rename the method to testRewardForDining
  • Remove the call to fail() - leave the method body blank for now.

Then, run your test class by selecting it and accessing Run >> Run As >> JUnit Test from the menu bar (you may also do this by right-clicking in the code window and using the Run As option in the popup menu). After your test runs, you should see the green bar indicating setUp() ran without throwing any exceptions.

If you see red, inspect the failure trace in the JUnit view to see what went wrong in the setup logic. Carefully inspect the stack trace - Spring error messages are usually very detailed in describing what went wrong. The most useful information is usually at the bottom of the stack trace, so you my need to scroll down to see it.

Once you have the green bar, move on to the last step!

2.3.2.3. Implement the test logic

Now the test setup logic works we can modify our test to invoke the RewardNetwork.rewardAccountFor(Dining) method and verify that all the pieces of your application work together to carry out a successful reward operation.

You will not have to write the Unit Test yourself. Have a look at RewardNetworkImplTest.testRewardForDining(). You can just copy and paste its content into RewardNetworkTests.testRewardForDining().

[Tip] Tip

In a real life application you would not have the same content for both tests. We are making things fast here so you can focus on Spring configuration rather than spending time on writing the test itself.

You can now rerun your test. In STS, you may simply select the green play button on the tool bar to Run Last Launched (CTRL+F11 or COMMAND+F11 on MacOS). A green play button can also be found in the JUnit view tab.

When you have the green bar, congratulations! You’ve completed this lab.

You have just used Spring to configure the components of a realistic Java application and have exercised application behavior successfully in a test environment inside your IDE.