SpringMVC学习笔记

Tips:本笔记主要信息来自于传智播客2015年胡斌SpringMVC备课笔记。笔记内容为:学习资料 + 个人补充;如有内容或排版方面的错误,欢迎纠正~

SpringMVC介绍

为什么使用SpringMVC?

  很多应用程序的问题在于处理业务数据和显示业务数据的视图的对象之间存在紧密耦合。通常,更新业务对象的命令都是从视图本身发起的,使视图对任何业务对象更改都有高度敏感性。而且,当多个视图依赖于同一个业务对象时是没有灵活性的。
  Spring Web MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职责解耦,基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,Spring Web MVC也是要简化我们日常Web开发的。

MVC设计模型

  MVC 是一种著名的设计模式,特别是在 Web 应用程序领域。模式全都是关于将包含业务数据的模块与显示模块的视图解耦的。这是怎样发生的?视图(例如,JSP 页面)怎样能够与其模型(例如,包含数据的 JavaBean)解耦?记得这句格言么?一个层次的重定向几乎可以解决计算机业中的所有问题。确实,在模型和视图之间引入重定向层可以解决问题。此重定向层是控制器。控制器将接收请求,执行更新模型的操作,然后通知视图关于模型更改的消息。依赖于模型的状态并且依赖于请求的控制器可以决定要显示哪个视图。图 1 演示了这种模式。

mvc设计模式

SpringMVC的强大之处

  1. Spring MVC 实现了即用的 MVC 的核心概念。它为控制器和处理程序提供了大量与此模式相关的功能。并且当向 MVC 添加反转控制(Inversion of Control,IoC)时,它使应用程序高度解耦,提供了通过简单的配置更改即可动态更改组件的灵活性。Spring MVC 为您提供了完全控制应用程序的各个方面的力量。
  2. Spring 的 Web MVC 模块是围绕 DispatcherServlet 而设计的。DispatcherServlet 给处理程序分派请求,执行视图解析,并且处理语言环境和主题解析,此外还为上传文件提供支持。
  3. DispatcherServlet 通过使用处理程序映射来决定哪一个处理程序应当处理传入的请求。处理程序映射只是用于标识使用哪一个处理程序来处理特定 URL 模式的映射。处理程序是只有一种方法 ModelAndView handleRequest(request,response) 的控制器接口的实现。Spring 还有一些可用的高级处理程序实现;其中一个重要的高级处理程序实现是 SimpleFormController,它提供了将命令对象绑定到表单、对其执行验证等功能。
  4. 在本系列教程(传智播客黑马程序员系列)的先前教程中使用了 DispatcherServlet 和简单的处理程序。在下一个部分中,将使用 SimpleFormController 并说明 Spring MVC 提供的各种即用功能。

springMVC优势

  • 清晰的角色划分:前端控制器(DispatcherServlet)、请求到处理器映射(HandlerMapping)、处理器适配器(HandlerAdapter)、视图解析器(ViewResolver)、处理器或页面控制器(Controller)、验证器( Validator)、命令对象(Command 请求参数绑定到的对象就叫命令对象)、表单对象(Form Object 提供给表单展示和提交到的对象就叫表单对象)。
  • 分工明确,而且扩展点相当灵活,可以很容易扩展,虽然几乎不需要;
  • 由于命令对象就是一个POJO,无需继承框架特定API,可以使用命令对象直接作为业务对象;
  • 和Spring 其他框架无缝集成,是其它Web框架所不具备的;
  • 可适配,通过HandlerAdapter可以支持任意的类作为处理器;
  • 可定制性,HandlerMapping、ViewResolver等能够非常简单的定制;
  • 功能强大的数据验证、格式化、绑定机制;
  • 利用Spring提供的Mock对象能够非常简单的进行Web层单元测试;
  • 本地化、主题的解析的支持,使我们更容易进行国际化和主题的切换。
  • 强大的JSP标签库,使JSP编写更容易。
    ………………还有比如RESTful风格的支持、简单的文件上传、约定大于配置的契约式编程支持、基于注解的零配置支持等等。

SpringMVC的运行原理

架构图

架构图

传统的MVC架构

mvc架构1

  首先让我们了解下 MVC(Model-View-Controller)三元组的概念:

  • Model(模型):数据模型,提供要展示的数据,因此包含数据和行为,可以认为是领域模型或 JavaBean 组件(包含数据和行为),不过现在一般都分离开来:Value Object(数据) 和 服务层(行为)。也就是模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。
    领域模型,javaBean组件等价于 域模型层 + 业务逻辑层 + 持久层
  • View(视图):负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西。
  • Controller(控制器):接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。 也就是说控制器做了个调度员的工作,。
    从上图我们还看到,在标准的 MVC 中模型能主动推数据给视图进行更新(观察者设计模式,在模型上注册视图,当模型更新时自动更新视图),但在 Web 开发中模型是无法主动推给视图(无法主动更新用户界面),因为在 Web 开发是请求-响应模型。
    mvc架构2

  核心架构的具体流程步骤如下:

  1. 首先用户发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;
  2. DispatcherServlet——>HandlerMapping,HandlerMapping将会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器)对象,通过这种策略模式,很容易添加新的映射策略;
  3. DispatcherServlet——>HandlerAdapter,HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;
  4. HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个ModelAndView对象(包含模型数据、逻辑视图名);
  5. ModelAndView的逻辑视图名——> ViewResolver, ViewResolver将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;
  6. View——>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支持其他视图技术;
  7. 返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。

DispatcherServlet核心代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
//前端控制器分派方法  
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
int interceptorIndex = -1;
try {
ModelAndView mv;
boolean errorView = false;
try {
//检查是否是请求是否是multipart(如文件上传),如果是将通过MultipartResolver解析
processedRequest = checkMultipart(request);
//步骤2、请求到处理器(页面控制器)的映射,通过HandlerMapping进行映射
mappedHandler = getHandler(processedRequest, false);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
//步骤3、处理器适配,即将我们的处理器包装成相应的适配器(从而支持多种类型的处理器)
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 304 Not Modified缓存支持
//此处省略具体代码
// 执行处理器相关的拦截器的预处理(HandlerInterceptor.preHandle)
//此处省略具体代码
// 步骤4、由适配器执行处理器(调用处理器相应功能处理方法)
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// Do we need view name translation?
if (mv != null && !mv.hasView()) {
mv.setViewName(getDefaultViewName(request));
}
// 执行处理器相关的拦截器的后处理(HandlerInterceptor.postHandle)
//此处省略具体代码
}
catch (ModelAndViewDefiningException ex) {
logger.debug("ModelAndViewDefiningException encountered", ex);
mv = ex.getModelAndView();
}
catch (Exception ex) {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(processedRequest, response, handler, ex);
errorView = (mv != null);
}
//步骤5 步骤6、解析视图并进行视图的渲染
//步骤5 由ViewResolver解析View(viewResolver.resolveViewName(viewName, locale))
//步骤6 视图在渲染时会把Model传入(view.render(mv.getModelInternal(), request, response);)
if (mv != null && !mv.wasCleared()) {
render(mv, processedRequest, 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");
}
}
// 执行处理器相关的拦截器的完成后处理(HandlerInterceptor.afterCompletion)
//此处省略具体代码
catch (Exception ex) {
// Trigger after-completion for thrown exception.
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex) throw ex;
}
catch (Error err) {
ServletException ex = new NestedServletException("Handler processing failed", err);
// Trigger after-completion for thrown exception.
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
throw ex;
}
finally {
// Clean up any resources used by a multipart request.
if (processedRequest != request) {
cleanupMultipart(processedRequest);
}
}
}

SpringMVC的第一个程序

  1. 创建一个javaweb工程
    包:cn.itcast.controller

  2. 导入Spring的jar包
    springmvc-first包列表

  3. 配置DispatcherServlet核心分发器(web.xml)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <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> -->
    </servlet>
    <!-- 加载默认mvc配置文件:springname-servlet.xml ,必须放在WEB-INF下面-->
    <servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>*.do</url-pattern>//或者使用默认拦截/
    </servlet-mapping>

说明:/和/*的区别?
  可以实现现在很流行的REST风格。很多互联网类型的应用很喜欢这种风格的URL。
  弊端:会导致静态文件(jpg,js,css)被拦截后不能正常显示。想实现REST风格,事情就是麻烦一些。后面有解决办法还算简单。
  拦截/*,这是一个错误的方式,请求可以走到Action中,但转到jsp时再次被拦截,不能访问到jsp。

  1. 配置handlerMapping映射器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.1.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-3.1.xsd ">
    <!-- 处理器映射器 -->
    <!-- 根据bean的name进行查找Handler 将action的url配置在bean的name中 -->
    <!-- 这是一个默认的映射处理器,即使不配置,那么也是默认就是这个 -->
    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>
  2. 配置handlerAdapter适配器

    1
    2
    //注意:这个适配器不是必须配置的,这是默认的、他在servlet容器已启动就被加载。
    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
  3. 编写一个Controller类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class TestController implements Controller{
    @Override
    public ModelAndView handleRequest(HttpServletRequest request,
    HttpServletResponse response) throws Exception {
    /**
    * 1.收集参数、验证参数
    * 2.绑定参数到命令对象
    * 3.将命令对象传入业务对象进行处理
    * 4.选择视图
    */
    ModelAndView mv = new ModelAndView();
    //添加模型数据,那么这个数据可以是任意的POJO对象。
    mv.addObject("hello","hello world!!");
    //设置逻辑视图名,视图解析器会根据该名字解析到具体的视图界面
    mv.setViewName("/WEB-INF/jsps/hello.jsp");
    return mv;
    }
    }
  4. 配置自定义控制器

    1
    2
    <!-- 配置自定义controller ,使用beanName:name=”/hello.do”进行进行请求映射匹配-->
    <bean name="/hello.do" class="cn.itcast.test.TestController"></bean>
  5. 定义一个响应页面(hello.jsp)

  6. 配置视图解析器

    1
    2
    3
    4
    5
    6
    7
    <!-- 使用视图解析器解析逻辑视图,这样跟方便,易于扩展。-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <!--逻辑视图前缀-->
    <property name="prefix" value="/WEB-INF/jsps/"></property>
    <!--逻辑视图后缀,匹配模式:前缀+逻辑视图+后缀,形成完整路径名-->
    <property name="suffix" value=".jsp"></property>
    </bean>

程序执行流程

程序执行流程

1、 首先用户发送请求`http://localhost:9080/springmvc-01/hello``——>web容器,web容器根据“/hello”路径映射到DispatcherServlet(url-pattern为/)进行处理;
2、 DispatcherServlet——>BeanNameUrlHandlerMapping进行请求到处理的映射,BeanNameUrlHandlerMapping将“/hello”路径直接映射到名字为“/hello”的Bean进行处理,即HelloWorldController,BeanNameUrlHandlerMapping将其包装为HandlerExecutionChain(只包括HelloWorldController处理器,没有拦截器)
3、DispatcherServlet——> SimpleControllerHandlerAdapter,SimpleControllerHandlerAdapter将HandlerExecutionChain中的处理器(HelloWorldController)适配为SimpleControllerHandlerAdapter;
4、 SimpleControllerHandlerAdapter——> HelloWorldController处理器功能处理方法的调用,SimpleControllerHandlerAdapter将会调用处理器的handleRequest方法进行功能处理,该处理方法返回一个ModelAndView给DispatcherServlet;
5、 hello(ModelAndView的逻辑视图名)——>InternalResourceViewResolver, InternalResourceViewResolver使用JstlView,具体视图页面在/WEB-INF/jsp/hello.jsp;
6、 JstlView(/WEB-INF/jsp/hello.jsp)——>渲染,将在处理器传入的模型数据(message=HelloWorld!)在视图中展示出来;
7、 返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。
到此HelloWorld就完成了,步骤是不是有点多?而且回忆下我们主要进行了如下配置:
1、 前端控制器DispatcherServlet;
2、 HandlerMapping
3、 HandlerAdapter
4、 ViewResolver
5、 处理器/页面控制器
6、 视图

HandlerMapping

BeanNameUrlHandlerMapping

  根据url请求去匹配bean的name属性url,从而获取Controller。HandlerMapping 将会把请求映射为 HandlerExecutionChain 对象(包含一个 Handler 处理器(页面控制器)对象、多个 HandlerInterceptor 拦截器)对象,通过这种策略模式,很容易添加新的映射策略;
配置案例:

1
2
3
4
//默认映射器,即使不配置,默认就使用这个来映射请求。
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>
//映射器把请求映射到controller
<bean id="testController" name="/hello.do" class="cn.itcast.controller.TestController"></bean>

SimpleUrlHandlerMapping

   根据浏览器url匹配简单url的key,key又Controller的id找到Controller

1
2
3
4
5
6
7
8
9
10
11
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/hello1.do">testController</prop>
<prop key="/a.do">testController</prop>
</props>
</property>
</bean>
//那么上面的这个映射配置:表示多个*.do文件可以访问多个Controller或者一个Controller。
//前提是:都必须依赖自定义的控制器bean
<bean id="testController" name="/hello.do" class="cn.itcast.controller.TestController"></bean>

ControllerClassNameHandlerMapping

   根据类名(MyController)类名.do来访问,类名首字母小写

1
2
//这个Mapping一配置:我们就可以使用Contrller的 [类名.do]来访问这个Controller.
<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"></bean>

HandlerMapping架构图

HandlerMapping架构图
总结:多个映射器可以共存。相互不影响。
  测试策略:三个映射器全部存在于配置文件中,使用映射器的不同访问方式,全部可以访问成功。

HandlerAdapter

SimpleControllerHandlerAdapter

SimpleControllerHandlerAdapter: 执行controller,调用controller里面方法,返回modelAndView。
  表示所有实现了org.springframework.web.servlet.mvc.Controller 接口的Bean 可以作为Spring Web MVC 中的处理器。如果需要其他类型的处理器可以通过实现 HadlerAdapter 来解决。
案例分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
① 模拟一个handlerAdapter
//定义一个Adapter接口
public interface HandlerAdapter {
public boolean supports(Object handler);
public void handle(Object handler);
}

② 模拟一个Controller
//以下是三种Controller实现
public interface Controller {
}
public class HttpController implements Controller{
public void doHttpHandler(){
System.out.println("http...");
}
}
public class SimpleController implements Controller{
public void doSimplerHandler(){
System.out.println("simple...");
}
}
public class AnnotationController implements Controller{
public void doAnnotationHandler(){
System.out.println("annotation...");
}
}

③  适配器
//下面编写适配器类
public class SimpleHandlerAdapter implements HandlerAdapter {
public void handle(Object handler) {
((SimpleController)handler).doSimplerHandler();
}
public boolean supports(Object handler) {
return (handler instanceof Controller);
}
}
public class HttpHandlerAdapter implements HandlerAdapter {
public void handle(Object handler) {
((HttpController)handler).doHttpHandler();
}
public boolean supports(Object handler) {
return (handler instanceof Controller);
}
}
public class AnnotationHandlerAdapter implements HandlerAdapter {
public void handle(Object handler) {
((AnnotationController)handler).doAnnotationHandler();
}
public boolean supports(Object handler) {
return (handler instanceof Controller);
}
}

④ DispatcherServlet
public class Dispatcher {
public static List<HandlerAdapter> handlerAdapters = new ArrayList<HandlerAdapter>();
public DispatchServlet(){
handlerAdapters.add(new AnnotationHandlerAdapter());
handlerAdapters.add(new HttpHandlerAdapter());
handlerAdapters.add(new SimpleHandlerAdapter());
}
public void doDispatch(){
//此处模拟SpringMVC从request取handler的对象,仅仅new出,可以出,
//不论实现何种Controller,适配器总能经过适配以后得到想要的结果
// HttpController controller = new HttpController();
// AnnotationController controller = new AnnotationController();
SimpleController controller = new SimpleController();
//得到对应适配器
HandlerAdapter adapter = getHandler(controller);
//通过适配器执行对应的controller对应方法
adapter.handle(controller);
}
public HandlerAdapter getHandler(Controller controller){
for(HandlerAdapter adapter: this.handlerAdapters){
if(adapter.supports(controller)){
return adapter;
}
}
return null;
}
public static void main(String[] args){
new DispatchServlet().doDispatch();
}
}

  通过一个简单的Adapter源码分析,发现原来适配器功能这么强大,设计的如此精妙,果然不同凡响。

HttpRequestHandlerAdapter

  HTTP请求处理器适配器将http请求封装成HttpServletResquest 和HttpServletResponse对象,和servlet接口类似

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
第一步:配置HttpRequestHandlerAdapter适配器
<!-- 配置HttpRequestHandlerAdapter适配器 -->
<bean class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter"></bean>

第二步:编写Controller
public class HttpRequestController implements HttpRequestHandler{
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setAttribute("message", "我是中国人");
request.getRequestDispatcher("/WEB-INF/jsps/mess.jsp").forward(request, response);
}
}

第三步:准备jsp页面
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="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>
<h1>这是我的第二个适配器</h1>
<h1>${message }</h1>
</body>
</html>

第四步:调试运行

总结:适配器可以共存,只是控制器实现不同的接口使用不同的适配器而已。
  注意:SimpleControllerHandlerAdapter是默认的适配器。如果使用后面这个适配器必须手动配置。否则将会访问失败。
  实现了HttpRequestHandler接口:配置文件里面没有配置这个适配器报如下错误:
  No adapter for handler [springMVC_01.HttpRequestController@12d527f]: Does your handler implement a supported interface like Controller?
  通过这个错误,我们发现:实现Controller接口是默认的,既是说明:SimpleControllerHandlerAdapter是默认的适配器
  还必须注意:实现HttpRequestHandler接口的控制器不支持ControllerClassNameHandlerMapping这个处理器映射器。

控制器

控制器架构图

控制器架构图

Controller 简介

1、收集、验证请求参数并绑定到命令对象;
2、将命令对象交给业务对象,由业务对象处理并返回模型数据;
3、返回ModelAndView(Model部分是业务对象返回的模型数据,视图部分为逻辑视图名)。

ServletForwardingController(转发控制器)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//将接收到的请求转发到一个命名的servlet,具体示例如下:
public class ForwardingServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.getWriter().write("Controller forward to Servlet");
} }


<servlet>
<servlet-name>forwarding</servlet-name>
<servlet-class>cn.javass.chapter4.web.servlet.ForwardingServlet</servlet-class>
</servlet>

//当我们请求/forwardToServlet时,会被转发到名字为“forwarding”的servlet处理,该sevletservlet-mapping标签配置是可选的。
<!— 在springmvc.xml配置处理器 -->
<bean name="/forwardToServlet"
class="org.springframework.web.servlet.mvc.ServletForwardingController">
<property name="servletName" value="forwarding"></property>
</bean>

命令控制器(AbstractCommandController)

通过get请求方式传递参数

① 命令控制器编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class CommandController extends AbstractCommandController{	
public CommandController() {
this.setCommandClass(Student.class);
}
@Override
/**
* command:命令对象
* errors:数据绑定出现错误
*/
protected ModelAndView handle(HttpServletRequest request,
HttpServletResponse response, Object command, BindException errors)
throws Exception {
// TODO Auto-generated method stub
Student s = (Student) command;
System.out.println(s);
ModelAndView mv = new ModelAndView();
mv.setViewName("add/success");
return mv;
}
}

② 配置bean

1
2
<!-- 配置命令控制器bean -->
<bean name="/add.do" class="springMVC_01.CommandController"></bean>

③ 编写JSP页面(add/add.jsp)

④ 传递参数
访问路径:localhost:8080/springmvc/add.do?name=zhangsan&password=123

使用post请求进行表单提交

模拟提交学生表单信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
① 编写跳转命令控制器(跳转到添加页面)
/**
* command:命令对象
* errors:数据绑定出现错误
*/
protected ModelAndView handle(HttpServletRequest request,
HttpServletResponse response, Object command, BindException errors)
throws Exception {
ModelAndView mv = new ModelAndView();
mv.setViewName("add/add");

return mv;
}

② 编写编辑页面控制器(填写添加数据)
@Override
/**
* command:命令对象
* errors:数据绑定出现错误
*/
protected ModelAndView handle(HttpServletRequest request,
HttpServletResponse response, Object command, BindException errors)
throws Exception {
Student s = (Student) command;
System.out.println(s);
ModelAndView mv = new ModelAndView();
mv.setViewName("add/success");
return mv; }
@Override
protected void initBinder(HttpServletRequest request,
ServletRequestDataBinder binder) throws Exception {
// 设置将页面字符串类型的数据转换成命令对象中Date属性,注意:这里的Date类型和命令对象的Date都使用Java.util.Date
binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true));
}
//也可以进行条件判断:进行时间类型各种格式的覆盖

③ 添加页面
<form action="${pageContext.request.contextPath }/add.do" method="post">
姓名:<input type="text" name="name"><br/>
密码:<input type="text" name="password"><br/>
日期:<input type="text" name="birthday"><br/>
<input type="submit" value="提交"> <br/>
</form>
④ 成功页面
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>成功提示页面</title>
</head>
<body>
<h1>恭喜登陆!!!</h1>
</body>

参数控制器(ParameterizableViewController)

1
2
3
4
5
<bean	name="/toIndex.do" class="org.springframework.web.servlet.mvc.ParameterizableViewController">
<!-- 配置你所要跳转到视图的名称 -->
<property name="viewName" value="index"></property>//跳转到index页面
</bean>
//注意:使用参数控制器:不用自己定义Controller,可以直接使用toIndex进行访问

使用注解的方式实现SpringMVC

注解快速入门

  • 新建一个web工程并导入jar包

  • 配置springMVC配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    <?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:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.1.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">
    <!-- 添加注解扫描!!! -->
    <context:component-scan base-package="cn.itcast"></context:component-scan>
    <!-- 添加注解映射器 -->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean>
    <!-- 注解适配器 -->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"></bean>
    <!-- 视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/jsps/"></property>
    <property name="suffix" value=".jsp"></property>
    </bean>
    </beans>
    <!-- 注解映射器和注解适配器可以使用<mvc:annotation-driven />代替。
    <mvc:annotation-driven />默认注册了注解映射器和注解适配器等bean。
    -->
  • 配置web.xml文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
    <display-name>springMVC_01</display-name>
    <!-- 配置编码过滤器,解决post请求的乱码问题 -->
    <filter>
    <filter-name>character</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
    <param-name>encoding</param-name>
    <param-value>UTF-8</param-value>
    </init-param>
    </filter>
    <filter-mapping>
    <filter-name>character</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!-- 配置springmvc框架,设置springmvc配置文件路径 -->
    <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>
    </servlet>
    <servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>*.do</url-pattern>
    </servlet-mapping>
    </web-app>
  • 编写Controller

    1
    2
    3
    4
    5
    6
    7
    8
    @Controller
    @RequestMapping("/user")
    public class MyOneController {
    @RequestMapping("/fun")
    public String fun(){
    return "hello";
    }
    }
  • 编写hello的JSP页面

Controller

  • @Controller:用于标识是处理器类.表示把我的控制器对象交给spring来创建。
  • Controller起作用:只需要扫描即可。
    1
    2
    <!-- 添加注解扫描!!! -->
    <context:component-scan base-package="cn.itcast"></context:component-scan>

RequestMapping

   @RequestMapping:请求到处理器功能方法的映射规则;
  URL路径映射:@RequestMapping(value="/user")或@RequestMapping("/user")

  RequestMapping请求方法限定:get、post

  • Get请求乱码
      对于get请求中文参数出现乱码解决方法有两个:
      修改tomcat配置文件添加编码与工程编码一致,如下:
    <Connector URIEncoding="utf-8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
      另外一种方法对参数进行重新编码:

    1
    2
    3
    String userName =new
    String(request.getParamter("userName").getBytes("ISO8859-1"),"utf-8")
    ISO8859-1是tomcat默认编码,需要将tomcat编码后的内容按utf-8编码
  • Post请求
      在web.xml中加入:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
    <param-name>encoding</param-name>
    <param-value>utf-8</param-value>
    </init-param>
    </filter>
    <filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>

  URL模板映射

  • 需求:将修改请求方式改变
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @RequestMapping(value="/useredit/{userId}"):{×××}占位符,请求的URL可以是“/useredit/001”或“/useredit/abc”,通过在方法中使用@PathVariable获取{×××}中的×××变量

    @RequestMapping("/useredit/{userid}")
    public String useredit(@PathVariable String userid,Model model) throws Exception{
    //方法中使用@PathVariable获取useried的值,使用model传回页面
    model.addAttribute("userid", userid);
    return"/user/useredit";
    }
    //实现restFul,所有的url都是一个资源的链接,有利于搜索引擎对网址收录。

    //多个占位符:
    @RequestMapping("/useredit/{groupid}/{userid}")
    public String useredit(@PathVariable String groupid,@PathVariable String userid,Model model) throws Exception{
    //方法中使用@PathVariable获取useried的值,使用model传回页面
    model.addAttribute("groupid", groupid);
    model.addAttribute("userid", userid);
    return "/user/useredit";
    }

  根路径+子路径

1
2
3
4
5
6
7
8
根路径:
@RequestMapping放在类名上边,如下:
@Controller
@RequestMapping("/user")
子路径
@RequestMapping放在方法名上边,如下:
@RequestMapping("/useradd")
public String useradd(…

  请求方法限定

1
2
3
4
5
6
7
8
9
10
11
12
13
•	@RequestMapping(method = RequestMethod.GET)
如果通过Post访问则报错:
HTTP Status 405 - Request method 'POST' not supported
例如:
@RequestMapping(value="/useredit/{userid}",method=RequestMethod.GET)

• 限定POST方法
@RequestMapping(method = RequestMethod.POST)
如果通过Post访问则报错:
HTTP Status 405 - Request method 'GET' not supported

• GET和POST都可以
@RequestMapping(method={RequestMethod.GET,RequestMethod.POST})

  需要注解映射器(默认使用)

1
2
<!-- 添加注解映射器 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean>

  需要使用注解适配器(默认使用)

1
2
<!-- 注解适配器 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"></bean>

  参数传递【请求参数绑定】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
////默认支持的参数类型////
HttpServletRequest、HttpResponse,HttpSession、Model………….

////基本类型参数(Integer、Long、boolean)////
//布尔型:
<tr>
<td>用户状态:</td>
<td>
<input type="radio" name="userstate" value="true"/>
<input type="radio" name="userstate" value="false"/></td>
</tr>

@RequestMapping("/fun")
public String fun(String userName,Integer age){
return "hello";
}

////Pojo对象(Object)////
@RequestMapping("/fun")
public String fun(Model model,User user){
return "hello";
}
//对比Struts2使用属性驱动进行封装参数
//业务需求:
//假如:现在有多个pojo、并且里面具有相同的属性,如果现在还使用基本的对象封装方式,那么将会出现错误。
//那么这个问题怎么解决呢?
//解析:我们使用包装类来包装pojo、经过包装的pojo相当于加了一层包结构。所以后面即使具有相同的属性也无所谓。
Public class UserVo {
private User user;
public User getUser() {
return user;
}
Public void setUser(User user) {
this.user = user;
}
}
//页面定义:
<input type="text" name="user.age" />
<input type="text" name="user.birthday" />
//Controller方法定义如下:
public String useraddsubmit(Model model,UserVo userVo)throws Exception{
System.out.println(userVo.getUser());
}

////数组(Array)////
//页面定义如下:
//页面选中多个checkbox向controller方法传递
<input type="checkbox" name="ids" value="001"/>
<input type="checkbox" name=" ids " value="002"/>
<input type="checkbox" name=" ids " value="002"/>
//修改时可以采用循环模式

@RequestMapping("/fun")
public String fun(Model model,Integer[] ids){
return "hello";
}

////集合(collection)////
//集合页面:
// List中存放对象,并将定义的List放在包装类中,controller使用包装对象接收。
//List中对象:
public class StudentScore {//成绩对象
private String coursename;//课程名称
private Float score;//成绩
public String getCoursename() {
returncoursename;
}
Public void setCoursename(String coursename) {
this.coursename = coursename;
}
public Float getScore() {
returnscore;
}
Public void setScore(Float score) {
this.score = score;
}
}
Public class UserVo {
Private List<StudentScore> scores;//成绩
//get/set方法..
}
//页面:
<tr>
<td>课程成绩:</td>
<td>
课程名:<input type="text"name="scores[0].coursename"/>成绩:<input type="text"name="scores[0].score"/><br/>
课程名:<input type="text"name="scores[1].coursename"/>成绩:<input type="text"name="scores[1].score"/><br/>
课程名:<input type="text"name="scores[2].coursename"/>成绩:<input type="text"name="scores[2].score"/><br/>
</td>
</tr>
//Contrller方法定义如下:
public String useraddsubmit(Model model,UserVo userVo)throws Exception{
System.out.println(userVo.getScores ());
}

////Map////
// 在包装类中定义Map对象,并添加get/set方法,action使用包装对象接收。
// 包装类中定义Map对象如下:
Public class UserVo {
private Map<String, Object>studentinfo = new HashMap<String, Object>();
//get/set方法..
}
页面定义如下:
<tr>
<td>学生信息:</td>
<td>
姓名:<inputtype="text"name="studentinfo['name']"/>
年龄:<inputtype="text"name="studentinfo['age']"/>
.. .. ..
</td>
</tr>
Contrller方法定义如下:
public String useraddsubmit(Model model,UserVo userVo)throws Exception{
System.out.println(userVo.getStudentinfo());
}

RequestParam

-value:参数名字,即入参的请求参数名字,如value=“studentid”表示请求的参数区中的名字为studentid的参数的值将传入;
-required:是否必须,默认是true,表示请求中一定要有相应的参数,否则将报400错误码;
-defaultValue:默认值,表示如果请求中没有同名参数时的默认值
定义如下:

1
2
3
4
5
6
public String userlist(@RequestParam(defaultValue="2",value="group",required=true) String groupid){
}
//形参名称为groupid,但是这里使用value="group"限定参数名为group,所以页面传递参数的名必须为group。这里通
//过required=true限定groupid参数为必需传递,如果不传递则报400错误,由于使用了defaultvalue=”2”默认值即使
//不传group参数它的值为”2”,所以页面不传递group也不会报错,如果去掉defaultvalue=”2”且定义required=true
//则如果页面不传递group则会报错。

Redirect

  Contrller方法返回结果重定向到一个url地址,如下方式:
return "redirect:/user/userlist.do";

注意:

  • redirect:add.do 与 redirect:/user/add.do” 同一个类

    在同一个类里面进行跳转。上面2个都可以实现跳转。但是有区别:第一个是同一个根路径下面跳转。第二个是在项目路径下进行跳转。
    
  • 不同的类进行跳转
      不同的类进行跳转只能使用:redirect:/user/add.do进行跳转。即是从项目路径下来查询。
      redirect方式相当于“response.sendRedirect()”,转发后浏览器的地址栏变为转发后的地址,因为转发即执行了一个新的request和response。
      由于新发起一个request原来的参数在转发时就不能传递到下一个url,如果要传参数可以/user/userlist.do后边加参数,如下:
    /user/userlist.action?groupid=2&…..

Forward

  controller方法执行后继续执行另一个controller方法。
return "forward:/user/userlist.action";
  forward方式相当于request.getRequestDispatcher().forward(request,response),转发后浏览器地址栏还是原来的地址。转发并没有执行新的request和response,而是和转发前的请求共用一个request和response。所以转发前请求的参数在转发后仍然可以读取到。
如下例子:

1
2
3
4
5
6
7
8
9
10
   @RequestMapping("/c")
public String c(String groupid,UserVo userVo)throws Exception{
System.out.println("...c...."+groupid+"...user..."+userVo.getUser());
return "forward:/to/d.action";
}
@RequestMapping("/d")
public String d(String groupid,UserVo userVo)throws Exception{
System.out.println("...d...."+groupid+"...user..."+userVo.getUser());
return "success";
}

RequestBody/ResponseBody处理Json数据

  • 作用:
    @RequestBody注解用于读取http请求的内容(字符串),通过springmvc提供的HttpMessageConverter接口将读到的内容转换为json、xml等格式的数据并绑定到controller方法的参数上。
    本例子应用:
    @RequestBody注解实现接收http请求的json数据,将json数据转换为java对象

请求Json响应Json

  1. 添加处理json数据配置

    1
    2
    3
    4
    5
    6
    7
    8
    <!-- 注解适配器 :添加json转换器-->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
    <list>
    <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"></bean>
    </list>
    </property>
    </bean>
  2. 编写代码【请求json、返回json】

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Controller
    @RequestMapping("json")
    public class MyJsonController {
    @RequestMapping("requestJson.do")
    public @ResponseBody User requestJson(Model model,@RequestBody User u){
    System.out.println("用户信息:"+u);
    return u;
    }
    }
  3. 编写页面ajax

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    <%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="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>
    <script type="text/javascript" src="${pageContext.request.contextPath }/js/jquery-1.9.1.min.js"></script>
    <script type="text/javascript">
    //请求json,返回json
    function request_json(){
    var user = JSON.stringify({userName:"张思德",age:21});
    $.ajax({
    type:'POST',//请求方式
    url: '${pageContext.request.contextPath}/json/requestJson.do',//请求地址
    contentType:'application/json;charset=utf-8',//发送数据类型
    data:user,//传递数据
    success : function(data){ //请求成功后的回调函数
    alert(data.userName+":"+data.age);
    }
    })
    }
    </script>
    </head>
    <body>
    <input type="button" onclick="request_json()" value="请求json,返回json">
    </body>
    </html>

请求pojo响应json

  1. 添加处理json数据配置【同上】
    前台请求数据构造:key=value&key=value.

  2. 编写Controller代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Controller
    @RequestMapping("json")
    public class MyJsonController {
    @RequestMapping("responseJson.do")
    public @ResponseBody User responseJson(User user){
    System.out.println(user);
    return user;
    }
    }
  3. 编写页面代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="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>
    <script type="text/javascript" src="${pageContext.request.contextPath }/js/jquery-1.9.1.min.js"></script>
    <script type="text/javascript">
    //请求pojo,返回json
    function response_json(){
    $.ajax({
    type:'POST',
    url: '${pageContext.request.contextPath}/json/responseJson.do',
    data:'userName=张三&age=12',
    success : function(data){
    alert(data.userName+":"+data.age);
    }
    })
    }
    </script>
    </head>
    <body>
    <input type="button" onclick="response_json()" value="请求pojo,返回json">
    </body>
    </html>

<mvc:annotation-driven />配置:

注解映射器和注解适配器可以使用<mvc:annotation-driven />代替。
<mvc:annotation-driven />默认注册了注解映射器和注解适配器等bean。
如下:

1
2
3
4
5
6
7
8
9
10
11
以下配置可用<mvc:annotation-driven />代替:
<!--注解映射器 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<!--注解适配器 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"></bean>
</list>
</property>
</bean>

多视图

  视图是展示给用户的内容,控制器先得到对应的数据模型,对于非逻辑视图,则直接将数据模型渲染便结束;对于逻辑视图,则要先通过视图解析器对其进一步解析,以定位真实视图。

对多视图的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<!-- 相当于配置requestmapping和requestAdapter以及json支持 -->
<mvc:annotation-driven/>
<!-- 配置视图解析器 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsps/"></property>
<property name="suffix" value=".jsp"></property>
</bean>

<!-- 配置多视图解析 -->
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<!-- 配置支持媒体类型 -->
<property name="contentNegotiationManager">
<bean class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="mediaTypes">
<map>
<entry key="json" value="application/json"></entry>
<entry key="xml" value="application/xml"></entry>
</map>
</property>
</bean>
</property>

<!-- 指定默认视图 -->
<property name="defaultViews">
<!-- 支持多个视图 -->
<list>
<!-- 对josn格式视图支持 -->
<bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView"></bean>
<!-- xml格式视图支持 -->
<bean class="org.springframework.web.servlet.view.xml.MarshallingView">
<constructor-arg>
<bean class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="classesToBeBound">
<list>
<value>cn.itcast.domain.User</value>
</list>
</property>
</bean>
</constructor-arg>
</bean>
</list>
</property>
</bean>
1
2
3
4
5
6
7
8
9
//Controller代码
@RequestMapping("multiView")
public User multiView(){
User user1 = new User();
user1.setId(1);
...
user1.setAddress("武当山");
return user1;
}
  • 多视图解析需要结合restful风格使用,访问 :multiView.xml||json 验证

注意
json视图需要导入jackson相关包
xml视图需要导入spring-oxm相关包

springMVC的其他视图:freemaker

  freemaker是springmvc本身的视图解析器,可以实现页面静态化

准备工作

  1. 导包:freemaker.jar
  2. 准备相关文件(index.ftl)
  3. 修改springmvc配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <bean id="freeMarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
    <property name="templateLoaderPath" value="/WEB-INF/jsp/"></property>
    <property name="defaultEncoding" value="UTF-8"></property>
    <property name="freemarkerSettings">
    <props>
    <prop key="template_update_delay">0</prop>
    <prop key="default_encoding">UTF-8</prop>
    <prop key="number_format">0.##########</prop>
    <prop key="datetime_format">yyyy-MM-dd HH:mm:ss</prop>
    <prop key="classic_compatible">true</prop>
    <prop key="template_exception_handler">ignore</prop>
    </props>
    </property>
    </bean>
    <bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
    <property name="suffix" value=".ftl"></property>
    <property name="contentType" value="text/html;charset=utf-8"></property>
    </bean>
  4. 编写controller

ftl文件内容格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html>
<html>
<head>
<mata charset="utf-8">
<title>Title</title>
</head>
<body>
////遍历List////
//定义数据
Map root = new HashMap();
List<String> persons = new ArrayList<String>();
persons.add("范冰冰");
persons.add("李冰冰");
persons.add("何灵");
root.put("persons", persons);
//
<#list persons as person>
${person}
</#list>
//页面
<#list persons as p>
${p.id}/${p.name}
</#list>

//// 在模板中赋值 ////
1:<#assign x=0 />
${x}
2:<#assign x="${world}" />
${x}
3:<#assign x>世界太好了</#assign>
${x}
4:<#assign x>
<#list ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期天"] as n>
${n}
</#list>
</#assign>
${x}
</body>
</html>

页面缓存(oscache为例)

需求:使用页面缓存来提高用户体验度

相关概念

  • 缓存原理
    缓存原理
    优点:提升性能
    缺点:不访问数据库,不是实时数据

  • 分布式缓存
    分布式缓存
      分布式缓存能控制颗粒的大小,分布式缓存使用的是redis,memcached等等。这相当于是数据库,那么我们是不是在任何一层都可以进行操作数据库。所以可以进行颗粒大小的控制。

使用oscache管理缓存

准备工作

  1. 在src根目录下创建oscache.properties

    1
    2
    3
    4
    //oscache.properties常用参数
    cache.memory=false//不能缓存内存
    cache.persistence.class=com.opensymphony.oscache.plugins.diskpersistence.DiskPersistenceListener//持久化类
    cache.path=F:\\cache//持久化到F盘
  2. 导入oscacha-2.4.1.jar

  3. 配置缓存过滤器
    配置缓存过滤器

oscache的使用

1
2
3
4
5
6
7
8
9
<!-- 在jsp页面中导入oscache标签库  -->
<%@ taglib uri="http://www.opensymphony.com/oscache" prefix="oscache" %>

<!-- 在body中使用标签控制缓存的部分 -->
<oscache:cache>
<!-- 缓存默认保存在application中,可在标签中加入"scope='session'"属性,将缓存保存在session中 -->
<!-- 若想让缓存不随路径变化而变,可通过加入key属性实现 -->
...
</oacache:cache>

注意
  缓存是通过“url=页面缓存”的形式保存的,故当请求地址发生变化后,缓存就不起作用了

文件上传

  需求分析:使用响应json格式数据的形式上传图片并回显

准备工作

  1. 导入jar文件(fileupload、io、jersey-client、jersey-core)
  2. 配置视图解析器
    1
    2
    3
    4
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!-- 文件上传的最大值,单位为byte -->
    <property name="maxUploadSize" value="10240000"/>
    </bean>

代码编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@Controller
@RequestMapping("/upload")
public class UploadController {
@RequestMapping("/uploadPic")
public void uploadPic(HttpServletRequest request,HttpServletResponse response,String fileName,PrintWriter out){
//将普通请求转换为多部件请求方式
MultipartHttpServletRequest mh = (MultipartHttpServletRequest) request;
//根据文件名进行获取文件对象
CommonsMultipartFile cm = (CommonsMultipartFile) mh.getFile(fileName);
//把多部件文件对象转换为字节
byte[] fbytes = cm.getBytes();
//定义文件名
String newFileName="";
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
newFileName= sdf.format(new Date());
//再加上三位随机数
Random ran = new Random();
for(int i=0;i<3;i++){
newFileName = newFileName+ran.nextInt(10);
}

//获取上传文件的扩展名
String suffix="";
String originalFilename = cm.getOriginalFilename();
suffix=originalFilename.substring(originalFilename.lastIndexOf("."));

//创建jersy客户端
Client client = Client.create();
//指定关联文件图片服务器地址
WebResource resource = client.resource(SSMConstants.PIC_HOST+"/upload/"+newFileName+suffix);
//跨服务器上传图片
resource.put(String.class, fbytes);

String fullPath=SSMConstants.PIC_HOST+"/upload/"+newFileName+suffix;
String relativePath="/upload/"+newFileName+suffix;

//{"":"","":""}
String result="{\"fullPath\":\""+fullPath+"\",\"relativePath\":\""+relativePath+"\"}";
//JSONObject jsonObject = JSON.parseObject(result);
out.print(result);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//页面回显ajax
<p><label></label>
<img id='imgSize1ImgSrc' src='${picPath }${item.pic }' height="100" width="100" />
<input type='file' id='imgSize1File' name='imgSize1File' class="file" onchange='submitImgSize1Upload()' />
<span class="pos" id="imgSize1FileSpan">请上传图片的大小不超过3MB</span>
<input type='hidden' id='imgSize1' name='pic' value='' reg="^.+$" tip="亲!您忘记上传图片了。" />
</p>

function submitUpload(){
var opt = {
//重新指定form的action的值
url:"${path}/upload/uploadPic.do",
type:"post",
dateType:"text",
data:{
fileName:"imgsFile"
},
success:function(responseText){
var obj = $.parseJSON(responseText);
$("#imgsImgSrc").attr("src",obj.fullPath);
$("#imgs").val(obj.fileName);

},
error:function(){
alert("系统错误");
}
};
$("#form111").ajaxSubmit(opt);
}

拦截器

定义

 &emap;SpringWebMVC的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理

局部拦截器

  针对某个handlerMapping进行配置只对当前HandlerMapping有效。

1
2
3
4
5
6
7
8
9
10
11
12
//在配置文件中的配置
<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"/>
<bean id="handlerInterceptor2" class="springmvc.intercapter.HandlerInterceptor2"/>

拦截器的代码实践:实现HandlerInterceptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Public class HandlerInterceptor1 implements HandlerInterceptor{
/**
* controller执行前调用此方法
* 返回true表示继续执行,返回false中止执行
* 这里可以加入登录校验、权限拦截等
*/
@Override
Public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
// TODO Auto-generated method stub
Return false;
}
/**
* controller执行后但未返回视图前调用此方法
* 这里可在返回用户前对模型数据进行加工处理,比如这里加入公用信息以便页面显示
*/
@Override
Public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// TODO Auto-generated method stub}
/**
* controller执行后且视图返回后调用此方法
* 这里可得到执行controller时的异常信息
* 这里可记录操作日志,资源清理等
*/
@Override
Public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// TODO Auto-generated method stub
}
}

全局拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
//相关配置
<!--拦截器 -->
<mvc:interceptors>
<!--多个拦截器,顺序执行 -->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="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>

拦截器测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//测试1
拦截器1放行,拦截器2放行:
HandlerInterceptor1.........preHandle
HandlerInterceptor2.........preHandle
HandlerInterceptor2.........postHandle
HandlerInterceptor1.........postHandle
HandlerInterceptor2.........afterCompletion
HandlerInterceptor1.........afterCompletion

//测试2
拦截器1放行,拦截器2阻止:
HandlerInterceptor1.........preHandle
HandlerInterceptor2.........preHandle
HandlerInterceptor1.........afterCompletion

//测试3:
拦截器1阻止,拦截器2阻止:
HandlerInterceptor1.........preHandle
  • 拦截器的应用(用户身份认证)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    Public class LoginInterceptor implements HandlerInterceptor{
    @Override
    Public boolean preHandle(HttpServletRequest request,
    HttpServletResponse response, 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;
    }
    }

SSM整合(SpringMVC,Spring,Mybatis)

  为了更好的学习Springmvc和mybatis整合开发的方法,需要将Springmvc和mybatis进行整合,整合目标为:控制层采用SpringMvc,持久层使用mybatis实现

需求

  实现商品查询列表,从mysql数据库查询商品信息

jar包

  包括:Spring(包括SpringMvc),mybatis,mybatis-Spring整合包,数据库驱动,第三方连接池
ssm包

Dao

  目标:spring管理SqlSessionFactory、mapper

详细参考mybatis教程与spring整合章节。

db.properties

1
2
3
4
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=XXXX
jdbc.password=XXXX

log4j.properties

1
2
3
4
5
6
# Global logging configuration,建议开发环境中要用debug
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

sqlMapConfig.xml

1
2
3
4
5
6
7
8
9
10
11
//在classpath下创建mybatis/sqlMapConfig.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!—使用自动扫描器时,mapper.xml文件如果和mapper.java接口在一个目录则此处不用定义mappers -->
<mappers>
<package name="cn.itcast.ssm.mapper" />
</mappers>
</configuration>

applicationContext-dao.xml

配置数据源、事务管理,配置SqlSessionFactory、mapper扫描器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<!-- 加载配置文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 数据库连接池 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="maxActive" value="30"/>
<property name="maxIdle" value="5"/>
</bean>
<!-- 让spring管理sqlsessionfactory 使用mybatis和spring整合包中的 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 数据库连接池 -->
<property name="dataSource" ref="dataSource" />
<!-- 加载mybatis的全局配置文件 -->
<property name="configLocation" value="classpath:mybatis/SqlMapConfig.xml" />
</bean>
<!-- mapper扫描器 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cn.itcast.springmvc.mapper"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
</beans>

ItemsMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.itcast.ssm.mapper.ItemsMapper">
<!-- sql片段 -->
<!-- 商品查询条件 -->
<sql id="query_items_where">
<if test="items!=null">
<if test="items.name!=null and items.name!=''">
and items.name like '%${items.name}%'
</if>
</if>
</sql>

<!-- 查询商品信息 -->
<select id="findItemsList" parameterType="queryVo" resultType="items">
select * from items
<where>
<include refid="query_items_where"/>
</where>
</select>


</mapper>

ItemsMapper.java

1
2
3
4
public interface ItemsMapper {
//商品列表
public List<Items> findItemsList(QueryVo queryVo) throws Exception;
}

Service

  目标:Service由spring管理;spring对service进行事务控制

applicationContext-service.xml

  配置service接口

ApplicationContext-transaction.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
////配置事务管理器
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">

<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 数据源 -->
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- 通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 传播行为 -->
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="insert*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="get*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>

<!-- 切面 -->
<aop:config>
<aop:advisor advice-ref="txAdvice"
pointcut="execution(* cn.itcast.springmvc.service.impl.*.*(..))"/>
</aop:config>

</beans>

OrderService

1
2
3
4
5
6
7
8
9
10
public interface OrderService {
@Autowired
private ItemsMapper itemsMapper;

@Override
public List<Items> findItemsList(QueryVo queryVo) throws Exception {
//查询商品信息
return itemsMapper.findItemsList(queryVo);
}
}

Action

springmvc.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">

<!-- 扫描controller注解,多个包中间使用半角逗号分隔 -->
<context:component-scan base-package="cn.itcast.ssm.controller"/>

<!--注解映射器 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<!--注解适配器 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
<!-- ViewResolver -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
</beans>

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
////加载spring容器,配置springmvc前置控制器。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>springmvc</display-name>
<!-- 加载spring容器 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/spring/applicationContext.xml,/WEB-INF/classes/spring/applicationContext-*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 解决post乱码 -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- springmvc的前端控制器 -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- contextConfigLocation不是必须的, 如果不配置contextConfigLocation, springmvc的配置文件默认在:WEB-INF/servlet的name+"-servlet.xml" -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/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>

<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
</web-app>

OrderController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Controller
public class OrderController {
@Autowired
private OrderService orderService;
@RequestMapping("/queryItem.action")
public ModelAndView queryItem() throws Exception {
// 商品列表
List<Items> itemsList = orderService.findItemsList(null);
// 创建modelAndView准备填充数据、设置视图
ModelAndView modelAndView = new ModelAndView();
// 填充数据
modelAndView.addObject("itemsList", itemsList);
// 视图
modelAndView.setViewName("order/itemsList");
return modelAndView;
}
}
感谢你的阅读!