一、spring3.1 mvc核心思想介绍
Spring MVC是spring的一个web组件,它为构建稳健的web应用提供了丰富的功能。
Spring MVC是基于每个逻辑和功能是高可配置的这样架构和设计的。当然spring MVC可以与其它流行的web框架像struts、webwork、javaserverface及tapestry实现无缝集成。我们看一下spring MVC的核心思想。
Spring请求的生命周期
总结一下springMVC几个关键的步骤,总共可以分为六个步骤,分别为:
(1)
(2)
(3)
(4)
(5)
(6)
通过上面的图和springMVC生命周期的六个步骤,想必大家对springMVC的核心思想有个了大概的了解了,下面我们以实例为主,带领大家慢慢熟悉整个springMVC及如何使用springMVC。(本教程基于maven实现springMVC中的例子,所以大家得对maven需要有大概的了解)。
二、spring3.1 mvc 框架的特点
如果仅仅关注于web方面的支持,Spring有下面一些特点:
· 清晰的角色划分:控制器,验证器,命令对象,表单对象和模型对象;分发器,处理器映射和视图解析器;等等。
· 直接将框架类和应用类都作为JavaBean配置,包括通过应用上下文配置中间层引用,例如,从web控制器到业务对象和验证器的引用。
· 可适应性,但不具有强制性:根据不同的情况,使用任何你需要的控制器子类(普通控制器,命令,表单,向导,多个行为,或者自定义的),而不是要求任何东西都要从Action/ActionForm继承。
可重用的业务代码,而不需要代码重复:你可以使用现有的业务对象作为命令对象或表单对象,而不需要在ActionForm的子类中重复它们的定义
·
·
·
三、spring 3.1 MVC入门列子HelloWorld
(1)在WEB-INF/web.xml中加入如下代码:
</servlet-mapping>
上述的配置的就是前段控制器,在servlet-mapping配置了*.html,意味着所有以.html结尾的请求多会通过这个servlet,当dispatcherServlet启动时,他默认会在web-info目录下查找一个spring-servlet.xml的配置文件。上面我们通过显示指定了这个文件的位置,即在类路径底下的spring-servlet.xml.这个文件我们会在第二步点给他家做详细介绍。
(2)在类路径底下添加spring-servlet.xml文件,其内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
</beans>
上面这个文件,我们定义了一个<context:component-scan
这样的标签,定义了这个后,当spring在启动时,会加载com.pango.spring.helloworld.controller这个包底下及子包底下的所有的组件(这就包的自动扫描机制,即spring会将标有@Controller @Component等类加载到spring容器管理中),后面我们还定义了<bean id="viewResolver"
ViewResolver是一个试图解析器,就是我们第一部分提到的springMVC生命周期中的第五步,上面这段的配置的意思就是,当我们从后端控制器中返回的视图时,前端控制器就根据这一段配置来返回一个具体的视图,如后端控制返回的是一个hello,根据上面的配置,最后前端控制器会组并成这样的一个地址:/web-inf/jsp/hello.jsp,然后从/web-inf/jsp/这个目录下面查找一个hello.jsp返回客户端。第三部分我们看我们写得HelloworldController后台控制器。
(3)在包底下写一个HelloWorldController的类,其内容如下:
@Controller
public class HelloWorldController {
@RequestMapping(value="/hello")
}
在这里简单介绍下上面的配置,后面我们会详细讲解各个参数:
Ø
Ø
Ø
(4)好了,大功告成,我们再在web-info/jsp/目录下添加一个hello.jsp文件,就可以启动运行我们的第一个程序了。hello.jsp的内容如下:
<%@ page language="java" contentType="text/html; charset=UTF-8"
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
</body>
</html>
运行后访问ip;port/project/hello.html就可以看到我们预期的结果了。
四、springMVC参数传递
mvc结构中,v层不断有数据和c层交互,所以弄明白在springMVC中如何与后台进行数据交互是极其重要的,当然在下面我不会介绍每一个方法,只是对常用的方法,对于我这里没有涉及的方法大家可以参考spring官方的文档中springMVC这个章节。下面我们来看一幅图。
当我们向springMVC发起请求到视图返回前,spring MVC帮我们做了主要是上面几个步骤,通过数据绑定、数据类型转换、验证、结果绑定这几个步骤。
让我们看下实例:
@RequestMapping("/user/find")
}
Ø
Value:指定路径
Method:请求方式
Params:参数
Headers:请求头
后面三个就是对请求路径的一个限制条件。
SpringMVC对于路径的定义非常的灵活
以下URL都是合法的:
l
l
l
l
l
Ø
@RequestParam有以下三个参数。
l
l
l
当发送请求时,请求参数中必须要包含userId这个参数,当不包含这个参数,请求将找不到这个映射。当属性required=true时,不包含这个参数将会抛异常,如果不能确定是否需要这个参数是我们可以写成,@RequestParam(value = "userId", required = false) 。
Ø
Ø
通过 REST 风格体系架构,请求和响应都是基于资源表示的传输来构建的。资源是通过全局 ID 来标识的,这些 ID 一般使用的是一个统一资源标识符(URI)。客户端应用使用 HTTP 方法(如,GET、POST、PUT 或 DELETE)来操作一个或多个资源。通常,GET 是用于获取或列出一个或多个资源,POST 用于创建,PUT 用于更新或替换,而 DELETE 则用于删除资源。
例如,GET http://host/context/employees/12345
将获取 ID 为 12345 的员工的表示。这个响应表示可以是包含详细的员工信息的 XML 或 ATOM,或者是具有更好 UI 的 JSP/HTML 页面。您看到哪种表示方式取决于服务器端实现和您的客户端请求的 MIME 类型。
RESTful Web Service 是一个使用 HTTP 和 REST 原理实现的 Web Service。通常,一个 RESTful Web Service 将定义基本资源 URI、它所支持的表示/响应 MIME,以及它所支持的操作。
Spring 3.0之后引入了对rest风格的支持。我们看实例
@RequestMapping("/user/find/{id}")
user.setName("marcle");
这里需要注意的地方时@RequestMapping("/user/find/{id}")和@PathVariable int id名称必须一样,否则会出现异常。
Ø
u
@RequestMapping("/user/save2")
ModelAndView就是对返回到页面的值和视图进行封装。
u
还有一种传递参数的方法,我放在springMVC中的rest技术介绍
下面我们看看springMVC返回的过程SpringMVC简单没几个标签,用起来还是非常好用的,在使用springMVC中的标签之前需要向每个jsp的头部引入标签支持<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
1)
这个标签会生成一个HTML的form标签,同时为内部标签的绑定暴露了一个绑定路径,它把命令对象(command object)放在pageContext中,这样内部的标签就可以访问这个对象,这个库中的其它标签都是这个标签的嵌套标签。
如我们有个user的域对象,包含id、name、password属性,我们将把它当作返回index.jsp表单控制器的对象,如下面的代码:
<form:form action="${ctx}/user/save.${ext}" method="post" commandName="user">
上述的id、name、password由页面控制器放置在pageContext中,即在内部控制器方法中需要做这样的声明:
@RequestMapping(value="/user/save",method=RequestMethod.GET)
后台控制器中必须绑定这个@ModelAttribute User user命令行对象,而form下面的属性需要于这个user中的属性对应起来,否则将会抛异常。标签经过解析后生成的代码如下:
<form id="user" action="/springTag/user/save.html" method="post">
(2) input 标签
使用时如上面的表达式,<form:input path ="id" />解析后会变成<input id="name" name="name" type="text" value=""/>可见用spring标签比传统的html简洁很多。
(3)checkbox 标签
这个标签解析之后会变成html’中的type为checkbox的input元素,我们假设我们的用户有很多的参考东西,如信息的订阅、爱好、格言等,即如下面的域模型:
public class Preferences {
private boolean receiveNewsletter;
private String[] interests;
private String favouriteWord;
public boolean isReceiveNewsletter() {
return receiveNewsletter;
}
public void setReceiveNewsletter(boolean receiveNewsletter) {
this.receiveNewsletter = receiveNewsletter;
}
public String[] getInterests() {
return interests;
}
public void setInterests(String[] interests) {
this.interests = interests;
}
public String getFavouriteWord() {
return favouriteWord;
}
public void setFavouriteWord(String favouriteWord) {
this.favouriteWord = favouriteWord;
}
}
我们的相应的jsp文件可以写成:
<form:form action="${ctx}/pre/save.${ext}" method="post" commandName="preferences">
</form:form>
如果有多个供选择的,在后台我们以数组的形式存储。
(4)radiobutton 标签
解析后会变成html元素中type为radio的input元素
如下面的情况:
<tr>
<td>Sex:</td>
<td>Male: <form:radiobutton path="sex" value="M"/> <br/>
Female: <form:radiobutton path="sex" value="F"/> </td>
<td> </td>
</tr>
(5)password标签
解析后变成html元素中type为password的input元素,即为密码框。
<tr>
<td>Password:</td>
<td>
<form:password path="password" />
</td>
</tr>
(6)select标签
这个标签对应于html元素中的下拉框,即为select元素。
<tr>
(7)option标签
<form:select path="house">
<form:option value="Gryffindor"/>
<form:option value="Hufflepuff"/>
<form:option value="Ravenclaw"/>
<form:option value="Slytherin"/>
</form:select>
(8)options标签
<form:select path="country">
<form:option value="-" label="--Please Select"/>
<form:options items="${countryList}" itemValue="code" itemLabel="name"/>
</form:select>
(9)textarea标签
<td><form:textarea path="notes" rows="3" cols="20" /></td>
(10)hidden标签
<form:hidden path="house" />
(11)errors标签
<form:form>
<table>
<tr>
<td>First Name:</td>
<td><form:input path="firstName" /></td>
<%-- Show errors for firstName field --%>
<td><form:errors path="firstName" /></td>
</tr>
<tr>
<td>Last Name:</td>
<td><form:input path="lastName" /></td>
<%-- Show errors for lastName field --%>
<td><form:errors path="lastName" /></td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="Save Changes" />
</td>
</tr>
</table>
</form:form>
六、springMVC拦截器
和Struts2一样,Spring MVC也可以使用拦截器对请求进行拦截处理,用户可以自定义拦截器来实现特定的功能,自定义的拦截器必须实现HandlerInterceptor接口。这个接口中定义了三个方法:preHandle()、postHandle()、afterCompletion()。
下面对代码中的三个方法进行解释。
preHandle():这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求request进行处理。如果程序员决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去进行处理,则返回true;如果程序员决定不需要再调用其他的组件去处理请求,则返回false。
postHandle():这个方法在业务处理器处理完请求后,但是DispatcherServlet向客户端返回请求前被调用,在该方法中对用户请求request进行处理。
afterCompletion():这个方法在DispatcherServlet完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。
下面通过一个例子来说明如何使用Spring MVC框架的拦截器。
要求编写一个拦截器,拦截所有不在工作时间的请求,把这些请求转发到一个特定的静态页面,而不对它们的请求进行处理。
首先编写TimeInterceptor.Java,代码如下:
public class TimeInterceptor extends HandlerInterceptorAdapte
}
可以看出,上面的代码重载了preHandle()方法,该方法在业务处理器处理请求之前被调用。在该方法中,首先获得当前的时间,判断其是否在 openingTime和closingTime之间,如果在,返回true,这样才会调用业务控制器去处理该请求;否则直接转向一个静态页面,返回 false,这样该请求就不会被处理。
下面是在dispatcherServlet-servlet.xml中对拦截器进行的配置,代码如下:
<mvc:interceptors>
可以看出,上面代码用bean标签去定义TimeInterceptor,令其id为officeHoursInterceptor,并给它的3个属性赋值。在urlMapping中通过<property name="interceptors">去指定officeHoursInterceptor为一个拦截器,读者可以在<list> 和</list>之间定义多个拦截器
outsideOfficeHours.html的代码很简单,只是输出一句提示语。
运行程序,在浏览器中随便访问一个页面,如果请求的时间在9点~18点之间,则该请求可以被处理;否则,返回一句提示语,如图23-5所示
说 明:在第22章中介绍过控制反转是Spring框架的核心思想,即用一个接口去定义一些操作,在接口的实现类中去重写这些操作,然后在Spring的配置文件中去把该接口的实现类注入到应有框架中,这样就可以通过调用接口去调用接口的实现类。本节讲的拦截器就体现了这种思想,即实现 HandlerInterceptorAdapte r接口,重写preHandle()方法并在配置文件中实现TimeInterceptor的注入。这 样当框架调用HandlerInterceptorAdapte r时,就可以调用到TimeInterceptor类的preHandle()方法
七、spring3 MVC 类型转换
Servlet中的输入参数为都是string类型,而spring mvc通过data bind机制将这些string 类型的输入参数转换为相应的command object(根据view和controller之间传输数据的具体逻辑,也可称为model attributes, domain model objects)。在这个转换过程中,spring实际是先利用java.beans.PropertyEditor中的 setAdText方法来把string格式的输入转换为bean属性,亦可通过继承java.beans.PropertyEditorSupport来实现自定义的PropertyEditors。
自定义完毕propertyEditor后,有以下几种方式来注册自定义的customer propertyEditor. (我只实现了第二种转换方式,至于其它方法大家可以自己尝试)
Ø
名称和java Bean相同但后面带Editor后缀。
例如需要转换的java bean 名为User,则在相同的包中存在UserEditor类可实现customer propertyEditor的自动注册。
Ø
这个在之前的笔记中已经介绍过了,即在controller类中增加一个使用@InitBinder标注的方法,在其中注册customer Editor
public class BaseController {
}
Ø
使用@InitBinder只能对特定的controller类生效,为注册一个全局的customer Editor,可以实现接口WebBindingInitializer 。
public class CustomerBinding implements WebBindingInitializer {
}
并修改 spring-servlet xml配置文件
<bean
但这样一来就无法使用mvc:annotation-driven
使用conversion-service来注册自定义的converter
DataBinder实现了PropertyEditorRegistry, TypeConverter这两个interface,而在spring mvc实际处理时,返回值都是return binder.convertIfNecessary(见HandlerMethodInvoker中的具体处理逻辑)。因此可以使用customer conversionService来实现自定义的类型转换。
<bean id="conversionService"
</bean>
需要修改spring-servlet xml配置文件中的annotation-driven,增加属性conversion-service指向新增的conversionService bean。
<mvc:annotation-driven
conversion-service="conversionService"
对于第二种方式实现如下
Date类型编辑器
public class CustomDateEditor extends PropertyEditorSupport {
}
@InitBinder来注册customer propertyEditor
@InitBinder
最后讲讲对于requestBody或httpEntity中数据的类型转换
Spring MVC中对于requestBody中发送的数据转换不是通过databind来实现,而是使用HttpMessageConverter来实现具体的类型转换。
例如,之前提到的json格式的输入,在将json格式的输入转换为具体的model的过程中,spring mvc首先找出request header中的contenttype,再遍历当前所注册的所有的HttpMessageConverter子类,根据子类中的canRead()方法来决定调用哪个具体的子类来实现对requestBody中的数据的解析。如果当前所注册的 httpMessageConverter中都无法解析对应contexttype类型,则抛出 HttpMediaTypeNotSupporte
那么需要如何注册自定义的messageConverter呢,很不幸,在spring 3.0.5中如果使用annotation-driven的配置方式的话,无法实现自定义的messageConverter的配置,必须老老实实的自己定义AnnotationMethodHandlerA
<mvc:annotation-driven>
</mvc:annotation-driven>
八、json格式数据的输入和输出
Spring mvc处理json需要使用jackson的类库,因此为支持json格式的输入输出需要先修改pom.xml增加jackson包的引用
<dependency>
</dependency>
在spring-servlet.xml中必须加入这段代码:<mvc:annotation-driven />
根据前面的分析,在spring mvc中解析输入为json格式的数据有两种方式
1:使用@RequestBody来设置输入
@RequestMapping("/json1")
}
2:使用HttpEntity来实现输入绑定
对应Json格式的输出也对应有两种方式
1:使用@responseBody来设置输出内容为context body
@RequestMapping(value="/kfc/brands/{name}", method = RequestMethod.GET)
}
当我们在地址栏中输入:http://localhost:8080/springJson/kfc/brands/kfc_name.html
服务器端会返回给我们jason格式的数据,这样我们就可以省去手工繁琐的组并了
2:返回值设置为ResponseEntity<?>类型,以返回context body
@RequestMapping("/json2")
九、spring3mvc文件上传
Spring mvc使用jakarta的commons fileupload来支持文件上传,因此我们需要在pom.xml中导入所依赖的两个包:
<dependency>
</dependency>
在spring-servlet.xml中加入以下这段代码:
<bean id="multipartResolver"
其中的property中可以限制最大和最小文件上传。
在客户端的代码如下:
<form method="post" action="${ctx}/user/upload.${ext}" enctype="multipart/form-data">
<input type="text" name="name"/>
<input type="file" name="file"/>
<input type="submit"/>
</form>
服务器端的代码如下:
@RequestMapping(value = "/user/upload", method = RequestMethod.POST)
getRealPath("/");
十、spring mvc国际化和本地化
何为国际化,简单来说就是在那个国家显示哪个国家的语言,在计算机中,国际化和本地化意味着计算机软件要适应不同的语言和地区的差异。国际化就是设计为了适应不同地区和语言的差异而工程不需要做任何改动。
这一节的目的就是在springMVC中增加国际化和本地化的应用,我们将在这一节实现三种语言可以相互切换的国际化和本地化。
(1)我们在resources下面添加三个property文件,分别为:messages_de.properties、messages_en.properties、messages_zh.properties,文件的命名规则:messages_语言.properties
三个文件的内容如下:
Ø
label.firstname=Vorname
label.lastname=Familiename
label.email=Email
label.telephone=Telefon
label.addcontact=Addieren Kontakt
label.title=spring mvc Internationalization (i18n) / Localization
Ø
label.firstname=First Name
label.lastname=Last Name
label.email=Email
label.telephone=Telephone
label.addcontact=Add Contact
label.title=spring mvc Internationalization (i18n) / Localization
Ø
label.firstname=\u59D3
label.lastname=\u540D\u5B57
label.email=\u7535\u5B50\u90AE\u4EF6
label.telephone=\u7535\u8BDD
label.addcontact=\u8054\u7CFB\u65B9\u5F0F
label.title=spring mvc \u56FD\u9645\u5316\u548C\u672C\u5730\u5316\u652F\u6301
(2)spring-servet.xml文件的配置
<!--
<!—session 解析区域
<!-- property name="defaultLocale" value="en"/> -->
<!--
(3)在jsp目录下面创建一个contact.jsp文件
<%@ page language="java" contentType="text/html; charset=UTF-8"
<%@taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:set var="ctx" value="${pageContext.request.contextPath}" />
<c:set var="ext" value="html" />
<html>
<head>
</head>
<body>
<h3><spring:message code="label.title"/></h3>
<span style="float: right">
</span>
<form:form method="post" action="addContact.html" commandName="contact">
</table>
</form:form>
</body>
</html>
其中<spring:message>标签结合 ResourceBundleMessageSou
(4)创建LanguageController
@Controller
public class LanguageController {
}
其中红色部分就是对语言的设置
效果如下图:
十一、使用jsr303 进行验证
JSR 303 – Bean Validation 是一个数据验证的规范,2009 年 11 月确定最终方案。2009 年 12 月 Java EE 6 发布,Bean Validation 作为一个重要特性被包含其中,Spring MVC在使用了<mvc:annotation-driven> 后,如果路径中有jsr 303的实现,将自动提供对jsr 303验证方式的支持。
Ø
</dependency>
Ø
public class Contact {
}
Ø
Ø
<form:form action="${ctx }/contact/add.${ext}" method="post"
Ø
使用jsr 303非常简单吧,有些人就问了,可以不可以自定义错误信息,当然是可以的,下面我就通过自定义错误来实现对contact的校验。
@Size(min = 1, message = "Contact first name is required.")
只要将错误信息写到注解后面的message中即可,简单吧,我们再来看看jsr 303 主要的注解有哪些?
表 1. Bean Validation 中内置的 constraint
注 | 功能说明 |
@Null | |
@NotNull | |
@AssertTrue | |
@AssertFalse | |
@Min(value) | |
@Max(value) | |
@DecimalMin(value) | |
@DecimalMax(value) | |
@Size(max, min) | |
@Digits (integer, fraction) | |
@Past | |
@Future | |
| 被注释的元素必须符合指定的正则表达式 |
表 2. Hibernate Validator 附加的 constraint
|
|
|
|
有人写就问了,那么可以不可以自定义注释类型呢?答案当然是可以的。
Ø
(1)@Age是一个定制化的 constraint,由两个内置的 constraint 组合而成。代码如下
@Min(1)
@Constraint(validatedBy = {})
@Documented
@Target( { ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Age {
}
(2)status是一个重新写得注释类型
@Status 的 annotation 部分
@Constraint(validatedBy = {StatusValidator.class})
@Documented
@Target( { ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Status {
}
@Status 的 constraint validator 部分
public class StatusValidator implements ConstraintValidator<Status, Integer> {
}
如果大家对于注释怎么写不够了解,请参考其它相关文档。
在属性上增加age、status
@Age
private int age;
@Status
private int status;
运行结果如下