Java Webapps without Spring

Java Webapps without Spring

Course: Learning Spring

Last updated on March 16, 2020 - +

What is this module about?

Before jumping right into Spring framework or Spring Boot, it makes sense to spend a couple of exercises on building web applications with plain Java.

That might sound like an unneeded, boring detour. Why not jump straight into Spring?

Because Spring builds on top of these basics and the Java basics are tremendously helpful in understanding what Spring is all about. It helps you figure out what is good about Spring, what is bad about Spring and how Spring fits into the whole Java ecosystem.

In other words: This detour will set the foundation for you becoming a Spring master.

The problem

Imagine a website, like the very website you are on right now. Customers can buy courses or e-books on that website and in addition to their purchase (like ebook.zip), customers also want to get a PDF invoice for their purchase.

Now, I, as the owner of the website, could chose to create these invoices manually with MS Word. Or, integrate with a web-based product called MyFancyPDF (random name), which offers me a REST API that I can call and which creates these invoices for me.

A simplified call to create an invoice for 50$ for user with id 1 would look like this:

POST /invoice?userid=1&amount=50

As a result of that REST call, you’ll get back a JSON object, which contains the generated PDF’s url.

{
  "userId": 1,
  "amount": 50,
  "pdfUrl": "https://somecdn/invoice-1-001.pdf"
}

Your goal

You are going to build that MyFancyPDF REST service in this module and fake the actual PDF generation. This way you can learn a lot about building actual, useful web apps with plain Java and not have to worry too much about details on how specifically e.g. PDF generation works.

Along the way, you are also going to build a very small dependency injection "framework", that you are going to replace with Spring in the next module.

Enough theory. Make sure to have your IDE open, as we will start coding immediately. Ready?

Let’s begin.

How to setup the course webapp from scratch

Using Maven to setup a Java webapp

To start off, we need to setup an empty, Java webapp project.

As you have Maven installed on your machine, go to any folder of your liking and then create a folder for your project, myfirstspringproject in my case.

c:\dev\myfirstspringproject
c:\dev\myfirstspringproject\pom.xml

Then make sure to create a pom.xml file inside this folder and edit the file to have the following contents.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.marcobehler</groupId> (1)
  <artifactId>myfirstspringapp</artifactId> (2)
  <version>1.0-SNAPSHOT</version>

 <packaging>jar</packaging> (3)

  <properties>
    <maven.compiler.source>13</maven.compiler.source> (4)
    <maven.compiler.target>13</maven.compiler.target> (4)
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
         (5)
  </dependencies>
</project>
  1. You can pick any groupId you want, I’m defaulting to com.myName.

  2. You can pick any artifactId you want, but as it is your first spring application, I’m calling it myfirstspringapp.

  3. You can leave out this tag, but if you put it in, make sure it is set to "jar". Historically, web applications were meant to be packaged in a .war file and put into a servler container, but lately, frameworks like Spring Boot have changed that and will produce .jar files that you can run with "java -jar yourjarfile.jar". We will pick the same route in this course.

  4. I have Java 13 installed on my machine, hence it says 13 in the pom.xml file in the compiler tags section. Make sure to replace this number with the one you have installed and also make sure that up until version 9, you need to specify the number as 1.x. So, 1.7 or 1.8 for example.

  5. Leave the dependencies tag empty for now, we’ll add many more third party libraries later on.

Checkpoint: Using Maven to setup a Java webapp

To find out if you are on the right track, go to your new directory and execute the "mvn validate" command.

cd c:\dev\myfirstspringproject
mvn validate

You should see something like this:

C:\dev\myfirstspringproject> mvn validate
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< com.marcobehler:myfirstspringapp >------------------
[INFO] Building myfirstspringapp 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.085 s
[INFO] Finished at: 2020-02-12T14:09:47+01:00
[INFO] ------------------------------------------------------------------------

How to display HTML pages with HttpServlets

Displaying Web Pages with HttpServlets

Up next, let’s assume that everything our Java web application is supposed to do, is to display a web page. The way to do this, or rather (almost) anything web-related in Java, is through the Servlet API.

You’ll need to do two things for that:

  1. A servlet container, something that can "run" servlets and put them under http://localhost:8080. You are going to use an embedded Tomcat here.

  2. The HttpServlet itself.

As for the embedded Tomcat, open up your pom.xml file and add this block to get the very latest, stable Tomcat 9.0.31 version added to your project. The exact minor version does not matter, but be sure to pick a 9.x or even 8.x version, nothing older.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <!-- ..... -->

  <dependencies>
     <dependency>  (1)
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-core</artifactId>
      <version>9.0.31</version>
    </dependency>
  </dependencies>
</project>
  1. If you are unsure about this whole XML block in the first place, check out this Apache Maven guide.

This library is enough to start writing web applications.

Now, create the following directories that Maven expects to work properly and make sure to replace the marcobehler directory with your name.

## Windows
cd c:\dev\myfirstspringproject
mkdir src\main\java\com\marcobehler

## Linux
cd /home/marco/myfirstspringproject
mkdir -p src/main/java/com/marcobehler

Then it’s finally time to create a HTTPServlet, which prints out a very simple HTML page.

package com.marcobehler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class MyServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        resp.setContentType("text/html; charset=UTF-8"); // (2)
        resp.getWriter().print("<html>\n" +  // (1)
                "<body>\n" +
                "<h1>This is my HTML page</h1>\n" +
                "<p> hallo what is going on!!!</p>\n" +
                "</body>\n" +
                "</html>");
    }
}
  1. Yes, you are indeed constructing a HTML page by hand, with good old String concatenation and sending that String directly to the browser by writing it to the HttpServletResponse. This isn’t what you would do in real-life, but is a great start for your Spring application to grow.

  2. You are setting the Content-Type header to let the browser know you are sending it text/html. For json, you would put application/json here, for xml application/xml etc.

Starting Tomcat & Registering your HttpServlet

You need to start Tomcat, and you also need to tell it about your HttpServlet. For that you best create an ApplicationLauncher class, with a simple main method, that reads like this:

package com.marcobehler;

import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.Wrapper;
import org.apache.catalina.startup.Tomcat;

public class ApplicationLauncher {

    public static void main(String[] args) throws LifecycleException {
        Tomcat tomcat = new Tomcat(); // (1)
        tomcat.setPort(8080);       // (2)
        tomcat.getConnector();      // (3)

        Context ctx = tomcat.addContext("", null); // (4)
        Wrapper servlet = Tomcat.addServlet(ctx, "myServlet", new MyServlet()); // (5)
        servlet.setLoadOnStartup(1);
        servlet.addMapping("/*"); // (6)

        tomcat.start(); // (7)
    }
}
  1. Instantiating a new Tomcat is as simple as calling "new Tomcat". This does not run the Tomcat just yet, but it lets you configure it.

  2. In the Java world, starting servlet containers on port 8080 is the convention. You could however put any port here you like.

  3. This line looks like it does nothing, but it actually bootstraps Tomcat’s HTTP support. It is unfortunately needed and a side-product of the API being a bit whacky.

  4. Historically, you could run many different web applications under differents paths, called "context". For example, two different web applications in the same Tomcat living under http://localhost/webapp1 and http://localhost/webapp2. As we only have one application and therefore don’t want a context in our application, we simply put in "" as contextPath.

  5. Here, you are adding your Servlet to Tomcat. The second parameter, the servlet name, does not really matter, as long as it does not clash with another registered servlet.

  6. Here, you are telling Tomcat that your Servlet should react to any request that starts with "/", i.e. any incoming request, be it /register, /login, /myaccount etc.

  7. Last but not least, you need Tomcat to start.

Checkpoint: HttpServlets & Webpages

If everything worked well, you should be able to run the main method of your ApplicationLauncher class, open up your browser, go to localhost:8080 and see the following picture:

Hello World Servlet

If not, make sure to verify the previous checkpoints or send an e-mail to ask@marcobehler.com if you completely get stuck.

Up next, we’re going to make our Servlet speak some JSON!

How to write JSON endpoints with HttpServlets

Building a JSON endpoint for browser GET requests

Writing a dummy HTML page is a nice achievement, but let’s continue working on some real business functionality.

As mentioned at the beginning of this module, imagine that you are running a REST service that lets other applications create invoices. To generate an invoice, you (for now) merely need a userId and an amount as input parameters. And you’ll get a JSON object back with the generated PDF’s url.

{
  "userId": 1,
  "amount": 50,
  "pdfUrl": "https://somecdn/invoice-1-001.pdf"
}

The REST service should not only contain an endpoint that lets you generate invoices, but also an endpoint that lets you get a list of all generated invoices.

Let’s start with the easier endpoint, getting the invoices, which you should be able to do whenever issueing a GET request to http://localhost:8080/invoices.

Remember, we have one Servlet at the moment, which listens to any incoming request. Hence, we need to refactor it to respond differently to different incoming request URIs, like "/" or "/invoices".

This is what that refactoring is going to look like:

package com.marcobehler;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class MyServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        if (req.getRequestURI().equalsIgnoreCase("/")) {
            resp.setContentType("text/html; charset=UTF-8");
            resp.getWriter().print("<html>\n" +
                    "<body>\n" +
                    "<h1>This is my HTML page</h1>\n" +
                    "<p> hallo what is going on!!!</p>\n" +
                    "</body>\n" +
                    "</html>");
        }
        else if (req.getRequestURI().equalsIgnoreCase("/invoices")) {
            resp.setContentType("application/json; charset=UTF-8"); // (1)
            resp.getWriter().print("[]");  // (2)
        }
    }
}
  1. We are setting the content-type of the HTTP response to JSON, to let the browser know that JSON is coming back from this servlet, instead of HTML.

  2. We are faking an empty JSON array here, as we do not have any invoices yet in our system.

Checkpoint: Building a JSON endpoint for browser GET requests

Restart your application (i.e. the main method of your ApplicationLauncher) and go to http://localhost:8080/invoices. You should see the empty [] JSON array in your browser.

Building a JSON endpoint for browser POST requests

Before we replace our empty, fake JSON array with real invoices, we need to let the User create new ones, which means we need to build a new REST endpoint.

For now, this will work by sending multipart form-data as a POST request to a new /invoices endpoint (remember, we are only handling GET requests for the moment in our servlet). This means:

  1. We need a new doPost() handler method in our HttpServlet.

  2. Our method needs to be able to read HttpServlet request parameters for userId and amount.

  3. It also needs to create an invoice (object). We have yet to create an invoice class.

  4. It also needs to convert that invoice object to JSON and we need a third-party library for that, as Java doesn’t offer convenient JSON conversions out of the box.

Let’s start from the reverse and add a JSON library to our project. One of the most popular choices in the Java ecosystem to do JSON conversions is called Jackson.

You’ll need to add the following dependency to your pom.xml to add the latest Jackson version to your project.

<!-- ... -->
<dependencies>
  <!-- ... -->
  <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.10.2</version>
  </dependency>
</dependencies>

Next, let’s write the Invoice class. For now, let’s assume an invoice has three properties:

  1. A unique id.

  2. A PDF Url. We are NOT going to implement actual PDF generation in this course, but will return a dummy PDF (url) whenever a user creates a new invoice.

  3. The user’s id.

package com.marcobehler;

import java.util.UUID;

public class Invoice {

    private String id, pdfUrl, userId;

    private Integer amount;

    public Invoice() {
    }

    public Invoice(String userId, Integer amount, String pdfUrl) { // (1)
        this.id = UUID.randomUUID().toString();
        this.pdfUrl = pdfUrl;
        this.userId = userId;
        this.amount = amount;
    }

    // Getter & Setter omitted
}
  1. This is a helper constructor, which will automatically generate a random invoice ID, whenever you pass in a userId and pdfUrl.

The actual invoice creation is going to happen in an InvoiceService, which will look like this:

package com.marcobehler;

public class InvoiceService {

    public Invoice create(String userId, Integer amount) { // (1)
        // TODO real pdf creation and storing it on network server
        return new Invoice(userId, amount, "http://www.africau.edu/images/default/sample.pdf"); // (2)
    }
}
  1. The InvoiceService has one method, create, which takes two parameters.

  2. It will then simply create a new invoice object, and as I have mentioned before, we will always return the same dummy PDF (which I found as the very first search result on Google while looking for sample.pdf)

Alright, we have the Invoice domain class, our InvoiceService and a JSON library added to our project. Now it’s time to hook everything up in our MyServlet.

import com.fasterxml.jackson.databind.ObjectMapper;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


public class MyServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
            // code omitted for brevity. see above
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws  IOException {
       if (req.getRequestURI().equalsIgnoreCase("/invoices")) {

           String userId = req.getParameter("userId");                          // (1)
           Integer amount = Integer.valueOf(req.getParameter("amount"));       // (2)

           Invoice invoice = new InvoiceService().create(userId, amount);   // (3)

           resp.setContentType("application/json; charset=UTF-8");
           String json = new ObjectMapper().writeValueAsString(invoice); // (4)
           resp.getWriter().print(json);
        }
        else {
           throw new IllegalStateException("no url  mapped");
       }
    }
}
  1. We are assuming and getting a request parameter called userId, that the user hopefully submits when posting to our servlet. Before deploying our project in production, we would actually need to make sure to put in some validation (null checks) here - which we’ll skip for the moment, but add in when we Springify our little project.

  2. The same goes for the amount. Even worse, here we have to explicitly convert the amount from a String to an Integer and if the amount is missing you would get an ugly exception.

  3. We are simply creating a new InvoiceService instance here and create a new invoice object with that.

  4. Last but not least, we need to convert our Invoice object to a JSON string. We can do that by creating a Jackson specific ObjectMapper, which can take our Invoice object and can create a nice, little JSON object from it.

Checkpoint: Posting to our JSON endpoint

With the above code, restart your application and execute a POST request against your new /invoices endpoint. You can do that with a browser plugin like Postman or Intellij’s internal REST Client.

When using Intellij’s new REST client, you will execute a request that looks like this:

POST http://localhost:8080/invoices?userId=1&amount=50
Accept: application/json
Cache-Control: no-cache

###

Which in turn should return you a JSON object that looks like this: Your first invoice!

{
  "id": "b810261e-d290-4407-971a-ce178fe06c31",
  "pdfUrl": "http://www.africau.edu/images/default/sample.pdf",
  "userId": "1",
  "amount": 50
}

Great, you can create invoices, now let’s fix up the GET endpoint that you wrote before.

Refactoring our web application

The InvoiceService so far only contains a method to create new invoices. Let’s give it another method that finds all existing invoices.

We are going to use a simple, in-memory list to store our invoices (instead of using something more complex like a database).

package com.marcobehler;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class InvoiceService {

    List<Invoice> invoices = new CopyOnWriteArrayList<>(); // (1)

    public List<Invoice> findAll() {
        return invoices;
    }

    public Invoice create(String userId, Integer amount) {
        // TODO real pdf creation and storing it on network server
        Invoice invoice = new Invoice(userId, amount, "http://www.africau.edu/images/default/sample.pdf");
        invoices.add(invoice); // (2)
        return invoice;
    }
}
  1. This is our thread-safe list.

  2. You need to make sure to add new invoices to that list as well, whenever creating them via the REST API.

Remember, though, that are still returning a fake, empty [] array in our servlet. We need to change that by calling our InvoiceService and turning the resulting list into JSON with the help of Jackson’s ObjectMapper.

While we are at it, we can also refactor our servlet a bit, to not create new instances of these services all the time. The final servlet code will look like this:

package com.marcobehler;

import com.fasterxml.jackson.databind.ObjectMapper;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

public class MyServlet extends HttpServlet {

    private InvoiceService invoiceService = new InvoiceService();  // (1)
    private ObjectMapper objectMapper = new ObjectMapper(); // (1)

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        if (req.getRequestURI().equalsIgnoreCase("/")) {
            resp.setContentType("text/html; charset=UTF-8");
            resp.getWriter().print("<html>\n" +
                    "<body>\n" +
                    "<h1>This is my HTML page</h1>\n" +
                    "<p> hallo what is going on!!!</p>\n" +
                    "</body>\n" +
                    "</html>");
        } else if (req.getRequestURI().equalsIgnoreCase("/invoices")) {
            resp.setContentType("application/json; charset=UTF-8");
            List<Invoice> invoices = invoiceService.findAll();  // (2)
            resp.getWriter().print(objectMapper.writeValueAsString(invoices));  // (3)
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        if (req.getRequestURI().equalsIgnoreCase("/invoices")) {

            String userId = req.getParameter("userId");
            Integer amount = Integer.valueOf(req.getParameter("amount"));

            Invoice invoice = invoiceService.create(userId, amount);  // (4)

            resp.setContentType("application/json; charset=UTF-8");
            resp.getWriter().print(objectMapper.writeValueAsString(invoice));  // (5)
        } else {
            throw new IllegalStateException("no url  mapped");
        }

    }
}
  1. Our Servlet has two new variables, the invoiceService and objectMapper, so we do not need to instantiate them all the time whenever someone calls the doGet or doPost method. One instance of these services is enough.

  2. This is where we wrote the fake [] JSON array. We replaced that by calling our new invoiceService method.

  3. We also need to convert the list into JSON, which Jackson’s ObjectMapper will happily do for us.

  4. We replaced new InvoiceService() with our invoiceService variable.

  5. The same for our ObjectMapper.

Checkpoint: Full REST Service

Restart the application. Open up your favorite REST client and do the following 2x, to create two new invoices.

POST http://localhost:8080/invoices?userId=1&amount=50
Accept: application/json
Cache-Control: no-cache

###

Then do a GET, which should return exactly two invoices.

GET http://localhost:8080/invoices
Accept: application/json

HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Content-Length: 239
Date: Wed, 12 Feb 2020 16:07:07 GMT
Keep-Alive: timeout=20
Connection: keep-alive

[
  {
    "id": "1b8e9a25-2e70-4a94-8324-bb92f773e304",
    "pdfUrl": "http://www.africau.edu/images/default/sample.pdf",
    "userId": "1",
    "amount": 50
  },
  {
    "id": "f0baebc9-cf53-447d-bd2d-7d52e511902b",
    "pdfUrl": "http://www.africau.edu/images/default/sample.pdf",
    "userId": "1",
    "amount": 50
  }
]

You can play around with your REST client and create a couple more invoices, which you should always be able to get via your GET /invoices endpoint.

Don’t worry that the pdfURL is hardcoded at the moment, we will fix that later on. The ID, however, is unique, so you know that you are indeed creating new, separate invoices.

Poor Man’s Dependency Injection

Building a global Application class

In the last step, we gave our MyServlet two new variables, the invoiceService and objectMapper. This is fine if we only have one HTTPServlet and no-one else needs to access these two classes.

But what if some other class needs to convert Java<→JSON? Then they also need an ObjectMapper. And what if some other service, like a batch job, needs access to our InvoiceService? Then having private fields in the MyServlet class won’t cut it and you’re likely going to implement the Application class pattern (without Spring), a poor man’s version of dependency management.

Look at what an Application class singleton could look like:

package com.marcobehler;

import com.fasterxml.jackson.databind.ObjectMapper;

public class Application { // (1)

    public static final InvoiceService invoiceService = new InvoiceService(); // (2)
    public static final ObjectMapper objectMapper = new ObjectMapper(); // (2)
}
  1. Create a class called Application, which for now will only have static final fields, effectively making them all singletons.

  2. Just move the InvoiceService and ObjectMapper from your Servlet to here.

We can now clean up our servlet. Instead of creating the services itself, it can simply get them from the Application class.

The major advantage being, that now any other class you are going to write can access the same services as well, going through the application class.

package com.marcobehler;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

public class MyServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        if (req.getRequestURI().equalsIgnoreCase("/")) {
            resp.setContentType("text/html; charset=UTF-8");
            resp.getWriter().print("<html>\n" +
                    "<body>\n" +
                    "<h1>This is my HTML page</h1>\n" +
                    "<p> hallo what is going on!!!</p>\n" +
                    "</body>\n" +
                    "</html>");
        } else if (req.getRequestURI().equalsIgnoreCase("/invoices")) {
            resp.setContentType("application/json; charset=UTF-8");
            List<Invoice> invoices = Application.invoiceService.findAll();  // (1)
            resp.getWriter().print(Application.objectMapper.writeValueAsString(invoices));  // (2)
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        if (req.getRequestURI().equalsIgnoreCase("/invoices")) {

            String userId = req.getParameter("userId");
            Integer amount = Integer.valueOf(req.getParameter("amount"));

            Invoice invoice = Application.invoiceService.create(userId, amount);  // (3)

            resp.setContentType("application/json; charset=UTF-8");
            resp.getWriter().print(Application.objectMapper.writeValueAsString(invoice));  // (4)
        } else {
            throw new IllegalStateException("no url  mapped");
        }

    }
}

Checkpoint: Application class

Make sure to restart your application and to check if POSTing/GETing your endpoints still works!

Adding more classes and complexity

Let’s add a bit more complexity to our InvoiceService. So far it accepts a userId and will gladly generate invoices for that user.

That’s however not what happens in the real world. Why? Because you need to make sure, that the User actually exists in your database!

So, let’s add a very simple user domain object to our application, with just two fields, id and name.

package com.marcobehler;

public class User {

    private String id;
    private String name;

    public User() {
    }

    public User(String id, String name) {  // (1)
        this.id = id;
        this.name = name;
    }

   public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  1. This is a convenience constructor, so that we can create Users faster.

With Users alone we cannot do much, we also need a corresponding UserService that lets us find users. We are going to fake that UserService for now, by always returning a User. (We’ll add database access later on).

package com.marcobehler;

public class UserService {

    public User findById(String id) {
        return new User(id, "john"); // always finds the user, every user is called john
    }

}

Now, let’s put that validation check into our InvoiceService.

package com.marcobehler;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class InvoiceService {

    List<Invoice> invoices = new CopyOnWriteArrayList<>();

    public List<Invoice> findAll() {
        return invoices;
    }

    public Invoice create(String userId, Integer amount) {
        User user = new UserService().findById(userId);  // (1)
        if (user == null) {
            throw new IllegalStateException(); // (2)
        }

        // TODO real pdf creation and storing it on a network server

        Invoice invoice = new Invoice(userId, amount, "http://www.africau.edu/images/default/sample.pdf");
        invoices.add(invoice);
        return invoice;
    }
}
  1. Before we create an invoice, we are now constructing a new UserService and checking if that users exists.

  2. If the user does not exist, we simply throw an Exception.

Actually, creating a new UserService instance in there does not make too much sense, we have that global Application class which keeps track of all our classes. So let’s do some refactorings.

Refactoring the global Application class

The first step is pretty simple, you’ll need to add the UserService to the Application class.

package com.marcobehler;

import com.fasterxml.jackson.databind.ObjectMapper;

public class Application { // (1)

    public static final InvoiceService invoiceService = new InvoiceService();
    public static final ObjectMapper objectMapper = new ObjectMapper();
    public static final UserService userService = new UserService();
}
  1. Adding the UserService field.

Which means we can also change the corresponding line in our InvoiceService.

package com.marcobehler;

public class InvoiceService {

    public Invoice create(String userId, Integer amount) {
        User user = Application.userService.findById(userId);  // (1)
        // ...
    }
}
  1. We are actively calling the Application class to give us an instance of the UserService.

This is in line with our Application class, but things got ugly again. Why?

Because our InvoiceService now needs to actively call the Application again, to get an instance of the UserService. In short, it has to know about the UserService and where it can get a UserService from.

Wouldn’t it be nicer if the InvoiceService didn’t have to care about that and could simply use a UserService? Actually, that would be great and we can move our little application towards that dream by preparing it for dependency injection.

Dependency Injection First Steps

First, let’s refactor our InvoiceService so that anyone who constructs a new InvoiceService also needs to pass in a valid UserService! Our InvoiceService will look like this:

package com.marcobehler;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class InvoiceService {

    List<Invoice> invoices = new CopyOnWriteArrayList<>();

    private final UserService userService; // (1)

    public InvoiceService(UserService userService) { // (2)
        this.userService = userService;
    }

    public List<Invoice> findAll() {
        return invoices;
    }

    public Invoice create(String userId, Integer amount) {
        User user = userService.findById(userId);  // (3)
        if (user == null) {
            throw new IllegalStateException();
        }

        // TODO real pdf creation and storing it on network server

        Invoice invoice = new Invoice(userId, amount, "http://www.africau.edu/images/default/sample.pdf");
        invoices.add(invoice);
        return invoice;
    }
}
  1. We added a new final UserService field to the InvoiceService class.

  2. From now on, you need to pass it into the constructor to create new InvoiceServices.

  3. The create method then simply uses that UserService.

Much nicer, isn’t it? The InvoiceService doesn’t have to care about plumbing anymore but can be guaranteed to get a working UserService injected.

We now need to fix the Application class, as it will not compile anymore.

package com.marcobehler;

import com.fasterxml.jackson.databind.ObjectMapper;

public class Application {

    public static final UserService userService = new UserService(); // (1)
    public static final InvoiceService invoiceService = new InvoiceService(userService);  // (2)
    public static final ObjectMapper objectMapper = new ObjectMapper();
}
  1. Make sure that the UserService gets created BEFORE the InvoiceService.

  2. "Inject" the userService into the InvoiceService, when creating it.

Better! But imagine for a second what happens, if our UserService also had other dependencies, i.e. classes that it needs to work properly. And more services in general, that all interdepend on each other?

Then putting everything into that one Application class will get rather cumbersome and unwieldy.

That is exactly where Spring comes in, to replace that Application class. Let’s see how that works in the next module!

What you learned in this module

  • How to write simple Java webapps with an embedded Tomcat and the Servlet API

  • How to actively or passively handle dependencies between classes in Java

  • How to create this course’s project in plain Java

Congratulations, you successfully finished this module!

You can now proceed directly to the next module.

Discussion:

Buy for $200

Due to the Coronavirus pandemic all courses are 33% off.

Offer valid through June 1st. Regular Price: $300 .