In the previous section, you saw the URI template facility to bind variables in the URL request path. But there is one more way to bind variables in the request URL in a name-value pair style, referred to as matrix variables within Spring MVC. Look at the following URL:
http://localhost:8080/webstore/market/products/filter/price;low=500;high=1000
In this URL, the actual request path is just up to http://localhost:8080/webstore/market/products/filter/price
, and after that we have something like low=500;high=1000
. Here, low
and high
are just matrix variables. But what makes Matrix variables so special is the ability to assign multiple values for a single variable; that is, we can assign a list of values to a URI variable. Look at the following URL:
http://localhost:8080/webstore/market/products/filter/params;brands=Google,Dell;categories=Tablet,Laptop
In this URL, we have two variables, namely brand
and category
. Both have multiple values: brands
(Google
, Dell
) and categories
(Tablet
, Laptop
). How can we read these variables from the URL during request mapping? Here comes the special binding annotation @MatrixVariable
(org.springframework.web.bind.annotation.MatrixVariable
). One cool thing about the @MatrixVariable
annotation is that it allows us to collect the matrix variables in the map of a collection (Map<String, List<String>>
), which will be more helpful when we are dealing with complex web requests.
Consider a situation where we want to filter the product list based on brands
and categories
. For example, you want to list all the products that fall under the category Laptop
and Tablets
and from the manufacturer Google
and Dell
. With the help of Matrix variables, we can form a URL something like the following to bind the brands
and categories
variables' values into the URL:
http://localhost:8080/webstore/market/products/filter/params;brands=Google,Dell;categories=Tablet,Laptop
Let's see how to map this URL to a handler method with the help of the @MatrixVariable
annotation:
ProductRepository
interface and add one more method declaration to getProductsByFilter
:List<Product> getProductsByFilter(Map<String,List<String>> filterParams);
InMemoryProductRepository
implementation class and add the following method implementation for getProductsByFilter
:@Override public List<Product> getProductsByFilter(Map<String, List<String>> filterParams) { String SQL = "SELECT * FROM PRODUCTS WHERE CATEGORY IN ( :categories ) AND MANUFACTURER IN ( :brands)"; return jdbcTemplate.query(SQL, filterParams, new ProductMapper()); }
ProductService
interface and add one more method declaration to getProductsByFilter
:List<Product> getProductsByFilter(Map<String, List<String>> filterParams);
ProductServiceImpl
service implementation class and add the following method implementation for getProductsByFilter
:public List<Product> getProductsByFilter(Map<String, List<String>> filterParams) { return productRepository.getProductsByFilter(filterParams); }
ProductController
and add one more request mapping method as follows:@RequestMapping("/products/filter/{params}") public String getProductsByFilter(@MatrixVariable(pathVar="params") Map<String,List<String>> filterParams, Model model) { model.addAttribute("products", productService.getProductsByFilter(filterParams)); return "products"; }
WebApplicationContextConfig.java
) and enable matrix variable support by overriding the configurePathMatch
method as follows:@Override public void configurePathMatch(PathMatchConfigurer configurer) { UrlPathHelper urlPathHelper = new UrlPathHelper(); urlPathHelper.setRemoveSemicolonContent(false); configurer.setUrlPathHelper(urlPathHelper); }
http://localhost:8080/webstore/market/products/filter/params;brands=Google,Dell;categories=Tablet,Laptop
. You will see the products list as shown in the following screen:Our aim was to retrieve the matrix variable values from the URL and do something useful; in our case the URL we were trying to map is http://localhost:8080/webstore/market/products/filter/params;brands=Google,Dell;categories=Tablet,Laptop
. Here we want to extract the matrix variables brands
and categories
. The brands
and categories
variables have values: (Google
, Dell
) and (Tablet
, Laptop
) respectively. In the previously specified URL, the request path is just up to http://localhost:8080/webstore/market/products/filter/params
only. That's why in step 5 we annotated our getProductsByFilter
request mapping method as follows:
@RequestMapping("/products/filter/{params}")
But you may be wondering why we have a URI template (/{params}
) in the @RequestMapping
annotation as a mapping to a path variable. This is because, if our request URL contains a matrix variable, then we have to form the @RequestMapping
annotation with a URI template to identify the matrix variable segments. That's why we defined params
as a URI template in the request mapping (@RequestMapping("/products/filter/{params}")
) annotation.
A URL can have multiple matrix variables, and each matrix variable must be separated with a ";" (semicolon). To assign multiple values to a single variable, each value must be "," (comma) separated or we can repeat the variable name. See the following URL, which is a variable repeated version of the same URL that we used in our example: http://localhost:8080/webstore/market/products/filter/params;brands=Google;brands=Dell;categories=Tablet;categories=Laptop
Note that we repeated the variable brands
and categories
twice in the URL.
Okay, we mapped the web request to the getProductsByFilter
method, but how do we retrieve the value from the matrix variables? The answer is the @MatrixVariable
annotation.
@MatrixVariable
is very similar to the @PathVariable
annotation; if you look at the getProductsByFilter
method signature in step 5, we annotated the method's parameter filterParams
with the @MatrixVariable
annotation as follows:
public String getProductsByFilter(@MatrixVariable(pathVar="params") Map<String,List<String>> filterParams, Model model)
So Spring MVC will read all the matrix variables found in the URL after the {params}
URI template and put them into the method parameter filterParams
map. The filterParams
map will have each matrix variable name as the key and the corresponding list will contain multiple values assigned for the matrix variable. The pathVar
attribute from @MatrixVariable
is used to identify the matrix variable segment in the URL; that's why it has the value params
, which is nothing but the URI template value we used in our request mapping URL.
A URL can have multiple matrix variable segments. See the following URL:
http://localhost:8080/webstore/market/products/filter/params;brands=Google,Dell;categories=Tablet,Laptop/specification;dimention=10,20,15;color=red,green,blue
It contains two matrix variable segments each identified by the prefix params
and specification
respectively. So, in order to capture each matrix variable segment into maps, we have to form the controller method signature as follows:
@RequestMapping("/products/filter/{params}/{specification}") public String filter(@MatrixVariable(pathVar="params") Map<String,List<String>> criteriaFilter, @MatrixVariable(pathVar=" specification") Map<String,List<String>> specFilter, Model model)
Okay, we got the value of the matrix variables' values into the method parameter filterParams
, but what we have done with that filterParams
map? We simply passed it as parameters to the service method to retrieve the products based on the criteria:
productService.getProductsByFilter(filterParams)
Again, the service passes that map to the repository to get the list of products based on the criteria. Once we get the list, as usual, we simply add that list to the Model, and return the same logical View name that was used to list the products.
To enable the use of matrix variables in Spring MVC, we must set the RemoveSemicolonContent
property of UrlPathHelper
to false; we did that in step 6. Finally, we are able to see products based on the specified criteria in step 7 on our product listing page.