概述
Spring MVC-03循序渐进之Spring MVC中我们介绍了传统的开发方式,其弊端Controller接口实现类只能处理一个单一动作,本篇博文我们来介绍下基于注解的控制器。
Spring MVC注解类型
基于注解的控制器优点如下:
一个控制器可以处理多个请求动作,而一个实现了Controller接口的控制器只能处理一个动作
基于注解的控制器的请求映射不需要存储在配置文件中,使用RequestMapping注解类型,可以对一个方法进行请求处理。
Controller和RequestMapping注解类型是SpringMVC API最重要的两个注释类型,当然了我们这里也会介绍其他一些注解类型
Controller注解类型
org.springframework.stereotype.Controller注解类型用于指示Spring类的实例是一个控制器。
下面是一个带有@Controller注解的例子
import org.springframework.stereotype.Controller; @Controller public class ArtisanController { }
Spring使用注解扫描的方式来找到应用中所有基于注解的控制器类,为了确保Spring能扫描到你的控制器,需要完成两件事情
- 在Spring MVC配置文件中声明spring-context及指定schema
- 然后配置component-scan扫描路径
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.artisan.springmvc.controller"/> <!--......省略其他配置项---> </beans>
确保所有控制器类都在基本包下,并且不要指定一个太宽泛的基本包,这样会使Spring扫描了无关的包。
RequestMapping注解类型
现在我们需要在控制器类内部为每一个动作开发相应的处理方法,要让Spring知道哪一种方法来处理它的动作,需要使用org.springframework.web.bind.annotation.RequestMapping注解类型映射的URL与方法。
RequestMapping注释类型的作用:映射一个请求和一种方法,可以使用@RequestMapping注释一种方法或者一个类
一个采用了@RequestMapping注解的方法将成为一个请求处理方法,并由调度程序在接收到对应的URL请求时调用
下面是一个@RequestMapping注解方法的控制器类
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class ArtisanController { @RequestMapping(value="/doSomething") public String doSomething() { // do some bussiness logic here return "custoomer"; } }
使用RequestMapping注解的value属性将URI映射到方法。在上面的例子中,我们将doSomething映射到doSomething方法,这样,就可以使用如下URL访问doSomething方法
http://domain/context/doSomething
value属性
由于value属性是RequestMapping注解的默认属性,如果只有唯一的属性,则可以省略属性名称。换句话说
@RequestMapping(value="/doSomething") @RequestMapping("/doSomething")
效果是等同的,但是如果超过一个属性时,就必须要输入value属性名称。
请求映射的值可以是一个空字符,此时该方法被映射到如下网址 http://domain/context
其他属性
RequestMapping除了具有value属性,还有其他属性。比如method属性用来指示改方法仅处理哪些HTTP方法. 当method为多个值时,后面写为数组{method1, method2}
例如只有在HTTP POST或者PUT方法时才能访问到下面的方法
@Controller public class ArtisanController { @RequestMapping(value="/doSomething",method={RequestMethod.POST,RequestMethod.PUT}) public String doSomething() { // do some bussiness logic here return "customer"; } }
如果method属性仅有一个HTTP方法值,则不需要花括号
@RequestMapping(value="/doSomething",method=RequestMethod.POST)
如果没有指定method属性值,则请求处理方法可以处理任意HTTP方法。
此外RequestMapping注释类型也可以用来注释一个控制器类
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller @RequestMapping("/artisan") public class ArtisanController { }
这种情况下,所有的方法都将映射为相对于类级别的请求,比如下面的deleteArtisan方法
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller @RequestMapping("/artisan") public class ArtisanController { @RequestMapping(value="/delete",method={RequestMethod.POST,RequestMethod.PUT}) public String deleteArtisan() { // do delete opertaion return "artisanList"; } }
由于控制器类的映射使用了“/artisan” ,而deleteArtisan方法映射为/delete,则如下的URL将映射到该方法上
htpp://domain/context/artisan/delete
编写请求处理方法
每个请求处理方法可以有多个不同类型的参数,以及一个多钟类型的返回结果。
比如在请求处理方法中需要访问HttpSession对象,则可以添加HttpSession作为参数,Spring会将对象正确传递给方法
@RequestMapping("/uri") public String method(HttpSession session){ // do something session.setAttribute(key, value); return ...; }
或者,如果需要访问客户端环境和HttpServletRequest,则可以在方法签名上包括这样的参数
@RequestMapping("/uri") public String method(HttpServletRequest request,Locale locale){ // access HttpServletRequest or Locale here return ...; }
每个请求处理方法可以有多个不同类型的参数,下面时可以在请求处理方法中出现的参数类型:
javax.servlet.ServletRequest 或 javax.servlet.HttpServletRequest
javax.servlet.ServletResponse
或 javax.servlet.httpHttpServletResponse
javax.servlet.http.HttpSession
org.springframework.web.context.request.WebRequest
或 org.springframework.web.context.request.nativeWebRequest
java.util.Locale
java.io.InputStream 或 java.io.Reader
java.io.OutputStream 或 java.io.Writer
java.security.Principal
HttpEntity<?>
java.util.Map 或 org.springframework.ui.Model
org.springframework.ui.ModelMap
org.springframework.web.servlet.mvc.support.RedirectAttributes
org.springframework.validation.Errors
orgspringframework.validation.BindingResult
命令或表单对象
org.springframework.web.util.UriCompontsBuilder
org.springframework.web.util.UriComponentsBuilder
带@PathVariable, @MatrixVariable注释的对象
@RequestParam, @RequestHeader, @RequestBody 或 @RequestPart
特别重要的是org.springframework.ui.Model类型不是一个Servlet API类型,而是一个包涵Map的Spring MVC类型。每次调用请求处理方法时,Spring MVC都创建Model对象将其Map注入到各种对象。
请求处理方法可以返回如下类型的对象:
ModelAndView
Model
Map包含模型的属性
View
代表逻辑视图名的String
void
提供对Servlet的访问。以相应HTTP头部和内容HttpEntity或ResponseEntity对象
Callable
DeferredResult
其他任意类型,Spring将其视作输出给View的对象模型
应用基于注解的控制器
该处的示例是对前面几篇博文的重写,区别于前几篇博文中的示例在于
- 控制器类中增加了@Controller注解
- Spring配置文件增加了部分元素,下面详解
目录结构
maven工程结构如上,在这里,只有一个控制器类,而不是之前示例中的两个。
同时增加了一个名为index.html的静态文件,以便Spring MVC Servlet的URL模式设置为”/”时,依然可以访问静态资源
配置文件
两个配置文件,第一个为部署描述符(web.xml文件)中注册Spring MVC的DispatcherServlet ,第二个Spring MVC的配置文件 springmvc-config.xml
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/config/springmvc-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
在部署描述符中servlet-mapping元素中url-pattern设置为 / ,而不是之前实例中的action。 实际上映射动作不必一定是要用某种URL扩展。
当然,当URL设置为/,意味着所有的请求( 包括那些静态资源)都被映射到DispatcherServlet, 为了正确的处理静态资源,就必须要在Spring MVC的配置文件中添加一些 resouce元素。
springmvc-config.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.artisan.springmvc.controller"/> <mvc:annotation-driven/> <mvc:resources mapping="/css/**" location="/css/"/> <mvc:resources mapping="/*.html" location="/"/> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> </beans>
Spring MVC配置文件中最重要的是context:component-scan元素,这是要告诉SpringMVC扫描目标包中的类。
接下来是一个mvc:annotation-driven和两个mvc:resources。
mvc:annotation-driven元素做的事情内包括注册用于支持基于注解的控制器的请求处理方法的bean对象
mvc:resources元素用于指示Spring MVC 哪些静态资源需要单独处理,即不通过Dispatcher Servlet
在这个示例中,第一个resources元素确保/css目录下的所有文件可见
第二个允许显示所有的.html文件
注意:如果没有annotation-driven,resources元素会阻止任意控制器被调用,如果不需要使用resources,则不需要annotation-driven元素
Controller类
使用Controller注释类型的一个优点在于:一个控制器类可以包含多个请求处理方法
package com.artisan.springmvc.controller; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import com.artisan.springmvc.domain.Product; import com.artisan.springmvc.form.ProductForm; @Controller public class ProductController { private static final Log logger = LogFactory.getLog(ProductController.class); @RequestMapping(value="/product_input") public String inputProduct() { logger.info("inputProduct called"); return "ProductForm"; } @RequestMapping(value="/product_save") public String saveProduct(ProductForm productForm, Model model) { logger.info("saveProduct called"); // no need to create and instantiate a ProductForm // create Product Product product = new Product(); product.setName(productForm.getName()); product.setDescription(productForm.getDescription()); try { product.setPrice(Float.parseFloat( productForm.getPrice())); } catch (NumberFormatException e) { } // add product model.addAttribute("product", product); return "ProductDetails"; } }
其中,ProductController#saveProduct()方法的第二个入参
public String saveProduct(ProductForm productForm, Model model)
无论是否会使用,SpringMVC都会在每一个请求处理方法被调用时创建一个Model实例,用于增加需要显示在视图中的属性,例如通过调用model.addAttribute("product", product);来添加Product实例。这样Product实例就可以被添加到HttpServletRequestt中那样访问了。
View
ProductForm.jsp
<!DOCTYPE HTML> <html> <head> <title>Add Product Form</title> <style type="text/css">@import url(css/main.css);</style> </head> <body> <div id="global"> <form action="product_save" method="post"> <fieldset> <legend>Add a product</legend> <p> <label for="name">Product Name: </label> <input type="text" id="name" name="name" tabindex="1"> </p> <p> <label for="description">Description: </label> <input type="text" id="description" name="description" tabindex="2"> </p> <p> <label for="price">Price: </label> <input type="text" id="price" name="price" tabindex="3"> </p> <p id="buttons"> <input id="reset" type="reset" tabindex="4"> <input id="submit" type="submit" tabindex="5" value="Add Product"> </p> </fieldset> </form> </div> </body> </html>
ProductDetails.jsp
<!DOCTYPE HTML> <html> <head> <title>Save Product</title> <style type="text/css">@import url(css/main.css);</style> </head> <body> <div id="global"> <h4>The product has been saved.</h4> <p> <h5>Details:</h5> Product Name: ${product.name}<br/> Description: ${product.description}<br/> Price: $${product.price} </p> </div> </body> </html>
测试应用
http://localhost:8080/chapter04a/product_input
输入对应的表格
提交后
http://localhost:8080/chapter04a/product_save
调用saveProduct方法