In the previous chapters, you saw how to map a particular URL to a Controller's method; for example, if we entered the URL http://localhost:8080/webstore/market/products
, we would map that request to the list
method of ProductController
and list all the product information in the web page.
What if we want to list only a subset of products based on category, for instance, if we want to display only the products that fall under the category of laptops? If the URL entered is http://localhost:8080/webstore/market/products/Laptop
, and similarly if the URL is http://localhost:8080/webstore/market/products/Tablet
, we will like to show only tablets on the web page.
One way to do this is to have a separate request mapping method in the Controller for every unique category. But it won't scale if we have hundreds of categories; in that case we have to write hundreds of request mapping methods in the Controller. So how do we do that in an elegant way?
The Spring MVC URI template patterns feature is the answer. If you look at the following URLs carefully, the only part of the URL that changes is the category type (Laptop
and Tablet
). Other than that, everything is the same:
http://localhost:8080/webstore/products/Laptop
http://localhost:8080/webstore/products/Tablet
So we can define a common URI template for these URLs, which might look like http://localhost:8080/webstore/products/{category}
. Spring MVC can leverage this fact and make the template portion ({category}
) of the URL a variable, which is called a path variable in the Spring MVC world.
Let's add a category-wise View to our products page using the path variable.
ProductRepository
interface and add one more method declaration to getProductsByCategory
:List<Product> getProductsByCategory(String category);
InMemoryProductRepository
implementation class and add an implementation for the previously declared method as follows:@Override public List<Product> getProductsByCategory(String category) { String SQL = "SELECT * FROM PRODUCTS WHERE CATEGORY = :category"; Map<String, Object> params = new HashMap<String, Object>(); params.put("category", category); return jdbcTemplate.query(SQL, params, new ProductMapper()); }
ProductService
interface and add one more method declaration to getProductsByCategory
:List<Product> getProductsByCategory(String category);
ProductServiceImpl
service implementation class and add an implementation as follows:public List<Product> getProductsByCategory(String category) { return productRepository.getProductsByCategory(category); }
ProductController
class and add one more request mapping method as follows:@RequestMapping("/products/{category}") public String getProductsByCategory(Model model, @PathVariable("category") String productCategory) { model.addAttribute("products", productService.getProductsByCategory(productCategory)); return "products"; }
http://localhost:8080/webstore/market/products/Tablet
. You should see the following screen:Step 5 is the most important in the whole sequence, because all the steps prior to step 5 are a prerequisite for it. What we are doing in step 5 is nothing but one normal way of adding a list of product objects to the model
.
model.addAttribute("products", productService.getProductsByCategory(productCategory));
One thing you need to notice here is the getProductsByCategory
method from productService
; we need this method to get the list of products for the given category. And productService
as such cannot give the list of products for the given category; it will ask the repository.
That's why in step 4 we used the productRepository
reference to get the list of products by category in the ProductServiceImpl
class. Notice the following line from ProductServiceImpl
:
return productRepository.getProductsByCategory(category);
Another important thing you need to notice in the step 5 code snippet is the @RequestMapping
annotation's request path value:
@RequestMapping("/products/{category}")
By enclosing a portion of a request path within curly braces, we are indicating to Spring MVC that it is a URI template variable. According to the Spring MVC documentation, a URI template is a URI-like string containing one or more variable names. When you substitute values for these variables, the template becomes a URI.
For example, the URI template http://localhost:8080/webstore/market/products/{category}
contains the category
variable. Assigning the value laptop
to the variable yields http://localhost:8080/webstore/market/products/Laptop
. In Spring MVC ,we can use the @PathVariable
(org.springframework.web.bind.annotation.PathVariable
) annotation to read a URI template variable.
Since we have the @RequestMapping("/market")
annotation on the ProductController
level, the actual request path for the getProductsByCategory
method will be /market/products/{category}
. So at runtime, if we provide a web request URL such as http://localhost:8080/webstore/market/products/Laptop
, then the category
path variable will have the value laptop
. Similarly for the web request http://localhost:8080/webstore/market/products/Tablet
, the category
path variable will have the value tablet
.
Now how do we retrieve the value stored in the URI template path variable category
? As we already mentioned, the @PathVariable
annotation will help us to read that variable. All we need to do is simply annotate the getProductsByCategory
method's parameter with the @PathVariable
annotation as follows:
public String getProductsByCategory(@PathVariable("category") String productCategory, Model model) {
Spring MVC will read whatever value is present in the category
URI template variable and assign it to the productCategory
method parameter. So we have the category value in a variable; we just pass it to productService
to get the list of products in that category. Once we get that list of products, we simply add it to the Model and return the same View name that we used to list all the products.
The value attribute in the @PathVariable
annotation should be the same as the variable name in the path expression of the @RequestMapping
annotation. For example, if the path expression is "/products/{identity}"
, then to retrieve the path variable identity
you have to form the @PathVariable
annotation as @PathVariable("identity")
.
If the @PathVariable
annotation is specified without any value
attribute, it will try to retrieve a path variable with the name of the variable it has been annotated with.
For example, if you specify simply @PathVariable String productId
, then Spring will assume that it should look for a URI template variable {productId}
in the URL. A request mapping method can have any number of @PathVariable
annotations.
Finally in step 6, when we enter the URL http://localhost:8080/webstore/market/products/Tablet
, we see information about Google's Nexus 7, which is a tablet. Similarly, if you enter the URL http://localhost:8080/webstore/products/Laptop
, you will able to see Dell's Inspiron laptop's information.
If we have a web application called webstore
with the following request mapping on the Controller class level and in the method level, which is the appropriate request URL?
@RequestMapping("/items") public class ProductController { ... @RequestMapping(value = "/type/{type}", method = RequestMethod.GET) public String productDetails(@PathVariable("type") String productType, Model model) {
http://localhost:8080/webstore/items/electronics
http://localhost:8080/webstore/items/type/electronics
http://localhost:8080/webstore/items/productType/electronics
http://localhost:8080/webstore/type/electronics
For the following request mapping annotation, which are the correct methods' signatures to retrieve the path variables?
@RequestMapping(value="/manufacturer/{ manufacturerId}/product/{productId}")
public String productByManufacturer(@PathVariable String manufacturerId, @PathVariable String productId, Model model)
public String productByManufacturer (@PathVariable String manufacturer, @PathVariable String product, Model model)
public String productByManufacturer (@PathVariable("manufacturer") String manufacturerId, @PathVariable("product") String productId, Model model)
public String productByManufacturer (@PathVariable("manufacturerId") String manufacturer, @PathVariable("productId") String product, Model model)