Overview of This Lesson

Refresh this page because I am probably still making changes to it.

API Used in this Lesson:

Review

Pre-Requisites

Make sure you've read over and done the exercises in the lessons on Intro to Java Web Development and Spring Boot Projects.

Controllers and Handler Methods

Recall that the Controller is part of the MVC design pattern, and it's job is perform tasks between the view and the model, to do the main processing parts of your application.

In a Spring Boot application, the controller intercepts incoming HTTP requests. It has access to the data and headers of the request, so this means it can process that data in some way. The controller can also send that processed data/results to any View that is part of the application.

A controller class must always have the @Controller annotation above the class header. For example, in your first project, you had the following controller class:

package ca.sheridancollege.jollymor.controllers;

import org.springframework.stereotype.Controller;

@Controller
public class MainController {

    @GetMapping("/test")
    public String testing() {
         return "templatePage.html";
     }

}

The class is called MainController and you can see that it has the @Controller annotation right above the class header. If the class doesn't have @Controller, it's not a controller and won't act like one. Note that when you add the @Controller annotation, you'll also need to import org.springframework.stereotype.Controller.

The controller class contains one or more handler methods. Handler methods handle HTTP requests, so they will execute whenever a URL matches a specific pattern. For example, in the above code, if the URL requested is http://localhost:8080/test, the method will execute.

You can have more than one controller in an application. You might have so many handler methods that you'll want to separate them into different controllers according to what they do or what sets of pages they access. For example, a BookController might contain handler methods dealing with all the book pages and book data, and the BorrowController might contain all the handler methods that deal with the pages where clients borrow the books and return them.

Handler Mapping

In the example up above, the controller has one method called testing(). This is a handler method. The handler method returns the name of the View that should load: templatePage.html.

How does the handler method know when it should execute? A typical controller could have several handler methods, so which one executes at any given time?

Controllers and handler methods are mapped to URLs. For example, you could specify that if the url ends with "/book", the BookController's methods are used. Or you could specify that if a URL ends with "/test", a specific handler method executes.

We map handler methods to URLs by using URL patterns.

In our code example, we're defining a URL Pattern using the @GetMapping annotation. The @GetMapping annotation indicates that you want to map a specific pattern in the request URL to the handler method that's immedately below the @GetMapping annotation. So in our example, we're saying that when the request URL ends with "/test", we want to map that to the handler method testing(). In other words, if the URL is http://localhost:8080/test, execute the testing() method.

Note that if you wanted to use POST instead of GET, you'd use the @PostMapping annotation. In fact, a mapping is not just about the URL pattern, but the method the request uses as well. For example:

@GetMapping("/test")
public String getPage() {
    return "page1.html";
}

@PostMapping("/test")
public String postPage() {
    return "page2.html";
}

In the above example, if the URL http://localhost:8080/test is retrieved using the GET method, then getPage() executes and page1.html is loaded. If the exact same URL is requested using the POST method, then postPage() executes and page2.html is loaded.

URL Patterns

A URL pattern is simply a pattern or expression that a request URL needs to match in order to execute a particular handler method.

Note that each of our patterns starts with the forward-slash "/" character, such as "/text" or "/foo" or even just "/". This is important! When matching patterns to a request URL, any parameters in the URL are removed along with the context path before the rest of the URL is checked for a match. The context path is the location of your application on the server. For example, when you're testing your applications, the context path is http://localhost:8080. If your application was running at http://www.petstore.ca/shop/cart.html, the context path is http://www.petstore.ca

Note that a context path for an application might also include a directory. For example, if www.petstore.ca had an inventory application and an online store application, then there might be a context path of http://www.petstore.ca/Inventory for the Inventory application and http://www.petstore.ca/shop for the Online Store application. Generallly, in these cases, the extra directory name (e.g. "/Inventory" or "/shop" is the same as, or similar to, the application name.

URL patterns don't have to be for a specific directory, like in the examples we've seen so far, e.g. "/test". You can use file names, wildcard symbols, and even regular expressions to match specific URLs.

There are a couple of other things you can use in mappings but we'll learn those in an upcoming lesson. Feel free to check out the PathPattern documentation for more information.

Once the context path and parameters are removed from the request URL, the controller will match the rest of the requested URL to the pattern. For example, a pattern "/shop", used in the application http://www.petstore.ca will match http://www.petstore.ca/shop. However, http://www.petstore.ca/catalog/shop would be an invalid URL because once you remove the context path http://www.petstore.ca, you're left with "/catalog/shop" and that doesn't match the pattern "/shop".

What if there is more than one match?

What happens if a request matches more than one of your URL patterns? For example, let's say we have the following mappings:

@GetMapping("/thing?")
public String getPage1() {
    return "page1.html";
}
@GetMapping("/things")
public String getPage2() {
    return "page2.html";
}

In the above example, a URL that ends with "thing" followed by any single character will execute getPage1() and load page1.html. Similarly, a URL that ends with "things" will execute getPage2() and load page2.html.

This means the URL http://localhost:8080/things actually matches both "/thing?" and "/things". Which one executes? If you try it out, you'll notice that page 2 loads.

The most specific pattern will always match first: "/things" is more specific than "/thing?" because the "s" is a specific letter but "?" is any character.

Sometimes it's hard to tell which pattern is more specific. For example, when I was writing these examples, I wondered what would happen if I had:

@GetMapping("/thing?")
public String getPage1() {
    return "page1.html";
}
@GetMapping("/{t:thing\\w}")
public String getPage2() {
    return "page2.html";
}

Here, the first pattern "/thing?" matches "thing" followed by a single character. The second pattern "/{t:thing\\w}" matches "thing" followed by any single word-character. They're the same thing, pretty much! I had no idea which one would be considered "more specific", so I just tried it! I found that with this example, page1.html always loaded. So clearly it considers "/thing?" more specific (probably because the second pattern uses a regex?) Luckily, this kind of example is very unlikely, in fact you shouldn't have 2 patterns like this, because they're just two different ways of writing the exact same pattern!

What if the URL doesn't match any pattern? For example, what if I have a single method mapped to "/foo" and the user enters the request URL http://localhost:8080/food ? Assuming there is no other mapping to any other handler method, a 404 error will occur.

Exercises

1. Given the following Controller code:

@GetMapping("/")
public String home() {
  return "index.html";
}
@GetMapping("/orders/**")
public String shop() {
   return "shop/index.html";
}
@GetMapping("*.frm")
public String getPage() {
   return "forms.html";
}
@GetMapping("/user/*")
public String user() {
  return "users.html";
}
@GetMapping("/user?")
public String userMgmt() {
    return "manageUsers.html";
}
@GetMapping("/inv/{str:[a-zA-Z]{3}product}")
public String getProductPage() {
     return "products/index.html";
}

Indicate which page will load with the given request URLs below, for an application at sydney.cattrees.ca. If a request URL doesn't match a pattern, it's an error (specifically, a 404 error). Determine the answer for each one first, then you can try coding the above handlers in a controller and try each URL to check your answer (just replace each url's domain with localhost:8080) or check the answers below.

  1. http://sydney.cattrees.ca/orders
  2. http://sydney.cattrees.ca/orders/index.html
  3. http://sydney.cattrees.ca/orders/foo/index.html
  4. http://sydney.cattrees.ca/orders/newOrder.frm
  5. http://sydney.cattrees.ca/register.frm
  6. http://sydney.cattrees.ca/users
  7. http://sydney.cattrees.ca/useracct
  8. http://sydney.cattrees.ca/user/acct/index.html
  9. http://sydney.cattrees.ca/user
  10. http://sydney.cattrees.ca/
  11. http://sydney.cattrees.ca/inv/product
  12. http://sydney.cattrees.ca/inv/abcproduct
  13. http://sydney.cattrees.ca/inv/abcproduct.html
  14. http://sydney.cattrees.ca/abcproduct
  15. http://sydney.cattrees.ca/abcproduct.frm
  1. shop/index.html
  2. shop/index.html
  3. shop/index.html
  4. shop/index.html (because it's looking for a specific .frm in a specific directory, whereas the /*.frm pattern is more general, looking for any .frm file anywhere)
  5. forms.html
  6. manageUsers.html
  7. 404
  8. 404
  9. 404
  10. index.html
  11. 404
  12. products/index.html
  13. 404
  14. 404
  15. forms.html

2. Create a new project called MyShop.

Pretend you sell things. What are you selling? You decide. Pick something that's fun or that interests you.

Optional (for now):

If you would like to add images to any of your pages, make sure you have them in the normal /images folder in your /static directory, first.

To add images to a static page, you just use the IMG element like you normally would, nothing new here.

To add images to a template page, you must use Thymeleaf. We'll learn Thymeleaf in an upcoming lesson, but here's a little bit to get you started. First, you must reference the Thymeleaf namespace inside your page's HTML tag:

<html xmlns:th="https://www.thymeleaf.org">

Next, you create the normal image tag, but note the src attribute value:

<img src="../static/images/mypicture.jpg" alt="accessible description here">

The above will ensure that your image will work if Thymeleaf stops working for some reason (not likely, but not impossible).

Of course, Thymeleaf should always be working, in which case the regular src attribute won't function properly - we use a special Thymeleaf attribute:

<img src="../static/images/mypicture.jpg" alt="accessible description here"
th:src="@{/images/mypicture.jpg}>

In the Thymeleaf lesson, we'll learn exactly what the th:src is and how it works, but don't worry about it for now.

You can do something similar with an external CSS file: assuming you follow the industry standard and have a /css or /styles directory in your /static directory, you can link to a CSS file from any static page normally. From a /templates page, you would use a link tag such as:

<link rel="stylesheet" type="text/css" media="all"
href="../static/css/main.css" th:href="@{/css/main.css}">

3. Create a new project called ControllersExercise.

Add the following to your /templates directory with some appropriate content:

Add a Controller to your project.

Add handler methods that map as follows: