O'Reilly logo

Just Spring by Madhusudhan Konda

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 1. Basics

Introduction

The Spring Framework has found a very strong user base over the years. Software houses and enterprises found the framework to be the best fit for their plumbing needs. Surprisingly, the core principle that Spring has been built for—the Dependency Injection (DI)—is very simple and straightforward to understand. In this chapter, we aim to discusses the fundamentals of the framework from a high ground. We will try to get a basic understanding of Dependency Injection principles. I will present a simple problem of object coupling and tight dependencies to begin with. Then we will aim to solve decoupling and dependencies by using Spring Framework.

Object Coupling Problem

Let us consider a simple program whose aim is to read data from various data sources. It should read data from a file system or database or even from an FTP server.

For simplicity, we will start writing a program that reads the data from a file system. The following example code is written without employing any best practices or dependency injection patterns—it’s just a simple and plain Java program that works.

This snippet shows a VanillaDataReaderClient class that uses FileReader to fetch the data.

Example 1-1. 
public class VanillaDataReaderClient {
  private FileReader fileReader = null;
  private String fileName = "src/main/resources/basics/basics-trades-data.txt";

  public VanillaDataReaderClient() {
    try {
      fileReader = new FileReader(fileName);
    } catch (FileNotFoundException e) {
      System.out.println("Exception" + e.getMessage());
    }
  }

  private String fetchData() {
    return fileReader.read();
  }

  public static void main(String[] args) {
    VanillaDataReaderClient dataReader = new VanillaDataReaderClient();
    System.out.println("Got data using no-spring: " + dataReader.fetchData());
  }
}

As the name suggests, the VanillaDataReaderClient is the client that fetches the data from a data source. When the program is executed, the VanillaDataReaderClient gets instantiated along with a referenced FileReader object. It then uses the VanillaFileReader object to fetch the result.

The following snippet shows the implementation of VanillaFileReader class:

Example 1-2. 

public class VanillaFileReader {
  private StringBuilder builder = null;
  private Scanner scanner = null;

  public VanillaFileReader(String fileName) throws FileNotFoundException {
    scanner = new Scanner(new File(fileName));
    builder = new StringBuilder();
  }

  public String read() {
    while (scanner.hasNext()) {
      builder.append(scanner.next());
      builder.append(",");
    }
    return builder.toString();
  }
}

Simple and sweet, isn’t it?

The limitation of the client above is that it can only read the data from file system.

Imagine, one fine morning, your manager asks you to improvise the program to read data from a Database or Socket instead of File.

With the current design, it is not possible to incorporate these changes without refactoring the code—as only File Reader associated to the client, it can’t work for any non-file resources. We have to associate a DatabaseReader or SocketReader with the client for any non-file resource reading excercise.

Lastly (and very importantly), we can see the client and the reader are coupled tightly. That is, client depends on VanillaFileReader’s contract, meaning if VanillaFileReader changes, so does the client. If the client has already been distributed and used by, say, 1,000 users across the globe, you will have fun refactoring the client!

If your intention is to build good scalable and testable components, then coupling is a bad thing.

So, let’s work out on your manager’s demands and make the program read the data from any source. For this, we will take this program one step further—refactoring so the client can read from any datasource. For this refactoring, we have to rely on our famous Design to Interfaces principle.

Designing to Interfaces

Writing your code against interfaces is a very good practice, although I am not going to sing praises about the best practices of designing here. The first step in designing to interfaces is to create an interface. The concrete classes will implement this interface, binding themselves to the interface contract rather than to an implementation. As long as you keep the interface contract unchanged, the implementation can be modified any number of times without affecting the client.

For our data reader program, we create an IReader interface. This has just one method:

public interface IReader {
  public String read();
}

The next step is to implement this contract. Because we have to read the data from different sources, we create respective concrete implementations such as FileReader for reading from a File System, DatabaseReader for reading from a Database, FtpReader for reading from FTP Server, and so on.

The template for concrete implementation goes in the form of XXXReader as shown here:

public class XXXReader implements IReader {
  public String read(){
    //impl goes here
  }
}

Once you have the XXXReader ready, the next step is to use it in the client program. However, instead of using the concrete class reference, we will be using the interface reference.

For example, the modified client program shown in this snippet has a IReader variable reference (highlighted in bold), rather than FileReader or FtpReader. It has a constructor that takes in an IReader interface:

public class DataReaderClient {
  private IReader reader = null;
  private static String fileName 
    = "src/main/resources/basics/basics-trades-data.txt";

  public DataReaderClient(IReader reader) {
    this.reader = reader;
  }

  private String fetchData() {
    return reader.read();
  }

  public static void main(String[] args) {
    ...
  }
}

Looking at the client code, if I ask you to tell me the actual reader that has been used by the client, would you be able to tell me? You can’t!

The DataReaderClient does not know where it is fed the data until runtime. The IReader class will only be resolved at runtime by using polymorphism. All we know is that the client can get any of the concrete implementations of IReader interface. The interface methods that were implemented in concrete incarnations of IReader are invoked appropriately.

The challenge is to provide the appropriate concrete implementations of IReader to the client. One way to do this is to create an instance of IReader in the client program—a FileReader is created and passed on to the client as shown here:

public class DataReaderClient {
  private IReader reader = null;
  private static String fileName 
    = "src/main/resources/basics/basics-trades-data.txt";
  ...
  public static void main(String[] args) {
    IReader fileReader = new FileReader(fileName);// Ummh..still hard wired?
    DataReaderClient client = new DataReaderClient(fileReader);
    System.out.println("Got data using interface design priciple: " 
        + client.fetchData());
  }
}

Well, it is still hardwired as the client should have to know about which IReader it is going to use. Of course, we could swap FileReader with DatabaseReader or FtpReader without much hassle, as they all implement IReader interface. So, we are in a much better position when our Manager comes along and changes his mind!

However, we still have the concrete IReader coupled to the client. Ideally, we should eliminate this coupling as much as possible. The question is how can we provide an instance of IReader to DataReaderClient without hardwiring? Is there a way we can abstract the creation of this FileReader away from the client?

Before I ask you more questions, let me tell you the answer: yes! Any Dependency Injection (DI) framework can do this job for us. One such framework is Spring Framework.

The Spring Framework is one of the Dependency Injection (or Inversion of Control) frameworks that provides the dependencies to your objects at runtime very elegantly. I won’t explain the framework details yet, because we are eager to find the solution to the IReader problem using Spring Framework first.

Introducing Spring

The object interaction is a part and parcel of software programs. The good design allows you to replace the moving parts with no or minimal impact to the existing code. We say the objects are coupled tightly when the moving parts are knitted closely together. However, this type of design is inflexible—it is in a state where it cannot be scalable or testable in isolation or even maintainable without some degree of code change.

Spring Framework can come to the rescue in designing the components and eliminating dependencies.

Dependency Injection

Spring Framework works on one single mantra: Dependency Injection. This is sometimes interchangeable with the Inversion of Control (IoC) principle.

When a standalone program starts, it starts the main program, creates the dependencies, and then proceeds to execute the appropriate methods. However, this is exactly the reverse if IoC is applied. That is, all the dependencies and relationships are created by the IoC container and then they are injected into the main program as properties. The program is then ready for action. This is essentially the reverse of usual program creation, thus the name Inversion of Control principle.

Refactoring IReader by Using the Framework

Coming back to our IReader program, the solution is to inject a concrete implementation of IReader into the client on demand.

Let’s modify the client program—see the decoupled client code here:

public class DecoupledDataReaderClient {
  private IReader reader = null;
  private ApplicationContext ctx = null;

  public DecoupledDataReaderClient() {
    ctx = new ClassPathXmlApplicationContext("basics-reader-beans.xml");
  }

  private String fetchData() {
    reader = (IReader) ctx.getBean("reader");
    return reader.read();
  }

  public static void main(String[] args) {
    DecoupledDataReaderClient client = new DecoupledDataReaderClient();
    System.out.println("Example 1.3: Got data: " + client.fetchData());
  }
}

As we can see, there is lots of magic going on in this class—the magician being the ApplicationContext class! Let’s see briefly what’s going on in there.

When the class is instantiated by the JVM, Spring Framework’s ClassPathXmlApplicationContext object is created in the class’s constructor. This context class reads a configuration XML file and creates any objects declared in that file—a FileReader by the name of “reader” bean in this case. The instantiation of the ApplicationContext creates the container that consists of the objects defined in that XML file. When the fetchData is invoked, the same reader bean is fetched from the container and the appropriate methods on the FileReader were executed.

Don’t worry if you didn’t get much of it, you will learn about them in no time, trust me! We will discuss the framework fundamentals later in the chapter, but for now, let’s continue with our reader example.

After creating the client class, create a XML file that consists of definitions of our FileReader. The XML file is shown here:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="reader" class="com.madhusudhan.jscore.basics.FileReader">
        <constructor-arg value
            ="src/main/resources/basics/basics-trades-data.txt" />
    </bean>

</beans>

The purpose of this XML file is to create the respective beans (instances of our classes) and their relationships. This XML file is then provided to the ApplicationContext instance, which creates a container with these beans and their object graphs along with relationships.

Looking at the reader bean in the preceding configuration file, the ctx = new ClasspathXmlApplicationContext("basics-reader-beans.xml") statement creates a container to hold the beans defined in the basics-reader-beans.xml. The FileReader class is created by the framework with a name reader and stored in this container. The Spring container is simply a holder for the bean instances that were created from the XML file. An API is provided to query these beans and use them accordingly from our client application.

For example, using the API method ctx.getBean("reader"), you can access the respective bean instances. That’s exactly what we are doing in our client, in the fetchData method by using the statement: reader = (IReader) ctx.getBean("reader");

The bean obtained is a fully instantiated FileReader bean, so you can invoke the methods normally: reader.read().

So, to wrap up, here are the things that we have done to make our program work without dependencies by using Spring:

  • We have implemented IReader creating a FileReader class as a simple POJO.

  • We have created a simple XML configuration file to declare this bean.

  • We then created a container (using the ApplicationContext) with this single bean by reading the XML file.

  • We queried the container to obtain our fully qualified instance of the FileReader class so we can invoke the respective methods.

Simple and straightforward, eh?

One quick note to the curious here: usually we use the new operator to create a brand new Object in Java. In the above client code, you don’t see us using new operator to create a FileReader. The framework has taken over this responsibility for us—it creates these objects by peeking at the declared definitions in the XML configuration file.

Before we wind up the chapter, there’s one more thing we should look at. The client and the reader are still coupled! That is, the client will always be injected with a type of IReader defined in configuration. Ideally, the client should not have to know about the IReader—instead, we can have a service layer to disassociate the reader with the client. This way the service would be glued to the client rather than the IReader. Let’s do this by creating a service.

ReaderService

The ReaderService is a simple class which abstracts away the implementation details of the IReader from the client. The client will only have knowledge of the service; it will know nothing about where the service is going to get the data from. The first step is to write the service. This snippet is the our new service class:

public class ReaderService {
  private IReader reader = null;

  public ReaderService(IReader reader) {
    this.reader = reader;
  }

  public String fetchData() {
    return reader.read();
  }
}

The service has a class variable of IReader type. It is injected with an IReader implementation via the constructor. It has just one method—fetchData which delegates the call to a respective implementation to return the data.

The following XML file wires the ReaderService with an appropriate IReader in our basics-reader-beans.xml file:

<beans>
  <bean name="readerService" 
    class="com.madhusudhan.jscore.basics.service.ReaderService">
    <constructor-arg ref="reader" />
  </bean>

  <bean name="reader" 
    class="com.madhusudhan.jscore.basics.readers.FileReader">
    <constructor-arg 
      value="src/main/resources/basics/basics-trades-data.txt" />
  </bean>
</beans>

When this config file is read by the Spring’s ApplicationContext in your program, the ReaderService and FileReader beans are instantiated. Note that, as the ReaderService has a reference to a reader (constructor-arg ref="reader"), the FileReader is instantiated first and injected into ReaderService. If for what ever reason the FileReader was not instantiated, the framework throws an exception and quits the program immediately—hence the framework is said to be designed as fail-fast.

Here is the modified ReaderServiceClient:

public class ReaderServiceClient {
  private ApplicationContext ctx = null;
  private ReaderService service =  null;

  public ReaderServiceClient() {
    ctx = new ClassPathXmlApplicationContext("basics-reader-beans.xml");
    service = (ReaderService) ctx.getBean("readerService");
  }

  private String fetchData() {
    return service.fetchData();
  }

  public static void main(String[] args) {
    ReaderServiceClient client = new ReaderServiceClient();
    System.out.println("Got data using ReaderService: " + client.fetchData());
  }
}

The notable thing is that the client will only have knowledge of the service—no Readers whatsoever—thus achieving the true decoupling of the software components.

If you wish to read data from a database, remember that except for config, no other code changes are required—enable an appropriate reader in the config and re-run the program. For example, in the next snippet, the FileReader is now swapped with a DatabaseReader—without changing even single line of code—by just modifying the meta data.

This is shown here:

<bean name="readerService" 
    class="com.madhusudhan.jscore.basics.service..ReaderService">
  <!-- The reader now refers to DBReader -->
  <property name="reader" ref="reader"/>
</bean>

 <!-- *** DBReader ** -->
 <bean name="reader" 
    class="com.madhusudhan.jscore.basics.readers.DatabaseReader">
    <property name="dataSource" ref="dataSource" />
 </bean>
  
  <!-- Datasource that DBReader depends -->
 <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
   .... 
 </bean>

The ReaderService is given a reference to the respective IReader dynamically when the program is run.

Summary

This chapter introduced the Spring Framework from the 10,000-foot view. We have seen the problem of object coupling and how the framework solved the dependency issues. We also glanced at a framework’s containers and scratched the surface of the framework’s usage, leaving many of the fundamentals to the upcoming chapters.

We are going to see the internals of the framework in depth in the next chapter.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required