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.
Exact match: to match an exact pattern, just type it.
For example "/test" matches http://localhost:8080/test
but won't match URLs like http://localhost:8080/test1,
http://localhost:8080/test.html,
or http://localhost:8080/test/test
Other examples:
"/image.png" will match http://localhost:8080/image.png
and nothing else.
"/foo" will match http://localhost:8080/foo but not
match http://localhost:8080/foo.jpg or
http://localhost:8080/foo/foo.jpg or
http://localhost:8080/foo/shoe
Root: Using the pattern "/" simply means
the root of the application directory. This matches
the URL http://localhost:8080
or http://localhost:8080/
Wildcard * can be used a few ways:
Extension Match: "/*.html" will
match any file with the "html" extension. For example,
it will match http://localhost:8080/boo.html or
http://localhost:8080/index.html but it won't
match http://localhost:8080/foo/boo.html. If
you want to match a specific type of file in a specific
directory, you can use "/foo/*.html", which will match
http://localhost:8080/foo/boo.html
Segment Match: "/foo/*"
will match any single "segment" after "/foo/".
For example, it will match http://localhost:8080/foo/boo.html
or http://localhost:8080/foo/boo,
but it won't match http://localhost:8080/foo/boo/shoe
because there is an extra "segment" after /boo
Multiple Segments: "/foo/**" will match
multiple segments in a URL e.g. it will match
http://localhost:8080/foo/boo.html,
http://localhost:8080/foo/stuff/boo.html,
http://localhost:8080/foo/boo,
and http://localhost:8080/foo/moo/shoe/boo.html
Single Character: you can match a single
character using the ? symbol. For example, "/image?.png"
matches http://localhost:8080/images.png,
http://localhost:8080/image1.png,
and http://localhost:8080/imageX.png
but not http://localhost:8080/image.png or
http://localhost:8080/image29.png
The above ? works with directory names, too: "/foo?" matches
http://localhost:8080/fool,
http://localhost:8080/fooo, and
http://localhost:8080/foo1
but not http://localhost:8080/foo
or http://localhost:8080/foo.html
Regular Expressions: You can use regular
expressions in your pattern. The syntax is a bit different:
{varname:expression}
where "varname" is a variable name to
store the results of this pattern (you'll learn how to actually
use that in a later lesson) and "expression" is the regex.
For example:
"/{img:image[0-9]{2}.png}" (or you can use "/{img:image\\d{2}.png"
instead) matches any file named "image"
followed by exactly 2 digits, followed by ".png". So it
will match http://localhost:8080/image01.png and
http://localhost:8080/image99.png
and http://localhost:8080/image00.png
but not http://localhost:8080/image1.png,
http://localhost:8080/image999.png, or
http://localhost:8080/image.png
"/order/{ordernum:\w+\d{2,4}}" matches a URL that
ends in "order/" followed by 1 or more word-characters
followed by 2, 3, or 4 digits. It would match
http://localhost:8080/orders/abc12,
http://localhost:8080/orders/12345,
http://localhost:8080/orders/_99, or
http://localhost:8080/orders/X2929,
but it won't match
http://localhost:8080/orders or
http://localhost:8080/orders/23
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.
http://sydney.cattrees.ca/orders
http://sydney.cattrees.ca/orders/index.html
http://sydney.cattrees.ca/orders/foo/index.html
http://sydney.cattrees.ca/orders/newOrder.frm
http://sydney.cattrees.ca/register.frm
http://sydney.cattrees.ca/users
http://sydney.cattrees.ca/useracct
http://sydney.cattrees.ca/user/acct/index.html
http://sydney.cattrees.ca/user
http://sydney.cattrees.ca/
http://sydney.cattrees.ca/inv/product
http://sydney.cattrees.ca/inv/abcproduct
http://sydney.cattrees.ca/inv/abcproduct.html
http://sydney.cattrees.ca/abcproduct
http://sydney.cattrees.ca/abcproduct.frm
shop/index.html
shop/index.html
shop/index.html
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)
forms.html
manageUsers.html
404
404
404
index.html
404
products/index.html
404
404
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.
Add an index.html page to the /static resources directory.
Make sure you add some kind of content to it that makes it
easily identifier as your page's main index.
Add three HTML pages to the /template resources directory:
shop.html: a page for your shop where you sell
your things. Add an appropriate heading and
a list of 3 of your items for sale. Use any layout
you like: a table, a css grid, whatever.
If you would like to include images and/or css,
see the blue box below!
contact.html: a page with your (or your company's)
contact info. Name, fake email, fake phone number
(you can use real ones if you want, I won't be
phoning you). For practice, make a contact form
so a client can send you a message (it doesn't
have to be functional)!
faq.html: a page with at least three frequently-asked
questions (and their answers). Feel free to make
this fancy, like the user can show/hide the
answer by clicking the question, or something similar.
Make sure each of your three template pages have a "Back"
link to the main index page e.g. <a href="/">Back</a>
Don't use history.back() or anything similar (because it's
obtrusive, so not accessible, but it also isn't guaranteed
to return to the index page).
Edit your static index page and add a link to each of the
three template pages. The text and links should be as follows:
shop page: Text="Shop Online", link="shop"
contact page: Text="Contact Us", link="contact"
faq page: Text="FAQ", link="faq"
Add a controller to a controllers package.
Add three handler methods to your controller that maps
to each of the 3 template pages.
Run your project and load the index page. Test each of the
three links to make sure they work!
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:
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: