7.1 SpringMVC 起步
7.1.1 SpringMVC的生命周期
浏览器发出一个request。
首先到达Spring的DispatcherServlet,SpringMVC所有的请求都会通过一个前端控制器Servlet。在这里一个单实例的Servlet将请求委托给应用程序的其他组件来执行实际的处理。在SpringMVC中,DispatcherServlet就是前端控制器。
DispatcherServlet将请求发送给SpringMVC控制器。DispatcherServlet会查询一个或多个处理器映射来确定请求发向哪里,处理器映射会根据请求所携带的URL信息来进行决策。
控制器对request进行处理
控制器在完成逻辑处理后,通常会产生一些信息,这些信息称为模型(model)。信息需要发送给一个视图(View)
控制器将模型数据打包,并且标示出用于渲染输出的视图名称。接下来会将请求连同模型和视图名称发送回DispatcherServlet。这里发送的仅仅是一个逻辑名,并不是某个具体的页面。
DispatcherServlet将会使用视图解析器来将逻辑视图名匹配为一个特定的视图实现。
数据返回视图,交付模型数据,视图将使用模型数据渲染输出,并通过这个输出将响应对象传递给客户端。
7.1.2 搭建SpringMVC
SpringMVC的核心是DispatcherServlet,与其他Servlet一样,DispatcherServlet必须在Web应用程序的web.xml文件中进行配置。
spitter
< servlet-class> org.springframework.web.servlet.DispatcherServlet
1
属性:DispatcherServlet在加载时会从一个基于这个Servlet名字的XML文件中加载Spring应用上下文。在这个例子中,DispatcherServlet将尝试从一个名为spitter-servlet.xml的文件来加载应用上下文。
通过将DispatcherServlet映射到/,声明了它会作为默认的servlet并且会处理所有的请求,包括对静态资源的请求。
为了避免DispatcherServlet处理静态资源,Spring的mvc命名空间提供了元素,它会处理静态资源的请求。
spitter-servlet.xml
xml version= "1.0" encoding= "UTF-8" ?>
< beans xmln= "http://www.springframework.org/schema/beans"
xmlns:xsi= "http:..www.w3.org/2001/XMLSchema-instance"
xmlns:mvc= "httpL..www.springframework.org/schema/mvc"
xsi:schemaLocation ="httpL://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd" >
beans>
*从Spring3.0.4开始支持
建立了一个服务于静态资源的处理器。属性mapping设置成/resources/**表明路径必须以/resources开始,包括它的任意子路径。location表明要提供服务的文件位置。例子中就代表着:所有以/resources路径开头的请求都会自动由应用程序根目录下的/resources目录提供服务。因此,所有图片、样式表、Javascript及其他静态资源都必须放在应用程序的/resources目录下。
7.2 编写基本的控制器
7.2.1 配置注解驱动的Spring MVC
DispatcherServlet需要咨询一个或多个处理器映射来明确的将请求分发给哪个控制器。Spring自带了多个处理器映射实现:
BeanNameUrlHandlerMapping:根据控制器Bean的名字将控制器映射到URL
ControllerBeanNameHandlerMapping:根据控制器Bean的名字将控制器映射到URL。使用该处理器映射实现,Bean的名字不需要遵循URL的约定
ControllerClassNameHandlerMapping:通过使用控制器的类名作为URL基础将控制器映射到URL
DefaultAnnotationHandlerMapping:将请求映射给使用@RequestMapping注解的控制器和控制方法
SimpleUrlHandlerMapping:使用定义在Spring应用上下文的属性集合将控制器映射到URL
使用这些处理器映射只需在Spring中配置一个Bean,如果没有找到处理器映射Bean,DispatcherServlet将创建并使用BeanNameUrlHandlerMapping和DefaultAnnotationHandlerMapping.
使用基于注解的控制器类,DefaultAnnotationHandlerMapping就可以满足需求。DefaultAnnotationHandlerMapping将请求映射到使用@RequestMapping注解的方法。但是实现注解驱动的SpringMVC并不是仅仅将请求映射到方法上,在构建控制器的时候,还需要使用注解将请求参数绑定到控制的方法参数上进行校验及信息转换,因此需要在配置文件中加入SpringMVC所提供的注解驱动特性:
这个标签注册包括JSR-303校验支持、信息转换、域格式化的支持等特性。
7.2.2 定义首页的控制器
eg:
HomeController是一个基本的SpringMVC控制器,它用于处理首页的请求
@Controller //声明为控制器
public class HomeController {
public static final int DEFAULT_SPITTLES_PER_PAGE = 25;
private SpitterService spitterService;
@Inject //注入Service
public HomeController(SpitterService spitterService) {
this.spitterService = spitterService;
}
@RequestMapping({"/","/homg"}) //处理对首页的请求
public String showHomePage(Map model) {
model.put("spittles",spitterService,getRecentSpittles(DEFAULT_SPITTELS_PER_PAGE)); //将Spittle放入模型中
return "home"; //返回视图名称
}
@Controller时@Component注解的一种具体化,将查找使用@Controller注解的类并将其注册为Bean。这意味着要在spring的配置文件中配置一个
在编写的构造器中使用Service作为参数并使用@Inject注解,这样当控制器初始化时Service会自动注入进来
@RequestMapping注解的作用:表明showHomePage()是一个请求处理方法,并指明了要处理“/”“/home”路径的请求。
请求处理方法的参数是一个KEY为String VALUE为Object的Map,这个Map就是模型。这里可以将HttpServletRequest、HttpServletResponse、String或者数字参数传递进来。
请求处理方法最后返回一个String类型值,这个值是要渲染结果的逻辑视图的名字。
7.2.3 解析视图
DispatcherServlet会查找一个视图解析器来将将控制器返回的逻辑视图名转换成渲染结果的实际视图。
实际上,视图解析器的工作是将逻辑视图的名字与org.springframework.web.servlet.View的实现相匹配。
Spring自带了的视图解析器实现
视图解析器
描述
BeanNameViewResolver
查找ID与逻辑视图名称相同View的实现
ContentNegotiatingViewResolver
委托给一个或多个视图解析器,而选择哪一个取决于请求的内容类型
FreeMarkerViewResolver
查找一个基于FreeMarker的模板,它的路径根据加完前缀和后缀的逻辑视图名称来确定
InternalResourceViewResolver
在Web应用程序的WAR文件中查找视图模板。视图模板的路径根据加完前缀和后缀的逻辑视图名称来确定
JasperReportsViewResolver
根据加完前缀和后缀的逻辑视图名称来查找一个Jasper Report报表文件
ResourceBundleViewResolver
根据属性文件(properties file)来查找View实现
TilesViewResolver
查找通过Tiles模板定义的视图,模板的名字与逻辑视图名称相同
URLBasedViewResolver
这是一些其他视图解析器(InternalResourceViewResolver)的积累,可以单独使用,但并没有子类强大
VelocityLayoutViewResolver
VelocityViewResolver的子类,支持通过Spring的VelocityLayoutView(模仿VelocityVelocityLayoutServlet的View实现)来进行页面的组合
VelocityViewResolver
解析基于Velocity的视图,Velocity模板的路径根据加完前缀和后缀的逻辑视图名来确定
XmlViewResolver
查找在XML文件(/WEB-INF/views.xml)中声明的View实现。这个视图解析器与BeanNameViewResolver类似,但在这里视图的声明与应用程序Spring上下文的其他是分开的
XsltViewResolver
解析基于XSLT的视图,XSLT样式表的路径根据加完前缀和后缀的逻辑视图名称来确定
解析内部视图
InternalResourceViewResolver是一个面向约定的元素(
约定优于配置 )。它将逻辑视图名称解析为View对象,而该对象将渲染的任务委托给Web应用程序上下文的一个模板(通常是JSP)。
/WEB-INF/views/ home .jsp
前缀 逻辑视图名 后缀
前后缀配置方式
DispatcherServlet要求InternalResourceViewResolver解析视图的时候,它将获取一个逻辑视图名称,添加相应的前缀后缀,得到一个渲染输出的JSP路径。在内部,InternalResourceViewResolver会将这个路径传递给View对象,View对象将请求传递(dispatch)给JSP。
默认情况下,InternalResourceViewResolver创建的View对象是InternalResourceView的示例,它只会简单的将请求传递给要渲染的JSP。本例中,JSP使用了一些JSTL标签,因此需要通过设置viewClass属性来将InternalResourceViewResolver替换为JstlView。
解析Tiles视图
Apache Tiles是一个模板框架,将页面分成片段并在运行时组装成完整的页面。最初是Struts框架的一部分。
要在SpringMVC中使用Tiles,首先要在spring配置文件中将TilesViewResolver注册为一个
这个Bean声明会简历一个视图解析器,她会查找逻辑视图名称与Tiles定义名称相同的Tiles模板定义,并将其作为视图。
TilesViewResolver本身并了解Tiles定义的信息,而是依靠TilesConfigurer来记录这个信息,所以需要在spring配置文件中添加TilesConfigurer类型的Bean
/WEB-INF/views/**/views.xml
TilesConfigurer会加载一个或多个的Tiles定义,并使得TilesViewResolver可以通过它来解析视图。ANT风格的**代表在/WEB-INF/views下的所有目录查找名为views.xml的文件
TilesViewResolver在解析逻辑视图名称时,将会找到对应的模板。DispatcherServlet将会把请求传递给Tiles,并用home定义来渲染结果。
7.2.4 定义首页的视图
7.2.5 完成Spring的应用上下文
ContextLoaderListener是一个Servlet监听器,除了DispatcherServlet创建的应用上下文以外,它能够加载其他的配置文件到一个Spring应用上下文中。为了使用ContextLoaderListener,需要在web.xml文件中添加如下的声明
org.springframework.web.context.ContextLoaderListener
如果没有配置,上下文加载器会查找/WEB-INF/applicationContext.xml这个Spring配置文件。
为了给ContextLoaderListener指定一个或多个Spring配置文件,需要在servlet上下文中配置contextConfigLocation参数
/WEB-INF/spitter-security.xml
classpath:service-context.xml
classpath:persistence-context.xml
classpath:dataSource-context.xml
contextConfigLocation参数指定了一个路径的列表。除非特别声明,路径是相对于应用程序根目录的。对其中的一些添加classpath:前缀,可以使它们能够以资源的方式在应用程序中的类路径中加载,而其他文件则添加了Web应用程序的本地路径。
7.3 处理控制器的输入
7.3.1 编写处理输入的控制器
@Controller
@RequestMapping("/spitter")
public class SpitterController {
... // 注入Service
@RequestMapping(value="/spittles, method=GET)
public String listSpittlesForSpitter(@RequestParam("spitter") String username, Model model) {
Spitter spitter = spitterService.getSpitter(username);
model.addAttribute(spitter);
model.addAttribute(spitterService.getSpittlesForSpitter(username));
return "spittles/list";
}
}
类级别的@RequestMapping定义了这个控制器所处理的根URL路径。最终会在SpitterController中添加多个处理方法,每个方法处理不同的请求。这里的@RequestMapping已经表明所有的请求的路径都必须以 /spitters开头。
方法级别的@RequestMappings限制了类级别所定义的@RequestMapping路径匹配。
在这里,SpitterController在类级别匹配到/spitters,在方法级别匹配到/spittles,合并起来意味着listSpittlesForSpitter()将处理/spitters/spittles的请求。method属性被设置为GET代表这个方法只会处理HTTP GET请求。
参数username使用RequestParam(“spitter”)注解表名它的值应该根据请求中名为spitter的查询参数来获取。
@RequestParam不是必须的,基于约定,如果处理方法的所有参数没有使用注解的话,将绑定到同名的查询参数上。但在查询参数与方法参数名字不匹配的时候,这个注解是有用的。
最好一直使用@RequestParam而不是过于依赖约定 。
7.3.2 渲染视图
7.4 处理表单
7.4.1 展现注册表单
当展现表单的时候,需要将一个Spitter对象绑定到表单域上。因为这是新建的Spitter,所以新构建的且没有初始化的Spitter是最好的。
@RequestMapping(method=RqeustMethod.GET, params="new")
public String createSpitterProfile(Model model) {
model.addAttribute(new Spitter());
return "spitters/edit";
}
方法级别的@RequestMapping如果没有指定路径,那么这个方法将会处理类级别@RequestMapping注解所指定的路径 。
@RequestMapping声明了这个方法只处理HTTP GET请求。此外,将params属性设置为new,意味着这个方法只处理对/sppiters的HTTP GET请求并要求请求中必须包含名为new的查询参数
http://localhost:8080/ Spitter/ spitters ?new
Servlet上下文 由SpitterController来处理 "new"查询参数
定义表单视图
<%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form"%>
...
..
这个JSP文件使用了Spring的表单绑定库。标签将createSpitterProfile()方法所放入模型的Spitter对象绑定到表单中的各个输入域。
各输入域的path属性引用的是锁绑定的Spitter对象的属性。表单提交时,这些输入域中的值会放入Spitter对象中并提交到服务器进行处理。
在没有指明URL的情况下,将被提交到展现表单的URL路径(/spitters)
7.4.2 处理表单输入
@RequestMapping(method=RequestMethod.POST)
public String addSpitterFromForm(@Valid Spitter spitter,BindingResult bindingResult) {
if(bindingResult.hasErrors()) { // 表单校验
return "spitters/edit";
}
spitterService.saveSpitter(spitter); // 保存Spitter
return "redirect:/spitters/" + spitter.getUsername(); // 重定向
}
返回了重定向的视图而不是指明逻辑视图名称。前缀redirect:说明请求将被重定向到指定路径。如果用户点击了浏览器的刷新按钮,通过重定向到另一个页面能够避免表单的重复提交。
对于重定向的路径,将采用 /spitters/{username}这样的形式,其中{user-name}是刚刚提交的Spitter对象的用户名
处理带有路径变量的请求
响应 /spitter/{username}请求的方法
@RequestMapping(value="/{username}", method=RequestMethod.GET)
public String showSpitterProfile(@PathVariable String username, Model model) {
model.addAttribute(spitterService.getSpitter(username));
return "spitters/view";
}
路径中{username}是一个占位符,它对应了使用@PathVariable注解的username方法参数。请求路径中的该位置的值将作为username的值传递进去。例如请求路径是/username/habuma,那么habuma将会作为username的值传递到showSpitterProfile()中。
7.4.3 校验输入
@Valid注解用来防止错误表单输入,实际上是JavaBean校验规范的一部分(JSR-303)。
如果校验出错,校验错误将会作为第二个参数以BindingResult的形式传递给addSpitterFromForm()。
定义校验规则
JSR-303定义了一些注解,这些注解可以放到属性上来指定校验规则。
@Size(min=3, max=20, message="Username must be between 3 and 20 characters long.")
@Pattern(regexp="^[a-zA-Z0-9]+$",message="Username must be alphanumeric with no spaces")
private String username;
...
展现校验错误
为用户展现错误的方式是通过BindingResult的geiFieldError()方法来获取字段的错误。但更好的方式是使用Spring的表单绑定JSP标签库来展现错误。能够渲染字段的校验错误。
<%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form"%>
...
...
Full name:
可以为设置delimiter属性来指定多个错误的分割方式
7.5 处理文件上传
7.5.1 在表单上添加文件上传域
典型的提交会带有一个application/x-www-form-urlencoded这样的内容类型并将表单上的名称-值以&符号分割
当提交带有文件的表单时,要选择的内容类型是multipart/form-data,需要设置的enctype属性
7.5.2 接收上传的文件
@RequestMapping(method=RequestMethod.POST)
public String addSpitterFromForm(@Valid Spitter spitter, BindingResult bindingResult,
@RequestParam(value="image", required=false, MultipartFile image) {
...
try {
if(!image.isEmpty()) {
validateImage(iamge);
saveImage(spitter.getId() + ".jpg", image);
}
} catch (ImageUploadException e) {
bindingResult.reject(e.getMessage());
return "spitters/edit";
}
return "redirect:/spitters/" + spitter.getUsername();
}
private void validateImage(MultipartFile image) {
if(!image.getContentType().equals("image/jpeg")) {
throw new ImageUploadException("Only JPG images accepted");
}
}
将文件保存到文件系统中
private void saveImage(String filename, MultipartFile image) throws ImageUploadException {
try {
File file = new File(webRootPath + "/resources/" + filename;
FileUtils.writeByeArrayToFile(file, image.getBytes());
} catch (IOException e) {
throw new ImageUploadException("Unable to save image", e);
}
}
File对象准备就绪,就可以使用ApacheCommonsIO的FileUtils将图片数据写入文件中。
还可以将文件保存到云中……
7.5.3 配置Spring支持文件上传
DispatcherServlet本身并不知道如何处理multipart的表单数据。我们需要一个multipart解析器把POST请求的multipart数据中抽取出来,这样DispatcherServlet就能将其传递给控制器。
为了在Spring中注册multipart解析器,需要声明一个实现了MultipartResolver接口的Bean。Spring只提供了一个multipart解析器
multipart解析器的Bean ID是有意义的。当DispatcherServlet查找multipart解析器的时候,它将会查找ID为multipart的解析器的Bean,如果multipart解析器是其他ID的话,DispatcherServlet将会忽略它
总结:
SpringMVC提供了几乎是POJO的开发模式,使得控制器的开发和测试更加简单。这些控制器一般不会直接处理请求,而是将其委托给Spring应用上下文中的其他Bean,通过Spring的依赖注入功能,这些Bean被注入到控制器中。
处理器映射会选择使用哪个控制器来处理请求,视图解析器会选择结果应该如何渲染。