1 SpringMVC架构
1.1 Spring web mvc介绍
Spring web mvc和Struts2都属于表现层的框架,它是Spring框架的一部分,我们可以从Spring的整体结构中看得出来:
1.2 Web MVC
mvc设计模式在b/s系统下应用:
1、 用户发起request请求至控制器(Controller)
控制接收用户请求的数据,委托给模型进行处理
2、 控制器通过模型(Model)处理数据并得到处理结果
模型通常是指业务逻辑
3、 模型处理结果返回给控制器
4、 控制器将模型数据在视图(View)中展示
web中模型无法将数据直接在视图上显示,需要通过控制器完成。如果在C/S应用中模型是可以将数据在视图中展示的。
5、 控制器将视图response响应给用户
通过视图展示给用户要的数据或处理结果。
1.3 Spring web mvc 架构
1.3.1 架构图
1.3.2 架构流程
1、 用户发送请求至前端控制器DispatcherServlet
2、 DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3、 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
4、 DispatcherServlet通过HandlerAdapter处理器适配器调用处理器
5、 执行处理器(Controller,也叫后端控制器)。
6、 Controller执行完成返回ModelAndView
7、 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
8、 DispatcherServlet将ModelAndView传给ViewReslover视图解析器
9、 ViewReslover解析后返回具体View
10、 DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。
11、 DispatcherServlet响应用户
1.3.3 组件说明
以下组件通常使用框架提供实现:
u DispatcherServlet:前端控制器
用户请求到达前端控制器,它就相当于mvc模式中的c,dispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet的存在降低了组件之间的耦合性。
u HandlerMapping:处理器映射器
HandlerMapping负责根据用户请求找到Handler即处理器,springmvc提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
u Handler:处理器
Handler 是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。
由于Handler涉及到具体的用户业务请求,所以一般情况需要程序员根据业务需求开发Handler。
u HandlAdapter:处理器适配器
通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
u View Resolver:视图解析器
View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。 springmvc框架提供了很多的View视图类型,包括:jstlView、freemarkerView、pdfView等。
一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面。
2 商品订单业务说明
本教程在通过商品订单业务学习使用springmvc进行功能开发。
2.1 业务流程
1、管理员维护商品信息
2、用户挑选商品,购买,创建订单
2.2 数据库环境
先导入sql_table.sql,再导入 sql_data.sql脚本:
如下:
2.3 商品订单数据模型
3 SpringMVC入门
3.1 需求
实现商品查询列表功能。
3.2 开发环境准备
本教程使用Eclipse+tomcat7开发
详细参考“Eclipse开发环境配置-indigo.docx”文档
3.3 第一步:建立一个Web项目
在eclipse下创建动态web工程springmvc_first。
3.4 第二步:导入spring3.2.0的jar包
3.5 第三步:前端控制器配置
在WEB-INF\web.xml中配置前端控制器,
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
load-on-startup:表示servlet随服务启动;
url-pattern:*.action的请交给DispatcherServlet处理。
contextConfigLocation:指定springmvc配置的加载位置,如果不指定则默认加
载WEB-INF/[DispatcherServlet的Servlet 名字]-servlet.xml。
3.5.1 Servlet拦截方式
1、拦截固定后缀的url,比如设置为 *.do、*.action, 例如:/user/add.action
此方法最简单,不会导致静态资源(jpg,js,css)被拦截。
2、拦截所有,设置为/,例如:/user/add /user/add.action
此方法可以实现REST风格的url,很多互联网类型的应用使用这种方式。
但是此方法会导致静态文件(jpg,js,css)被拦截后不能正常显示。需要特殊处理。
3、拦截所有,设置为
privatestaticfinallongserialVersionUID = -5212079010855161498L;
public CustomException(Stringmessage){
super(message);
this.message =message;
}
//异常信息
private Stringmessage;
public String getMessage() {
returnmessage;
}
publicvoid setMessage(String message) {
this.message = message;
}
}
6.3.3 自定义异常处理器
publicclass CustomExceptionResolverimplements HandlerExceptionResolver {
@Override
public ModelAndViewresolveException(HttpServletRequest request,
HttpServletResponseresponse, Object handler, Exception ex) {
ex.printStackTrace();
CustomExceptioncustomException =null;
//如果抛出的是系统自定义异常则直接转换
if(exinstanceof CustomException){
customException =(CustomException)ex;
}else{
//如果抛出的不是系统自定义异常则重新构造一个未知错误异常。
customException = new CustomException("未知错误,请与系统管理员联系!");
}
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("message", customException.getMessage());
modelAndView.setViewName("error");
returnmodelAndView;
}
}
6.3.4 错误页面
<%@pagelanguage="java"contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@tagliburi="http://java.sun.com/jsp/jstl/core"prefix="c"%>
<%@tagliburi="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<!DOCTYPEhtmlPUBLIC "-//W3C//DTDHTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<metahttp-equiv="Content-Type"content="text/html; charset=UTF-8">
<title>错误页面</title>
</head>
<body>
您的操作出现错误如下:<br/>
${message }
</body>
</html>
6.3.5 异常处理器配置
在springmvc.xml中添加:
<!--异常处理器 -->
<bean id="handlerExceptionResolver"class="cn.itcast.ssm.controller.exceptionResolver.CustomExceptionResolver"/>
6.3.6 异常测试
修改商品信息,id输入错误提示商品信息不存在。
修改controller方法“editItem”,调用service查询商品信息,如果商品信息为空则抛出异常:
//调用service查询商品信息
Items item = itemService.findItemById(id);
if(item ==null){
thrownew CustomException("商品信息不存在!");
}
在service中抛出异常方法同上。
6.4 上传图片
6.4.1 配置虚拟目录
在tomcat上配置图片虚拟目录,在tomcat下conf/server.xml中添加:
<Context docBase="F:\develop\upload\temp"path="/pic" reloadable="false"/>
访问http://localhost:8080/pic即可访问F:\develop\upload\temp下的图片。
也可以通过eclipse配置:
6.4.2 配置解析器
<!--文件上传 -->
<beanid="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设置上传文件的最大尺寸为5MB -->
<property name="maxUploadSize">
<value>5242880</value>
</property>
</bean>
6.4.3 jar包
CommonsMultipartResolver解析器依赖commons-fileupload和commons-io,加入如下jar包:
6.4.4 图片上传
u controller:
//商品修改提交
@RequestMapping("/editItemSubmit")
public String editItemSubmit(Items items,MultipartFile pictureFile)throws Exception{
//原始文件名称
String pictureFile_name = pictureFile.getOriginalFilename();
//新文件名称
String newFileName = UUID.randomUUID().toString()+pictureFile_name.substring(pictureFile_name.lastIndexOf("."));
//上传图片
File uploadPic = new java.io.File("F:/develop/upload/temp/"+newFileName);
if(!uploadPic.exists()){
uploadPic.mkdirs();
}
//向磁盘写文件
pictureFile.transferTo(uploadPic);
.....
u 页面:
form添加enctype="multipart/form-data":
<formid="itemForm"
action="${pageContext.request.contextPath }/item/editItemSubmit.action"
method="post"enctype="multipart/form-data">
<input type="hidden"name="pic"value="${item.pic }"/>
file的name与controller形参一致:
<tr>
<td>商品图片</td>
<td><c:iftest="${item.pic !=null}">
<img src="/pic/${item.pic}"width=100height=100/>
<br />
</c:if><inputtype="file"name="pictureFile"/></td>
</tr>
6.5 json数据交互
6.5.1 @RequestBody
作用:
@RequestBody注解用于读取http请求的内容(字符串),通过springmvc提供的HttpMessageConverter接口将读到的内容转换为json、xml等格式的数据并绑定到controller方法的参数上。
本例子应用:
@RequestBody注解实现接收http请求的json数据,将json数据转换为java对象
6.5.2 @ResponseBody
作用:
该注解用于将Controller的方法返回的对象,通过HttpMessageConverter接口转换为指定格式的数据如:json,xml等,通过Response响应给客户端
本例子应用:
@ResponseBody注解实现将controller方法返回对象转换为json响应给客户端
6.5.3 请求json,响应json实现:
6.5.3.1 环境准备
Springmvc默认用MappingJacksonHttpMessageConverter对json数据进行转换,需要加入jackson的包,如下:
6.5.3.2 配置json转换器
在注解适配器中加入messageConverters
<!--注解适配器 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<propertyname="messageConverters">
<list>
<beanclass="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"></bean>
</list>
</property>
</bean>
注意:如果使用<mvc:annotation-driven/> 则不用定义上边的内容。
6.5.3.3 controller编写
//商品修改提交json信息,响应json信息
@RequestMapping("/editItemSubmit_RequestJson")
public@ResponseBody Items editItemSubmit_RequestJson(@RequestBody Items items)throws Exception {
System.out.println(items);
//itemService.saveItem(items);
return items;
}
6.5.3.4 页面js方法编写:
引入 js:
<script type="text/javascript"
src="${pageContext.request.contextPath}/js/jquery-1.4.4.min.js"></script>
//请求json响应json
function request_json(){
$.ajax({
type:"post",
url:"${pageContext.request.contextPath}/item/editItemSubmit_RequestJson.action",
contentType:"application/json;charset=utf-8",
data:'{"name":"测试商品","price":99.9}',
success:function(data){
alert(data);
}
});
}
6.5.3.5 测试结果:
从上图可以看出请求的数据是json格式
6.5.4 请key/value,响应json实现:
表单默认请求application/x-www-form-urlencoded格式的数据即key/value,通常有post和get两种方法,响应json数据是为了方便客户端处理,实现如下:
6.5.4.1 环境准备
同第一个例子
6.5.4.2 controller编写
// 商品修改提交,提交普通form表单数据,响应json
@RequestMapping("/editItemSubmit_ResponseJson")
public@ResponseBody ItemseditItemSubmit_ResponseJson(Items items)throws Exception {
System.out.println(items);
// itemService.saveItem(items);
return items;
}
6.5.4.3 页面js方法编写:
function formsubmit(){
var user ="name=测试商品&price=99.9";
alert(user);
$.ajax(
{
type:'post',//这里改为get也可以正常执行
url:'${pageContext.request.contextPath}/item/editItemSubmit_RequestJson.action',
//ContentType没指定将默认为:application/x-www-form-urlencoded
data:user,
success:function(data){
alert(data.name);
}
}
)
}
从上边的js代码看出,已去掉ContentType的定义,ContentType默认为:application/x-www-form-urlencoded格式。
6.5.4.4 测试结果
从上图可以看出请求的数据是标准的key/value格式。
6.5.5 小结
实际开发中常用第二种方法,请求key/value数据,响应json结果,方便客户端对结果进行解析。
6.6 RESTful支持
6.6.1 需求
RESTful方式实现商品信息查询,返回json数据
6.6.2 添加DispatcherServlet的rest配置
<servlet>
<servlet-name>springmvc-servlet-rest</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc-servlet-rest</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
6.6.3 URL 模板模式映射
@RequestMapping(value="/viewItems/{id}"):{×××}占位符,请求的URL可以是“/viewItems/1”或“/viewItems/2”,通过在方法中使用@PathVariable获取{×××}中的×××变量。
@PathVariable用于将请求URL中的模板变量映射到功能处理方法的参数上。
@RequestMapping("/viewItems/{id}")
public @ResponseBody viewItems(@PathVariable("id") String id,Model model)throws Exception{
//方法中使用@PathVariable获取useried的值,使用model传回页面
//调用 service查询商品信息
ItemsCustom itemsCustom =itemsService.findItemsById(id);
return itemsCustom;
}
如果RequestMapping中表示为"/viewItems/{id}",id和形参名称一致,@PathVariable不用指定名称。
6.6.4 静态资源访问<mvc:resources>
如果在DispatcherServlet中设置url-pattern为 /则必须对静态资源进行访问处理。
spring mvc 的<mvc:resourcesmapping="" location="">实现对静态资源进行映射访问。
如下是对js文件访问配置:
<mvc:resources location="/js/"mapping="/js
@Override
Public boolean preHandle(HttpServletRequestrequest,
HttpServletResponseresponse, Object handler)throws Exception {
// TODO Auto-generated method stub
Return false;
}
@Override
Public void postHandle(HttpServletRequestrequest,
HttpServletResponseresponse, Object handler,
ModelAndView modelAndView)throws Exception {
// TODO Auto-generated method stub
}
@Override
Public void afterCompletion(HttpServletRequestrequest,
HttpServletResponseresponse, Object handler, Exception ex)
throws Exception {
// TODO Auto-generated method stub
}
}
7.3 拦截器配置
7.3.1 针对某种mapping配置拦截器
<bean
class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
<property name="interceptors">
<list>
<ref bean="handlerInterceptor1"/>
<ref bean="handlerInterceptor2"/>
</list>
</property>
</bean>
<bean id="handlerInterceptor1"class="springmvc.intercapter.HandlerInterceptor1"/>
<beanid="handlerInterceptor2"class="springmvc.intercapter.HandlerInterceptor2"/>
7.3.2 针对所有mapping配置全局拦截器
<!--拦截器 -->
<mvc:interceptors>
<!--多个拦截器,顺序执行 -->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<beanclass="cn.itcast.springmvc.filter.HandlerInterceptor1"></bean>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="cn.itcast.springmvc.filter.HandlerInterceptor2"></bean>
</mvc:interceptor>
</mvc:interceptors>
7.4 正常流程测试
7.4.1 代码:
定义两个拦截器分别为:HandlerInterceptor1和HandlerInteptor2,每个拦截器的preHandler方法都返回true。
7.4.2 运行流程
HandlerInterceptor1..preHandle..
HandlerInterceptor2..preHandle..
HandlerInterceptor2..postHandle..
HandlerInterceptor1..postHandle..
HandlerInterceptor2..afterCompletion..
HandlerInterceptor1..afterCompletion..
7.5 中断流程测试
7.5.1 代码:
定义两个拦截器分别为:HandlerInterceptor1和HandlerInteptor2。
7.5.2 运行流程
HandlerInterceptor1的preHandler方法返回false,HandlerInterceptor2返回true,运行流程如下:
HandlerInterceptor1..preHandle..
从日志看出第一个拦截器的preHandler方法返回false后第一个拦截器只执行了preHandler方法,其它两个方法没有执行,第二个拦截器的所有方法不执行,且controller也不执行了。
HandlerInterceptor1的preHandler方法返回true,HandlerInterceptor2返回false,运行流程如下:
HandlerInterceptor1..preHandle..
HandlerInterceptor2..preHandle..
HandlerInterceptor1..afterCompletion..
从日志看出第二个拦截器的preHandler方法返回false后第一个拦截器的postHandler没有执行,第二个拦截器的postHandler和afterCompletion没有执行,且controller也不执行了。
总结:
preHandle按拦截器定义顺序调用
postHandler按拦截器定义逆序调用
afterCompletion按拦截器定义逆序调用
postHandler在拦截器链内所有拦截器返成功调用
afterCompletion只有preHandle返回true才调用
7.6 拦截器应用
7.6.1 用户身份认证
Public class LoginInterceptorimplements HandlerInterceptor{
@Override
Public boolean preHandle(HttpServletRequestrequest,
HttpServletResponseresponse, Object handler)throws Exception {
//如果是登录页面则放行
if(request.getRequestURI().indexOf("login.action")>=0){
return true;
}
HttpSession session =request.getSession();
//如果用户已登录也放行
if(session.getAttribute("user")!=null){
return true;
}
//用户没有登录挑战到登录页面
request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);
return false;
}
}
7.6.2 用户登陆controller
//登陆提交
//userid:用户账号,pwd:密码
@RequestMapping("/login")
public String loginsubmit(HttpSessionsession,String userid,String pwd)throws Exception{
//向session记录用户身份信息
session.setAttribute("activeUser", userid);
return"redirect:item/queryItem.action";
}
//退出
@RequestMapping("/logout")
public String logout(HttpSession session)throws Exception{
//session过期
session.invalidate();
return"redirect:item/queryItem.action";
}