学习笔记
1 Springmvc框架
1.1 什么是springmvc
springmvc是spring框架的一个模块,springmvc和spring无需通过中间整合层整合。
springmvc是一个基于mvc的web框架。
1.2 mvc在b/s系统下的应用
mvc是一个设计模式,mvc在b/s系统下的应用
1.3 springmvc框架
2 入门程序
2.1 环境
- 数据库环境:sqlserver2008
- Java环境:jdk1.7.0_72
- eclipse indigo
- springmvc版本:spring4.2
2.2 需求
springmvc和mybatis使用一个案例(商品订单)
功能需求:商品列表查询
2.3 配置前端控制器
-
在web.xml中配置
<!-- 配置前端控制器 --> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- contextConfigLocation配置springmvc加载的配置文件(配置处理器映射器、适配器等等) 如果不配置contextConfigLocation,默认加载的是/WEB-INF/servlet名称/serlvet.xml(springmvc/servlet.xml) --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <!-- 第一种:*.action,访问以action结尾的由DispatcherServlet进行解析 第二种:/,所有访问的地址都由DispatcherServlet进行解析,对于静态文件解析需要配置不让DispatcherServlet解析 使用此种方法可以实现RESTful风格 第三种:/*,这样配置不对,使用这种配置最终要转发到jsp界面时, 仍由DispatcherServlet解析jsp,不能根据jsp页面找到Handler,所以会报错 --> <url-pattern>*.action</url-pattern> </servlet-mapping>
2.4 处理器适配器
-
在classpath下的springmvc.xml中配置
<!-- 处理器适配器 所有的处理器适配器都实现HandlerAdapter接口 --> <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />
-
通过查看源代码,此适配器能执行实现controller接口的Handler
public class SimpleControllerHandlerAdapter implements HandlerAdapter { public boolean supports(Object handler) { return (handler instanceof Controller); } ...... }
2.5 开发Handler
-
需要实现controller接口,才能由org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter适配器执行
public class ItemsController1 implements Controller{ @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { //调用service查找数据库,查询商品列表,这里使用静态数据模拟 List<Items> itemsList = new ArrayList<Items>(); Items items_1 = new Items(); items_1.setName("联想笔记本"); items_1.setPrice(6000f); items_1.setDetail("ThinkPad T430 联想笔记本电脑!"); Items items_2 = new Items(); items_2.setName("苹果手机"); items_2.setPrice(5000f); items_2.setDetail("iphone6苹果手机!"); itemsList.add(items_1); itemsList.add(items_2); //返回ModelAndView ModelAndView modelAndView = new ModelAndView(); //相当于request的setAttribute,在jsp页面中通过itemsList取数据 modelAndView.addObject("itemsList", itemsList); //指定视图 modelAndView.setViewName("/WEB-INF/jsp/items/itemsList.jsp"); return modelAndView; } }
2.6 视图编写
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<!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>查询商品列表</title>
</head>
<body>
<form action="${pageContext.request.contextPath }/item/queryItem.action" method="post">
查询条件:
<table width="100%" border=1>
<tr>
<td><input type="submit" value="查询"/></td>
</tr>
</table>
商品列表:
<table width="100%" border=1>
<tr>
<td>商品名称</td>
<td>商品价格</td>
<td>生产日期</td>
<td>商品描述</td>
<td>操作</td>
</tr>
<c:forEach items="${itemsList }" var="item">
<tr>
<td>${item.name }</td>
<td>${item.price }</td>
<td><fmt:formatDate value="${item.createtime}" pattern="yyyy-MM-dd HH:mm:ss"/></td>
<td>${item.detail }</td>
<td><a href="${pageContext.request.contextPath }/item/editItem.action?id=${item.id}">修改</a></td>
</tr>
</c:forEach>
</table>
</form>
</body>
</html>
2.7 配置Handler
-
将编写Handler在spring容器加载
<!-- 配置Handler --> <bean name="/queryItems.action" class="cn.itcast.ssm.controller.ItemsController1"/>
2.8 配置处理器映射器
-
在classpath下的springmvc.xml中配置
<!-- 处理器映射器 将bean的name作为URL进行查找,需要在配置Handler是指定beanname(就是url) --> <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />
2.9 配置视图解析器
-
需要配置解析jsp的视图解析器
<!-- 视图解析器 解析jsp视图,默认实用jstl标签,classpath下面得有jstl的包 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" />
2.10 部署调试
- 访问地址:http://localhost:8080/SpringMVCStudy/queryItems.action
- 如果地址输错,处理器映射器根据url找不到Handler,报下边的错误。说明url错误。
- 如果处理器映射器根据url找到了Handler,转发的jsp页面找到,报下边的错误,说明jsp页面地址错误了。
//指定视图里的ViewName可能写错了,不存在你写的那个jsp文件
modelAndView.setViewName("/WEB-INF/jsp/items/itemsList.jsp");
3 非注解的处理器映射器和适配器
3.1 非注解的处理器映射器
-
处理器映射器:
org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping<!-- 配置Handler --> <bean id="itemsController1" name="/queryItems.action" class="cn.itcast.ssm.controller.ItemsController1"/> <!-- 处理器映射器 将bean的name作为URL进行查找,需要在配置Handler是指定beanname(就是url) --> <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />
-
另外一个映射器
org.springframework.web.servlet.handler.SimpleUrlHandlerMapping<!--简单url映射 --> <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <!-- 对itemsController1进行url映射,url是/queryItems1.action,1个bean可以配多个url --> <prop key="/queryItems1.action">itemsController1</prop> <prop key="/queryItems2.action">itemsController1</prop> </props> </property> </bean>
-
多个映射器可以并存,前端控制判断url能让哪个映射器处理,就让哪个映射器处理。
3.2 非注解的处理器适配器
-
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter
要求编写的Handler实现Controller接口<!-- 处理器适配器 所有的处理器适配器都实现HandlerAdapter接口 --> <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />
-
org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter
要求编写的Handler实现HttpRequestHandler接口<!-- 另一个非注解的适配器 --> <bean class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter" />
-
ItemsController2
public class ItemsController1 implements HttpRequestHandler{ @Override public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //调用service查找数据库,查询商品列表,这里使用静态数据模拟 List<Items> itemsList = new ArrayList<Items>(); Items items_1 = new Items(); items_1.setName("联想笔记本"); items_1.setPrice(6000f); items_1.setDetail("ThinkPad T430 联想笔记本电脑!"); Items items_2 = new Items(); items_2.setName("苹果手机"); items_2.setPrice(5000f); items_2.setDetail("iphone6苹果手机!"); itemsList.add(items_1); itemsList.add(items_2); //设置模型数据 request.setAttribute("itemsList", itemsList); //设置转发的视图 request.getRequestDispatcher("/WEB-INF/jsp/items/itemsList.jsp").forward(request, response); //使用此方法可以通过修改response,设置响应的数据格式,比如json数据 } }
-
别忘了配置Handler和映射器
<!-- 配置另外一个Handler --> <bean id="itemsController2" class="cn.itcast.ssm.controller.ItemsController1"></bean> <!--简单url映射 --> <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <!-- 对itemsController1进行url映射,url是/queryItems1.action,1个bean可以配多个url --> <prop key="/queryItems1.action">itemsController1</prop> <prop key="/queryItems2.action">itemsController1</prop> <prop key="/queryItems3.action">itemsController2</prop> </props> </property> </bean>
-
总的来说,处理器映射器就是根据URL来找Handler,处理器适配器就是按照它要求的规则(handler instanceof XXX接口)去执行Handler。
4 DispatcherSerlvet.properties
通过以前咱们写的样例,会发现,多个适配器和多个映射器可以并存但是其实你不在springmvc.xml中配置适配器,也可以正常运行程序。因为在SpringMVC的jar包中含有一个默认配置文件,如果没有在springmvc.xml配置,就默认使用DispatcherSerlvet.properties的配置如图
前端控制器会从上边的文件中加载处理映射器、适配器、视图解析器等组件,如果不在springmvc.xml中配置,使用默认加载的。
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.
org.springframework.web.servlet.LocaleResolver=
org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=
org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping= org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
org.springframework.web.servlet.HandlerAdapter=
org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=
org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=
org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=
org.springframework.web.servlet.support.SessionFlashMapManager
5 注解的处理器映射器和适配器
- 注解映射器
- 在spring3.1之前,使用org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMappin注解映射器。
- 在spring3.1之后,使用org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping注解映射器。
- 注解适配器
- 在spring3.1之前使用org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter注解适配器。
- 在spring3.1之后使用org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter注解适配器。
5.1 配置注解映射器和适配器
<!-- 注解映射器 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<!-- 注解适配器 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
<!-- 使用mvc的注解驱动mvc:annotation-driven可以代替上边的注解映射器和注解适配器配置
mvc:annotation-driven默认加载很多参数绑定方法,比如json转换解析器
如果使用mvc:annotation-driven就不用配置上边的RequestMappingHandlerMapping和RequestMappingHandlerAdapter
实际开发使用RequestMappingHandlerAdapter
-->
<!-- <mvc:annotation-driven></mvc:annotation-driven> -->
5.2 开发注解Handler
-
必须同时使用注解的映射器和注解的适配器,必须配对使用。
//使用Controller标识他是一个控制器 @Controller public class ItemsController3 { //商品查询列表 //@RequestMapping实现queryItems方法和url进行映射,一个方法对应一个url //一般建议将url和方法名写成一样 @RequestMapping("/queryItems") public ModelAndView queryItems() throws Exception{ //调用service查找数据库,查询商品列表,这里使用静态数据模拟 List<Items> itemsList = new ArrayList<Items>(); Items items_1 = new Items(); items_1.setName("联想笔记本"); items_1.setPrice(6000f); items_1.setDetail("ThinkPad T430 联想笔记本电脑!"); Items items_2 = new Items(); items_2.setName("苹果手机"); items_2.setPrice(5000f); items_2.setDetail("iphone6苹果手机!"); itemsList.add(items_1); itemsList.add(items_2); //返回ModelAndView ModelAndView modelAndView = new ModelAndView(); //相当于request的setAttribute,在jsp页面中通过itemsList取数据 modelAndView.addObject("itemsList", itemsList); //指定视图 modelAndView.setViewName("/WEB-INF/jsp/items/itemsList.jsp"); return modelAndView; } }
5.3 在spring中加载Handler
<!-- 对于注解Handler可以单个进行配置
实际开发中建议使用组件扫描
-->
<!-- <bean class="cn.itcast.ssm.controller.ItemsController3"></bean> -->
<!-- 可以扫描controller,service,...
这里让组件扫描controller,指定controller的包
-->
<context:component-scan base-package="cn.itcast.ssm.controller"></context:component-scan>
5.4 部署测试
6 源码分析
-
通过前端控制区源码分析springmvc的执行过程
-
点开org.springframework.web.servlet.DispatcherServlet,里面有doDiapatch的方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = checkMultipart(request); multipartRequestParsed = processedRequest != request; // Determine handler for the current request. mappedHandler = getHandler(processedRequest, false); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { String requestUri = urlPathHelper.getRequestUri(request); logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } try { // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); } finally { if (asyncManager.isConcurrentHandlingStarted()) { return; } } applyDefaultViewName(request, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Error err) { triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); return; } // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } }
-
第一步:前端控制器接收请求:.action类型的URL通过过滤器进入DispatcherServlet类,调用其doDiapatch()方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); ...... }
-
第二步:前端控制器调用处理器映射器查找Handler:在doDiapatch()方法中调用了DispatcherServlet类的getHandler方法
HandlerExecutionChain mappedHandler = null; ...... mappedHandler = getHandler(processedRequest, false);
-
其中getHandler方法:
@Deprecated protected HandlerExecutionChain getHandler(HttpServletRequest request, boolean cache) throws Exception { return getHandler(request); } protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { for (HandlerMapping hm : this.handlerMappings) { if (logger.isTraceEnabled()) { logger.trace( "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'"); } HandlerExecutionChain handler = hm.getHandler(request); if (handler != null) { return handler; } } return null; }
- 说明映射器根据request当中的URL,找到了Handler,最终返回一个执行器的链(HandlerExecutionChain)。这个链里面有Handler。
-
第三步:调用处理器适配器执行Handler,得到执行结果ModelAndView
ModelAndView mv = null; ...... mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
-
第四步:视图渲染,将model数据填充到request域
-
视图解析,得到view,在doDiapatch()方法中有这一句
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
-
其中processDispatchResult方法
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception { boolean errorView = false; if (exception != null) { if (exception instanceof ModelAndViewDefiningException) { logger.debug("ModelAndViewDefiningException encountered", exception); mv = ((ModelAndViewDefiningException) exception).getModelAndView(); } else { Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); mv = processHandlerException(request, response, handler, exception); errorView = (mv != null); } } // Did the handler return a view to render? if (mv != null && !mv.wasCleared()) { render(mv, request, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } else { if (logger.isDebugEnabled()) { logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() + "': assuming HandlerAdapter completed request handling"); } } if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Concurrent handling started during a forward return; } if (mappedHandler != null) { mappedHandler.triggerAfterCompletion(request, response, null); } }
-
其中render(mv, request, response);方法中有
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
-
渲染方法:
view.render(mv.getModelInternal(), request, response);
-
-
调用视图渲染方法,将model数据填充到request域
protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception { //遍历model里面的数据,填充到request域 for (Map.Entry<String, Object> entry : model.entrySet()) { String modelName = entry.getKey(); Object modelValue = entry.getValue(); if (modelValue != null) { request.setAttribute(modelName, modelValue); if (logger.isDebugEnabled()) { logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() + "] to request in view with name '" + getBeanName() + "'"); } } else { request.removeAttribute(modelName); if (logger.isDebugEnabled()) { logger.debug("Removed model object '" + modelName + "' from request in view with name '" + getBeanName() + "'"); } } } }
-
-
7 入门程序小结
通过入门程序理解springmvc前端控制器、处理器映射器、处理器适配器、视图解析器用法。
前端控制器配置:
- 第一种:*.action,访问以action结尾的由DispatcherServlet进行解析
- 第二种:/,所有访问的地址都由DispatcherServlet进行解析,对于静态文件解析需要配置不让DispatcherServlet解析,使用此种方法可以实现RESTful风格
处理器映射器:
- 非注解处理器映射器(了解)
- 注解的处理器映射器(掌握)
- 对标记有@Controller类中标识有@RequestMapping的方法进行映射。在@RequestMapping里边定义映射的url。
- 使用注解映射器不用再xml中配置url和Handler的映射关系。
处理器适配器:
-
非注解处理器适配器(了解)
-
注解的处理器适配器(掌握)
-
注解的处理器映射器和注解的处理器适配器是配对使用的。
-
<mvc:annotation-driven></mvc:annotation-driven>
可以代替下面的配置<!-- 注解映射器 --> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/> <!-- 注解适配器 --> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
-
-
实际开发使用:mvc:annotation-driven
-
视图解析器配置前缀和后缀
<!-- 视图解析器
解析jsp视图,默认实用jstl标签,classpath下面得有jstl的包
-->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 配置jsp路径的前缀 -->
<property name="prefix" value="/WEB-INF/jsp/"></property>
<!-- 配置jsp路径的后缀 -->
<property name="suffix" value=".jsp"></property>
</bean>
-
程序中不用指定前缀和后缀:
//指定视图 //下边的路径,如果在视图解析器中配置jsp的路径前缀和后缀,修改为items/itemsList //modelAndView.setViewName("/WEB-INF/jsp/items/itemsList.jsp") //下边的路径配置就可以不在程序中指定jsp路径的前缀和后缀 modelAndView.setViewName("items/itemsList");