0%

SpringMVC

SpringMVC概述

Spring 简介

SpringMVC:是基于spring的一个框架, 实际上就是spring的一个模块, 专门是做web开发的。理解是servlet的一个升级

web开发底层是servlet

框架是在servlet基础上面加入一些功能,让你做web开发方便。

  • SpringMVC就是一个Spring。 Spring是容器,ioc能够管理对象,使用<bean>, @Component, @Repository, @Service, @Controller
    SpringMVC能够创建对象, 放入到容器中(SpringMVC容器), springmvc容器中放的是控制器对象,

  • 我们要做的是 使用@Contorller创建控制器对象, 把对象放入到springmvc容器中, 把创建的对象作为控制器使用这个控制器对象能接收用户的请求, 显示处理结果,就当做是一个servlet使用。

  • 使用@Controller注解创建的是一个普通类的对象, 不是Servlet。 springmvc赋予了控制器对象一些额外的功能。

  • web开发底层是servlet, springmvc中有一个对象是Servlet : DispatherServlet(中央调度器)
    DispatherServlet: 负责接收用户的所有请求, 用户把请求给了DispatherServlet, 之后DispatherServlet把请求转发给我们的Controller对象, 最后是Controller对象处理请求

index.jsp -> DispatherServlet(Servlet) -> 转发,分配 -> Controller对象(@Controller注解创建的对象)

SpringMVC 优点

  1. 基于 MVC 架构

    基于 MVC 架构,功能分工明确。解耦合

  2. 容易理解,上手快;使用简单

    就可以开发一个注解的 SpringMVC 项目,SpringMVC 也是轻量级的,jar 很小。不依赖特定的接口和类

  3. 作为Spring框架的一部分,能够使用Spring的 IoC 和 Aop。 方便整合Strtus, MyBatis, Hiberate, JPA等其他框架

  4. SpringMVC 强化注解的使用,在控制器,Service, Dao都可以使用注解。方便灵活

    使用@Controller 创建处理器对象,@Service 创建业务对象,@Autowired 或者@Resource 在控制器类中注入 Service, Service 类中注入 Dao。

第一个注解的 SpringMVC 程序

所谓 SpringMVC 的注解式开发是指,在代码中通过对类与方法的注解,便可完成处理器 在 springmvc 容器的注册。注解式开发是重点。


项目:primary-annotation

需求:用户在页面发起一个请求,请求交给springmvc的控制器对象,并显示请求的处理结果(在结果页面显示一个欢迎语句)


实现步骤:

  1. 新建web maven工程

  2. 加入依赖

    spring-webmvc依赖,间接把spring依赖都加入到项目中

    jsp, servlet依赖

  3. web.xml 中注册 springmvc 框架的核心对象 DispatcherServlet

    • DispatcherServlet 又叫做中央调度器,是一个Servlet,它的父类是继承 HttpServlet
    • DispatcherServlet 也叫做前端控制器(front controller)
    • DispatcherServlet 负责接受用户提交的请求,调用其他的控制器对象,并把请求的处理结果显示给用户
  4. 创建一个发起请求的页面 index.jsp

  5. 创建控制器类

    1. 在类的上面加入@Controller 注解,创建对象,并放入springmvc容器中
    2. 类中方法上面加入@RequestMapping 注解
  6. 创建一个结果的 jsp, 显示请求的处理结果

  7. 创建一个springmvc 配置文件(和spring配置文件一样)

    1. 声明组件扫描器,指定@Controller注解所在的包名
    2. 声明视图解析器。帮助处理视图的

新建 maven web 项目

在pom.xml 加入依赖

在创建好 web 项目后,加入 Servlet 依赖,SpringMVC 依赖

依赖:

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>

插件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<build>
<plugins>
<!-- 编码和编译和JDK版本 -->
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>

注册中央调度器

(1) 全限定性类名

该中央调度器为一个 Servlet,名称为 DispatcherServlet。中央调度器的全限定性类名在导入的 Jar 文件 spring-webmvc-5.2.5.RELEASE.jar 的第一个包 org.springframework.web.servlet 下可找到。

(2) <load-on-startup/>

在<servlet/>中添加<load-on-startup/>的作用是,标记是否在Web服务器(这里是Tomcat) 启动时会创建这个 Servlet 实例,即是否在 Web 服务器启动时调用执行该 Servlet 的 init()方 法,而不是在真正访问时才创建。

它的值必须是一个整数。

  • 当值大于等于 0 时,表示容器在启动时就加载并初始化这个 servlet,数值越小,该 Servlet 的优先级就越高,其被创建的也就越早
  • 当值小于 0 或者没有指定时,则表示该 Servlet 在真正被使用时才会去创建。
  • 当值相同时,容器会自己选择创建顺序。

(3)<url-pattern/>

对于<url-pattern/>,可以写为 / ,建议写为*.do 的形式。

(4) 配置文件位置与名称

注册完毕后,可直接在服务器上发布运行。此时,访问浏览器页面,控制台均会抛出 FileNotFoundException 异常。即默认要从项目根下的 WEB-INF 目录下找名称为 Servlet 名称 -servlet.xml 的配置文件。这里的“Servlet 名称”指的是注册中央调度器标签 中指定的 Servlet 的 name 值。本例配置文件名为 springmvc-servlet.xml。

而一般情况下,配置文件是放在类路径下,即 resources 目录下。所以,在注册中央调度器时,还需要为中央调度器设置查找 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
31
32
33
34
35
36
37
38
39
40
41
<!--     
声明,注册springmvc的核心对象DispatcherServlet
需要在tomcat服务器启动后,创建DispatcherServlet对象的示例
为什么要创建DispatcherServlet对象的示例?
因为DispatcherServlet在它创建的时候,会同时创建springmvc容器,
读取springmvc的配置文件,把这个配置文件中的对象都创建好,当用户
发起请求时,就可以直接使用对象了
Servlet的初始化会执行init()方法。DispatcherServlet在init()中 {
//创建容器,读取配置文件
WebApplicationContext ctx = new ClassPathXmlApplicationContext("springmvc.xml");
//把容器对象放入ServletContext中
getServletContext.setAttribute(key, ctx);
}
-->
<servlet>
<servlet-name>myweb</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 自定义springmvc读取的配置文件的位置-->
<init-param>
<!-- springmvc配置文件的位置的属性-->
<param-name>contextConfigLocation</param-name>
<!-- 指定自定义文件的位置-->
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- 在Tomcat服务器启动后,创建Servlet对象
load-on-startup: 表示tomcat启动后创建对象的顺序。它的值时一个整数,
数值越小,tomcat创建的时间越早。大于等于0的整数
-->
<load-on-startup>1</load-on-startup>
</servlet>
<!--
使用框架时,url-pattern可以使用两种值
1. 使用扩展名的方式,语法 *.xxx xxx是自定义的扩展名。常用的方式,*.do, *.action, *.mvc等
http://localhost:8080/myweb/some.do
2. 使用斜杠 "/"
-->
<servlet-mapping>
<servlet-name>myweb</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>

创建 SpringMVC 配置文件

在工程的类路径即 src 目录下创建 SpringMVC 的配置文件 springmvc.xml。该文件名可以任意命名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 声明组件扫描器-->
<context:component-scan base-package="com.bjpowernode.controller"/>
<!-- 声明springmvc框架中的视图解析器,帮助开发人员设置视图文件的路径-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前缀:视图文件的路径-->
<property name="prefix" value="/WEB-INF/view/"/>
<!-- 后缀:表示视图文件的扩展名-->
<property name="suffix" value=".jsp"/>
</bean>
</beans>

定义页面

在 webapp 目录下新建一个 jsp 页面 index.jsp。

1
2
3
4
5
6
7
8
9
10
11
12
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<p>第一个springMVC项目</p>
<p> <a href = "some.do">发起some.do的请求</a> </p>
<p> <a href = "other.do">发起other.do的请求</a></p>
</body>
</html>

在webapp/WEB-INF/view/下新建show.jsp, other.jsp

1
2
3
4
5
6
7
8
9
10
11
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>show</title>
</head>
<body>
<h3>/WEB-INF/view/show.jsp 从request作用域获取对象</h3>
<h3>msg : ${msg} </h3> <br/>
<h3>dun: ${fun} </h3>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>/WEB-INF/view/other.jsp 从request作用域获取对象</h3>
<h3>msg : ${msg} </h3> <br/>
<h3>dun: ${fun} </h3>
</body>
</html>

创建处理器

在类上与方法上添加相应注解即可。

@Controller:表示当前类为处理器

@RequestMapping:表示当前方法为处理器方法。该方法要对 value 属性所指定的 URI 进行处理与响应。被注解的方法的方法名可以随意。

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
/**
* @Controller: 创建处理器对象,对象放在springmvc容器中
* 位置:在类的上面
* 和 Spring 中讲的@Service, @Component类似
* 能处理请求的都是控制器(处理器),MyController能处理请求,叫做后端控制器(back controller)
*/
@Controller
public class MyController {
/*
处理用户提交的请求,springmvc是使用方法来处理的
方法自定义,可以有多种返回值,多种参数,方法名称自定义
*/

/**
* 准备使用doSome处理some.do的请求
* @RequestMapping: 请求映射,作用是把一个请求地址和方法绑定在一起
* 一个请求指定一个方法处理。
* 属性:1. value是一个String, 表示请求的url地址的(some.do)
* value的值必须是唯一的,不能重复,在使用时,推荐以“/”开头
* 位置:1. 在方法的上面,最常用
* 2. 在类的上面
* 说明:使用RequestMapping修饰的方法叫做处理器方法或控制器方法。
* 使用RequestMapping修饰的方法是可以处理请求的,类似Servlet中的doGet(), doPost();
*
* 返回值:ModelAndView 表示本次请求的处理结果
* Model: 数据,请求处理完成后,要显示给用户的数据
* View: 视图,比如jsp等等
*/
@RequestMapping(value={"/some.do", "/first.do"})
public ModelAndView doSome() {
//处理some.do的请求, 相当于service处理完成了
ModelAndView mv = new ModelAndView();
//添加数据,框架在请求的最后把数据放入到request作用域
//request.setAttribute("msg", "欢迎使用springmvc做web开发");
mv.addObject("msg", "欢迎使用springmvc做web开发");
mv.addObject("fun", "执行的是doSome()方法");

//指定视图,指定视图的完整路径
//框架对视图执行forword操作,request.getRequestDispatcher("show.jsp").forward(..);
// mv.setViewName("/show.jsp");
// mv.setViewName("/WEB-INF/view/show.jsp");
// 当配置了视图解析器,可以使用逻辑名称(文件名),指定视图
// 框架会使用视图解析器的前缀 + 逻辑名称 + 后缀组成完成路径,这里就是字符串连接操作
// /WEB-INF/view/ + show + .jsp
mv.setViewName("show");
return mv;
}
@RequestMapping(value={"/other.do", "/second.do"})
public ModelAndView doOther() {
ModelAndView mv = new ModelAndView();
mv.addObject("msg", "===欢迎使用springmvc做web开发===");
mv.addObject("fun", "执行的是doOther()方法");
mv.setViewName("other");
return mv;
}
}

若有多个请求路径均可匹配该处理器方法的执行,则@RequestMapping 的 value 属性中 可以写上一个数组。

ModelAndView 类中的 addObject()方法用于向其 Model 中添加数据。Model 的底层为一 个 HashMap。

Model 中的数据存储在 request 作用域中,SpringMVC 默认采用转发的方式跳转到视图, 本次请求结束,模型中的数据被销毁。

声明组件扫描器

1
<context:component-scan base-package="com.bjpowernode.controller"/>

修改视图解析器的注册

SpringMVC 框架为了避免对于请求资源路径与扩展名上的冗余,在视图解析器 InternalResouceViewResolver 中引入了请求的前辍与后辍。而 ModelAndView 中只需给出要跳转页面的文件名即可,对于具体的文件路径与文件扩展名,视图解析器会自动完成拼接。

1
2
3
4
5
6
7
<!--    声明springmvc框架中的视图解析器,帮助开发人员设置视图文件的路径-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前缀:视图文件的路径-->
<property name="prefix" value="/WEB-INF/view/"/>
<!-- 后缀:表示视图文件的扩展名-->
<property name="suffix" value=".jsp"/>
</bean>

使用 SpringMVC 框架 web 请求处理顺序

SpringMVC 的 MVC 组件

SpringMVC 执行流程

流程图

执行流程简单分析

(1)浏览器提交请求到中央调度器

(2)中央调度器直接将请求转给处理器映射器。

  • 处理器映射器:springmvc中的一种对象,框架把实现了HandlerMapping接口的类叫做映射器(多个)

  • 处理器映射器作用:根据请求,从springmvc容器对象中获取处理器对象(MyController controller = ctx.getBean(“some.do”) )

    框架把找到的处理器对象放入到一个叫做处理器执行链(HandlerExecutionChain)的类中保存

    • HandlerExecutionChain: 类中保存着 : 1.处理器对象(MyController) ; 2.项目中所有的拦截器(List<HandlerInterceptor> interceptorList)

(3)处理器映射器会根据请求,找到处理该请求的处理器,并将其封装为处理器执行链后返回给中央调度器。

(4)中央调度器根据处理器执行链中的处理器,找到能够执行该处理器的处理器适配器。

  • DispatcherServlet把2中的HandlerExecutionChain中的处理器对象交给了处理器适配器对象(多个)
    • 处理器适配器:springmvc框架中的对象,需要实现HanderAdapter接口
    • 处理器适配器作用:执行处理器方法(调用MyController.doSome() 得到返回值ModelAndView )

(5)处理器适配器调用执行处理器。

(6)处理器将处理结果及要跳转的视图封装到一个对象 ModelAndView 中,并将其返回给 处理器适配器

(7)处理器适配器直接将结果返回给中央调度器。

(8)中央调度器调用视图解析器,将 ModelAndView 中的视图名称封装为视图对象。

  • DispatcherServlet把获取的ModelAndView交给了视图解析器对象
    • 视图解析器:springmvc中的对象,需要实现ViewResolver接口(可以有多个)
    • 视图解析器作用:组成视图完整路径,使用前缀,后缀,并创建view对象
      • view是一个接口,表示视图的,在框架中jsp, html不是string表示,而是使用view和它的实现类表示视图
      • InternalResourceView: 视图类,表示jsp文件,视图解析器会创建InternalResourceView对象
        • 这个对象里面,有一个属性url=/WEB-INF/view/show.jsp

(9)视图解析器将封装了的视图对象返回给中央调度器

(10)中央调度器调用视图对象,让其自己进行渲染,即进行数据填充,形成响应对象。

  • DispatcherServlet把View对象获取到,调用View类自己的方法,把Model数据放入到request作用域中

    执行视图的forward,请求结束

(11)中央调度器响应浏览器。

SpringMVC 注解式开发

@RequestMapping 定义请求规则

指定模块名称

通过@RequestMapping 注解可以定义处理器对于请求的映射规则。该注解可以注解在方法上,也可以注解在类上,但意义是不同的。value 属性值常以“/”开始。

@RequestMapping 的 value 属性用于定义所匹配请求的 URI。但对于注解在方法上与类上,其 value 属性所指定的 URI,意义是不同的。

一个@Controller 所注解的类中,可以定义多个处理器方法。当然,不同的处理器方法 所匹配的 URI 是不同的。这些不同的 URI 被指定在注解于方法之上的@RequestMapping 的 value 属性中。但若这些请求具有相同的 URI 部分,则这些相同的 URI,可以被抽取到注解在类之上的@RequestMapping 的 value 属性中。此时的这个 URI 表示模块的名称。URI 的请求是相对于 Web 的根目录。

换个角度说,要访问处理器的指定方法,必须要在方法指定 URI 之前加上处理器类前定义的模块名称

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
/*
* @RequestMapping:
* value: 所有请求地址的公共部分,叫做模块名称
* 位置:放在类的上面
*/
@RequestMapping("/user")
@Controller
public class MyController {
@RequestMapping(value={"/some.do", "/first.do"})
public ModelAndView doSome() {
ModelAndView mv = new ModelAndView();
mv.addObject("msg", "欢迎使用springmvc做web开发");
mv.addObject("fun", "执行的是doSome()方法");
mv.setViewName("show");
return mv;
}
@RequestMapping(value={"/other.do", "/second.do"})
public ModelAndView doOther() {
ModelAndView mv = new ModelAndView();
mv.addObject("msg", "===欢迎使用springmvc做web开发===");
mv.addObject("fun", "执行的是doOther()方法");
mv.setViewName("other");
System.out.println("HHHHHHHHHH");
return mv;
}
}

对请求提交方式的定义

对于@RequestMapping,其有一个属性 method,用于对被注解方法所处理请求的提交方式进行限制,即只有满足该 method 属性指定的提交方式的请求,才会执行该被注解方法。

Method 属性的取值为 RequestMethod 枚举常量。常用的为 RequestMethod.GET 与 RequestMethod.POST,分别表示提交方式的匹配规则为 GET 与 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
@RequestMapping("/user")
@Controller
public class MyController {
/**
* @RequestMapping: 请求映射
* 属性:method,表示请求的方式。它的值RequestMethod类枚举值
* 例如get请求方式,RequestMethod.GET
* post方式:RequestMethod.POST
* 你不用get方式,错误是
* HTTP Status 405 - Request method 'GET'/'POST' not supported
*/
//指定some.do使用get请求方式
@RequestMapping(value="/some.do", method= RequestMethod.GET)
public ModelAndView doSome() {
ModelAndView mv = new ModelAndView();
mv.addObject("msg", "欢迎使用springmvc做web开发");
mv.addObject("fun", "执行的是doSome()方法");
mv.setViewName("show");
return mv;
}
//指定other.do是post请求方式
@RequestMapping(value="/other.do", method=RequestMethod.POST)
public ModelAndView doOther() {
ModelAndView mv = new ModelAndView();
mv.addObject("msg", "===欢迎使用springmvc做web开发===");
mv.addObject("fun", "执行的是doOther()方法");
mv.setViewName("other");
return mv;
}
//不指定请求方式,没有限制
@RequestMapping(value="/first.do")
public ModelAndView doFirst() {
System.out.println("HHHHHHHHHH");
ModelAndView mv = new ModelAndView();
mv.addObject("msg", "===welcome springmvc web ===");
mv.addObject("fun", "执行的是doFirst方法");
mv.setViewName("other");
return mv;
}
}

客户端浏览器常用的请求方式,及其 提交方式有以下几种:

也就是说,只要指定了处理器方法匹配的请求提交方式为 POST,则相当于指定了请求 发送的方式:要么使用表单请求,要么使用 AJAX 请求。其它请求方式被禁用。

当然,若不指定 method 属性,则无论是 GET 还是 POST 提交方式,均可匹配。即对于 请求的提交方式无要求。

处理器方法的参数

处理器方法可以包含以下四类参数,这些参数会在系统调用时由系统自动赋值,即程序员可在方法内直接使用。

  • HttpServletRequest
  • HttpServletResponse
  • HttpSession
  • 请求中所携带的请求参数

逐个参数接收

Step1: 修改index页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<p>提交参数给Controller</p>
<form action="receiveproperty.do" method="post">
姓名:<input type="text" name="name"><br/>
年龄:<input type="text" name="age"><br/>
<input type="submit" value="提交参数">
</form>
</body>
</html>

Step2:修改处理器类 MyController

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
@Controller
public class MyController {
/**
* 逐个接受请求的参数:
* 要求:处理器(控制器)方法的形参名和请求中的参数名必须一致
* 同名的请求参数赋值给同名的形参
* 框架接受请求的参数
* 1. 使用request对象接受请求参数
* String strName = request.getParameter("name");
* String strAge = request.getParameter("age");
* 2. springmvc框架通过 DispatcherServlet 调用MyController 的doSome()方法
* 调用方法时,按名称对应,把接受的参数赋给形参
* doSome(strName, Integer.valueOf(strAge));
* 框架会提供类型转换的功能,能把String转换为int, long, float...
*/
@RequestMapping(value="receiveproperty.do")
public ModelAndView doSome(String name, Integer age) {
System.out.println("doSome === name = " + name + " age = " + age);
ModelAndView mv = new ModelAndView();
mv.addObject("myName", name);
mv.addObject("myage", age);
mv.setViewName("show");
return mv;
}

}

Step3:添加 show 页面

在/WEB-INF/jsp 下添加 show.jsp 页面。

1
2
3
4
5
6
7
8
9
10
11
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>show</title>
</head>
<body>
<h3>/WEB-INF/view/show.jsp 从request作用域获取对象</h3>
<h3>myname : ${myName} </h3> <br/>
<h3>myage: ${myage} </h3>
</body>
</html>

请求参数中文乱码问题

对于前面所接收的请求参数,若含有中文,则会出现中文乱码问题。Spring 对于请求参数中的中文乱码问题,给出了专门的字符集过滤器:spring-web-5.2.5.RELEASE.jar 的 org.springframework.web.filter 包下的 CharacterEncodingFilter 类。

web.xml 中注册字符集过滤器,即可解决 Spring 的请求参数的中文乱码问题。不过, 最好将该过滤器注册在其它过滤器之前。因为过滤器的执行是按照其注册顺序进行的。

直接在项目 receiveParameters-property 上进行修改。

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
<!--    注册声明过滤器,解决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>
<!-- 强制请求对象(HttpServletRequest)使用encoding编码的值-->
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<!-- 强制应答对象(HttpServletResponse)使用encoding编码的值-->
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<!--
/*: 表示强制所有的请求先通过过滤器处理
-->
<url-pattern>/*</url-pattern>
</filter-mapping>

校正请求参数名@RequestParam

所谓校正请求参数名,是指若请求 URL 所携带的参数名称与处理方法中指定的参数名不相同时,则需在处理方法参数前,添加一个注解@RequestParam(“请求参数名”),指定请求 URL 所携带参数的名称。该注解是对处理器方法参数进行修饰的。value 属性指定请求参数的名称。

Step1:修改 index 页面

1
2
3
4
5
6
<p>请求参数名和处理器方法名不一样</p>
<form action="receiveparam.do" method="post">
姓名:<input type="text" name="rname"><br/>
年龄:<input type="text" name="rage"><br/>
<input type="submit" value="提交参数">
</form>

Step2:修改处理器类 MyController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 请求中参数名和处理器形参名不一样
* @RequestParam: 解决请求中参数名和形参名不一致问题
* 属性:1. value: 请求中参数的名称
* 2. required 是一个boolean, 默认是true
* true: 表示请求中必须包含此参数
* 位置:在处理器方法的形参定义的前面
*/
@RequestMapping(value="receiveparam.do")
public ModelAndView receiveParam(@RequestParam(value = "rname", required = false) String name,
@RequestParam(value = "rage", required = false) Integer age) {
System.out.println("doSome === name = " + name + " age = " + age);
ModelAndView mv = new ModelAndView();
mv.addObject("myName", name);
mv.addObject("myage", age);
mv.setViewName("show");
return mv;
}

对象参数接收

将处理器方法的参数定义为一个对象,只要保证请求参数名与这个对象的属性同名即可。 项目:receiveParameters-object。在 receiveParameters-property 基础上修改。

Step1:定义类 Student

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
public class Student {
private String name;
private Integer age;

public Student() {
System.out.println(" ====== Student无参数构造方法 ===========");
}

public String getName() {
return name;
}

public void setName(String name) {
System.out.println("setName: " + name);
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
System.out.println("setAge: " + age);
this.age = age;
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

Step2:修改处理器类 MyController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 处理器方法形参是java对象,这个对象的属性名和请求中参数名是一样的
* 框架会创建形参的java对象,给属性赋值。请求中的参数是name,框架会调用setName()
* @return
*/
@RequestMapping(value="receiveObject.do")
public ModelAndView receiveParam(Student myStudent) {
System.out.println("doSome === name = " + myStudent.getName() + " age = " + myStudent.getAge());
ModelAndView mv = new ModelAndView();
mv.addObject("myName", myStudent.getName());
mv.addObject("myage", myStudent.getAge());
mv.addObject("mystudent", myStudent);
mv.setViewName("show");
return mv;
}

Step3:修改 show 页面

1
2
3
4
5
6
7
8
9
10
11
12
13
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>show</title>
</head>
<body>
<h3>/WEB-INF/view/show.jsp 从request作用域获取对象</h3>
<h3>myname : ${myName} </h3> <br/>
<h3>myage: ${myage} </h3>
<h3>student数据: ${mystudent}</h3>
</body>
</html>

处理器方法的返回值

使用@Controller 注解的处理器的处理器方法,其返回值常用的有四种类型:

  • 第一种:ModelAndView
  • 第二种:String
  • 第三种:无返回值 void
  • 第四种:返回自定义类型对象

返回 ModelAndView

若处理器方法处理完后,需要跳转到其它资源,且又要在跳转的资源间传递数据,此时处理器方法返回 ModelAndView 比较好。当然,若要返回 ModelAndView,则处理器方法中需要定义 ModelAndView 对象。

在使用时,若该处理器方法只是进行跳转而不传递数据,或只是传递数据而并不向任何资源跳转(如对页面的 Ajax 异步响应),此时若返回 ModelAndView,则将总是有一部分多余:要么 Model 多余,要么 View 多余。即此时返回 ModelAndView 将不合适。

返回 String

处理器方法返回的字符串可以指定逻辑视图名,通过视图解析器解析可以将其转换为物理视图地址

返回内部资源逻辑视图名

若要跳转的资源为内部资源,则视图解析器可以使用 InternalResourceViewResolver 内部资源视图解析器。此时处理器方法返回的字符串就是要跳转页面的文件名去掉文件扩展名后的部分。这个字符串与视图解析器中的 prefix、suffix 相结合,即可形成要访问的 URI。

注册视图解析器

1
2
3
4
5
6
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前缀:视图文件的路径-->
<property name="prefix" value="/WEB-INF/view/"/>
<!-- 后缀:表示视图文件的扩展名-->
<property name="suffix" value=".jsp"/>
</bean>

直接修改处理器类 MyController

1
2
3
4
5
6
7
8
9
10
11
12
13
    /**
* 处理器方法返回string,表示逻辑视图名称,需要配置视图解析器
*/
@RequestMapping(value="returnString-view.do")
public String doReturnView(HttpServletRequest request,String name, Integer age) {
System.out.println("doSome === name = " + name + " age = " + age);
// 可以自己手工添加数据到request作用域
request.setAttribute("myName", name);
request.setAttribute("myage", age);
//show: 表示逻辑视图名称,项目中配置了视图解析器
//框架对视图执行了forward转发操作
return "show";
}

当然,也可以直接返回资源的物理视图名。不过,此时就不需要再在视图解析器中再配 置前辍与后辍了

返回 void

对于处理器方法返回 void 的应用场景,AJAX 响应.

若处理器对请求处理后,无需跳转到其它任何资源,此时可以让处理器方法返回 void

例如,对于 AJAX 的异步请求的响应

Step1:maven 加入 jackson 依赖

由于本项目中服务端向浏览器传回的是 JSON 数据,需要使用一个工具类将字符串包装 为 JSON 格式,所以需要导入 JSON 的依赖。

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>

Step2:引入 jQuery 库

由于本项目要使用 jQuery 的 ajax()方法提交 AJAX 请求,所以项目中需要引入 jQuery 的库。在 WebRoot 下新建一个 Folder(文件夹),命名为 js,并将 jquery-1.11.1.js 文件放入其 中。

1
<script type="text/javascript" src="js/jquery-3.6.0.js"></script> 

Step3:定义 index 页面

index 页面由两部分内容构成:一个是<button>,用于提交 AJAX 请求;一个是<scirpt/>, 用于处理 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
29
30
31
32
33
34
35
36
37
38
39
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<script type="text/javascript" src="js/jquery-3.6.0.js"></script>
<script type="text/javascript">
$(function(){
$("#btn").click(function() {
// alert("button click ! ")
$.ajax({
url:"returnVoid-ajax.do",
data:{
name:"zhangsan",
age:20
},
type:"post",
dataType:"json",
success:function(resp) {
//resp从服务器端返回的是json格式的字符串{"name":"zhangsan","age":20}
//jquery会把字符串转换为json对象,赋值给resp形参
alert(resp.name + " " + resp.age);
}
})
})
})
</script>
</head>
<body>
<p>处理器方法返回String表示视图名称</p>
<form action="returnString-view.do" method="post">
姓名:<input type="text" name="name"><br/>
年龄:<input type="text" name="age"><br/>
<input type="submit" value="提交参数">
</form>
<br/>
<br/>
<button id="btn">发起ajax请求 </button>
</body>
</html>

Step4: 定义对象 Student

1
2
3
4
5
public class Student {
private String name;
private Integer age;
....
}

Step5:修改处理器类 MyController

处理器对于 AJAX 请求中所提交的参数,可以使用逐个接收的方式,也可以以对象的方式整体接收。只要保证 AJAX 请求参数与接收的对象类型属性同名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RequestMapping(value="returnVoid-ajax.do")
public void doReturnVoidAjax(HttpServletResponse response,String name, Integer age)
throws IOException {
System.out.println("doSome === name = " + name + " age = " + age);
//处理ajax, 使用json做数据的格式
//service调用完成了,使用Student 表示处理结果
Student student = new Student();
student.setName(name);
student.setAge(age);
String json = "";
if(student != null) {
ObjectMapper om = new ObjectMapper();
json = om.writeValueAsString(student);
System.out.println("student 转换的json === " + json);
}
//输出数据,响应ajax请求
response.setContentType("application/json; charset=utf-8");
PrintWriter pw = response.getWriter();
pw.println(json);
pw.flush();
pw.close();
}

返回对象 Object

处理器方法也可以返回 Object 对象。这个 Object 可以是 Integer,String,自定义对象, Map,List 等。但返回的对象不是作为逻辑视图出现的,而是作为直接在页面显示的数据出现的。

返回对象,需要使用@ResponseBody 注解,将转换后的 JSON 数据放入到响应体中。

实现步骤:

  1. 加入json的工具库的依赖,springmvc默认使用的是 jackson

  2. 在springmvc配置文件中加入 <mvc:annotation-driven> 注解驱动

    相当于:

    1
    json  = om.writeValueAsString(student);
  3. 在处理器方法上面加入@ResponseBody注解

    相当于:

    1
    2
    3
    response.setContentType("application/json; charset=utf-8");
    PrintWriter pw = response.getWriter();
    pw.println(json);

内部原理

springmvc 处理器方法返回Object, 可以转为json输出到浏览器,响应ajax的内部原理

1. <mvc:annotation-driven> 注册驱动。

注册驱动实现的功能是:完成java对象到 json, xml, text, 二进制等数据格式的转换

<mvc:annotation-driven> 在加入到 springmvc配置文件 后,会自动创建7个HttpMessageConverter接口的7个实现类对象,

包括 MappingJackson2HttpMessageConverter() ( 使用jackson工具库中的ObjectMappper实现java对象转为字符串 )

HttpMessageConvert接口:消息转换器

功能: 定义了java 转为 json, xml等数据格式的方法。这个接口有很多实现类,这些类完成java对象到json, xml等数据的转换

下面的两个方法是在控制器类把结果输出给浏览器时使用的

1
2
boolean canWrite(Class<?> var1, @Nullable MediaType var2);
void write(T var1, @Nullable MediaType var2, HttpOutputMessage var3) throws IOException, HttpMessageNotWritableException;
  • canWrite() 作用 : 检查处理器方法的返回值,能不能转为var2 表示的数据格式, 如果能,返回true

  • MediaType : 表示数据格式的,例如 json, xml 等等

  • write() 作用: 把处理器方法的返回值对象,调用jackson中的ObjectMapper 转为 json 字符串

    类似于

    1
    json  = om.writeValueAsString(student); 
2. @ResponseBody注解

放在处理器方法上面,通过HttpServletResponse 输出数据,响应ajax请求

类似于

1
2
3
response.setContentType("application/json; charset=utf-8");
PrintWriter pw = response.getWriter();
pw.println(json);

具体实现

springmvc.xml文件中声明注解驱动

1
<mvc:annotation-driven />

修改处理器MyController

1
2
3
4
5
6
7
8
9
@RequestMapping(value = "/returnStudentJson.do")
@ResponseBody
public Student doStudentJsonObject(String name, Integer age) {
//调用service,获取请求结果数据 , Student对象表示结果数据
Student student = new Student();
student.setName("李四同学");
student.setAge(20);
return student; // 会被框架转为json
}

返回对象框架的处理流程

  1. 框架会把返回Student 类型, 调用框架的中ArrayList<HttpMessageConverter> 中每个类的canWrite() 方法去检查哪个HttpMessageConverter接口的实现类能处理Student类型的数据-MappingJackson2HttpMessageConvert

  2. 框架会调用实现类的write(), MappingJackson2HttpMessageConvert的write()方法,把李四同学的student对象转为json, 调用Jackson的ObjectMapper实现转为json

    contentType: application/json;charset=utf-8

  3. 框架会调用@ResponseBody把2的结果数据输出到浏览器,ajax请求处理完成

返回 List 集合

Step1:修改处理器 MyController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RequestMapping(value = "/returnStudentJsonArray.do")
@ResponseBody
public List<Student> doStudentJsonObjectArray(String name, Integer age) {
List<Student> list = new ArrayList<>();
Student student = new Student();
student.setName("李四");
student.setAge(80);
list.add(student);
student = new Student();
student.setName("张三");
student.setAge(50);
list.add(student);
return list;
}
Step2:修改 index 页面
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
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<script type="text/javascript" src="js/jquery-3.6.0.js"></script>
<script type="text/javascript">
$(function(){
$("#btn").click(function() {
// alert("button click ! ")
$.ajax({
// url:"returnVoid-ajax.do",
// url:"returnStudentJson.do",
url:"returnStudentJsonArray.do",
data:{
name:"zhangsan",
age:20
},
type:"post",
dataType:"json",
success:function(resp) {
//resp从服务器端返回的是json格式的字符串{"name":"zhangsan","age":20}
//jquery会把字符串转换为json对象,赋值给resp形参
// alert(resp.name + " " + resp.age);
//[{"name":"李四","age":80},{"name":"张三","age":50}]
$.each(resp, function(i, n) {
alert(n.name + " " + n.age)
})
}
})
})
})
</script>
</head>
<body>
<p>处理器方法返回String表示视图名称</p>
<form action="returnString-view.do" method="post">
姓名:<input type="text" name="name"><br/>
年龄:<input type="text" name="age"><br/>
<input type="submit" value="提交参数">
</form>
<br/>
<br/>
<button id="btn">发起ajax请求 </button>
</body>
</html>

返回字符串对象

若要返回非中文字符串,将前面返回数值型数据的返回值直接修改为字符串即可。但若 返 回 的 字 符 串 中 带 有 中 文 字 符 , 则 接 收 方 页 面 将 会 出 现 乱 码 。 此 时 需 要 使 用 @RequestMapping 的 produces 属性指定字符集。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 处理器方法返回String, String表示数据的,不是视图
* 区分返回值String是数据还是视图,看有没有@ResponseBody注解
* 如果有@ResponseBody注解,返回String就是数据,反之就是视图
*
* 默认使用ISO-8859-1作为contentType, 导致中文有乱码
* 解决方案:给RequestMapping增加一个属性 produces, 使用这个属性来指定新的contentType
*
*/
@RequestMapping(value = "/returnStringData.do", produces = "text/plain;charset=utf-8")
@ResponseBody
public String doStringData(String name, Integer age) {
return "Hello 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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<script type="text/javascript" src="js/jquery-3.6.0.js"></script>
<script type="text/javascript">
$(function(){
$("#btn").click(function() {
// alert("button click ! ")
$.ajax({
// url:"returnVoid-ajax.do",
// url:"returnStudentJson.do",
// url:"returnStudentJsonArray.do",
url:"returnStringData.do",
data:{
name:"zhangsan",
age:20
},
type:"post",
//dataType:"json",
success:function(resp) {
//resp从服务器端返回的是json格式的字符串{"name":"zhangsan","age":20}
//jquery会把字符串转换为json对象,赋值给resp形参
// alert(resp.name + " " + resp.age);
//[{"name":"李四","age":80},{"name":"张三","age":50}]
// $.each(resp, function(i, n) {
// alert(n.name + " " + n.age)
// })
alert("返回的是文本数据:" + resp);
}
})
})
})
</script>
</head>
<body>
<p>处理器方法返回String表示视图名称</p>
<form action="returnString-view.do" method="post">
姓名:<input type="text" name="name"><br/>
年龄:<input type="text" name="age"><br/>
<input type="submit" value="提交参数">
</form>
<br/>
<br/>
<button id="btn">发起ajax请求 </button>
</body>
</html>

解读<url-pattern/>

配置详情

*.do

在没有特殊要求的情况下,SpringMVC 的中央调度器 DispatcherServlet 的常使用后辍匹配方式,如写为*.do 或者 *.action, *.mvc 等。

/

可以写为/,因为 DispatcherServlet 会将向静态资源的获取请求,例如.css、.js、.jpg、.png 等资源的获取请求,当作是一个普通的 Controller 请求。中央调度器会调用处理器映射器为其查找相应的处理器。当然也是找不到的,所以在这种情况下,所有的静态资源获取请求也均会报 404 错误。

项目:url-pattern。在项目 primary-annotation 基础上进行修改。 需求:在 index.jsp 页面中存在一个访问图片的链接。该项目用于演示将写为*.do 可以访问到该图片,而写为/,则无法访问。

在项目中添加图片

在项目的 WebRoot 下添加一个目录 images,并在其中添加一张图片资源。

修改 index 页面
1
<img src="images/image1.png" alt="我是一个静态资源,不能显示">
修改<url-pattern/>的值

保持的值为 *.do,扩展名方式,图片会正常显示.

将的值修改为 / ,则图片将无法显示。

静态资源访问

<url-pattern/>的值并不是说写为/后,静态资源就无法访问了。经过一些配置后,该问题也是可以解决的。

使用<mvc:default-servlet-handler/>

声 明 了 后 , springmvc 框 架 会 在 容 器 中 创 建 DefaultServletHttpRequestHandler 处理器对象。它会像一个检查员,对进入 DispatcherServlet 的 URL 进行筛查,如果发现是静态资源的请求,就将该请求转由 Web 应用服务器默认的 Servlet 处理。一般的服务器都有默认的 Servlet。

在 Tomcat 中,有一个专门用于处理静态资源访问的 Servlet 名叫 DefaultServlet。其<servlet-name/>为 default。可以处理各种静态资源访问请求。该 Servlet 注册在 Tomcat 服务器的 web.xml 中。在 Tomcat 安装目录/conf/web.xml。

只需要在 springmvc.xml 中添加标签即可。

1
2
3
4
5
6
7
<!--    第一种处理静态资源的方式
在springmvc配置文件中加入<mvc:default-servlet-handler>
原理:加入这个标签后,框架会自动创建DefaultServletRequestHandler
(类似于我们自己创建的MyController)
DefaultServletRequestHandler 可以把接受的请求转发给 tomcat的default这个servlet
-->
<mvc:default-servlet-handler />

<mvc:default-servlet-handler/>表示使用 DefaultServletHttpRequestHandler 处理器对象。 而该处理器调用了 Tomcat 的 DefaultServlet 来处理静态资源的访问请求。

使用<mvc:resources/>

在 Spring3.0 版本后,Spring 定义了专门用于处理静态资源访问请求的处理器 ResourceHttpRequestHandler。并且添加了<mvc:resources/>标签,专门用于解决静态资源无法访问问题。需要在 springmvc 配置文件中添加如下形式的配置:

1
<mvc:resources mapping="/statics/**" location="/statics/" />

location 表示静态资源所在目录。当然,目录不要使用/WEB-INF/及其子目录

mapping 表 示 对 该 资 源 的 请 求 ( 以 /images/ 开 始 的 请 求 , 如 /image/beauty.jpg , /images/car.png 等)。注意,后面是两个星号**。

声明注解驱动

解决动态资源和静态资源冲突的问题,在 springmvc 配置文件加入

1
<mvc:annotation-driven />

SSM 整合开发

SSM 编程,即 SpringMVC + Spring + MyBatis 整合,是当前最为流行的 JavaEE 开发技术架 构。其实 SSM 整合的实质,仅仅就是将 MyBatis整合入 Spring。因为 SpringMVC原本就是 Spring 的一部分,不用专门整合。

SSM 整合的实现方式可分为两种:基于 XML 配置方式,基于注解方式

ch07-ssm: SSM整合开发。
SSM: SpringMVC + Spring + MyBatis

SpringMVC: 视图层,界面层,负责接受请求,显示处理结果
Spring: 业务层,管理service, dao, 工具类对象
MyBatis: 持久层, 访问数据库

用户发起请求 — SpringMVC接受 — Spring中的Service对象 —- MyBatis处理数据

SSM整合也叫做SSI(IBatis也就是MyBatis前身),整合中有容器

  1. SpringMVC容器,管理Controller控制器对象的
  2. Spring容器,管理Service, Dao, 工具类对象的
    我们要做的是把使用的对象交给合适的容器创建,管理。把Controller还有web开发的相关对象
    交给springMVC容器,这些web用的对象写在springmvc配置文件中

service, dao对象定义在spring配置文件中,让spring管理这些对象

springmvc容器是spring容器的子容器,类似于java中的继承,
在子容器中的Controller可以访问父容器的Service对象,就可以实现Controller使用Service对象


实现步骤:

  1. 使用springdb的mysql库,表使用student(id auto_increment, name, age)

  2. 新建maven web项目

  3. 加入依赖
    springmvc, spring, mybatis三个框架的依赖, jackson依赖, mysql驱动, druid连接池
    jsp, servlet依赖

  4. 写web.xml

    1. 注册DispatcherServlet, 目的:
    • 创建 springmvc 容器对象,才能创建Controller类对象

    • 创建的是Servlet, 才能接受用户的请求

    1. 注册spring的监听器:ContextLoaderListener, 目的:创建spring的容器对象,才能创建service, dao等对象

    2. 注册字符集过滤器,解决post请求乱码的问题

  5. 创建包, Controller, service, dao实体类包名创建好

  6. springmvc, spring, mybatis的配置文件

    1. springmvc配置文件
    2. spring配置文件
    3. mybatis主配置文件
    4. 数据库的属性配置文件
  7. 写代码,dao接口和mapper文件,service和实体类, controller, 实体类

  8. 写jsp页面

搭建 SSM 开发环境

maven pom.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
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
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.bjpowernode</groupId>
<artifactId>ch07-ssm</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>


<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- servlet依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- jsp依赖 -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2.1-b03</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.9</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>

</dependencies>

<build>
<resources>
<resource>
<directory>src/main/java</directory><!--所在的目录-->
<includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>

</build>
</project>

配置 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
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- 注册中央调度器-->
<servlet>
<servlet-name>myweb</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:conf/dispatcherServlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>myweb</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>

<!-- 注册spring监听器-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:conf/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- 注册字符集过滤器-->
<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>
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>


</web-app>

SSM 整合注解开发

建表

新建Web工程

maven依赖

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
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.bjpowernode</groupId>
<artifactId>ch07-ssm</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>


<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- servlet依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- jsp依赖 -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2.1-b03</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.9</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>

</dependencies>

<build>
<resources>
<resource>
<directory>src/main/java</directory><!--所在的目录-->
<includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>

</build>
</project>

定义包,组织程序的结构。

jsp文件

编写配置文件

Spring 配置文件 applicationContext.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"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- spring的配置文件: 声明service, dao, 工具类等对象-->
<!-- 声明数据源,连接数据库 -->
<context:property-placeholder location="classpath:conf/jdbc.properties" />

<!-- 声明数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!-- SqlSessionFactoryBean创建SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:conf/mybatis.xml" />
</bean>
<!-- 声明mybatis扫描器,创造dao对象-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="com.bjpowernode.dao" />
</bean>
<!-- 声明service的注解@Service所在包的包名位置-->
<context:component-scan base-package="com.bjpowernode.service" />
<!-- 事务配置:注解配置, aspectj的配置 -->

</beans>

Springmvc 配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- springmvc配置文件,声明controller和其他web相关的对象 -->
<context:component-scan base-package="com.bjpowernode.controller" />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!--
1. 响应ajax请求,返回json
2. 解决静态资源访问问题
-->
<mvc:annotation-driven />

</beans>

Mybatis 配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!-- 设置别名-->
<typeAliases>
<!-- name:实体类所在的包名(不是实体类的包名也可以)-->
<package name="com.bjpowernode.domain"/>
</typeAliases>
<!-- sql mapper(sql映射文件)的位置-->
<mappers>
<!--
name: 是包名,这个包中所有的mapper.xml一次都能加载
使用package的要求:
1. mapper文件名称和dao接口名必须完全一样,包括大小写
2. mapper文件和dao接口必须在同一目录
-->
<package name="com.bjpowernode.dao"/>
</mappers>
</configuration>

定义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
54
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- 注册中央调度器-->
<servlet>
<servlet-name>myweb</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:conf/dispatcherServlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>myweb</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>

<!-- 注册spring监听器-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:conf/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- 注册字符集过滤器-->
<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>
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>


</web-app>

实体类 Student

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
public class Student {
private Integer id;
private String name;
private Integer age;

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}
}

Dao 接口和 sql 映射文件

1
2
3
4
5
6
import java.util.List;

public interface StudentDao {
int insertStudent(Student student);
List<Student> selectStudents();
}
1
2
3
4
5
6
7
8
9
10
11
12
<?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="com.bjpowernode.dao.StudentDao">
<select id="selectStudents" resultType="Student">
select id, name, age from student order by id desc
</select>
<insert id="insertStudent">
insert into student(name, age) values(#{name}, #{age})
</insert>
</mapper>

Service 接口和实现类

1
2
3
4
5
6
import java.util.List;

public interface StudentService {
int addStudent(Student student);
List<Student> findStudents();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service
public class StudentServiceImpl implements StudentService {
//引用类型自动注入, @Autowired, @Resource
@Resource
private StudentDao studentDao;
@Override
public int addStudent(Student student) {
int nums = studentDao.insertStudent(student);
return nums;
}

@Override
public List<Student> findStudents() {
return studentDao.selectStudents();
}
}

处理器定义

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
@Controller
@RequestMapping("/student")
public class StudentController {
@Resource
private StudentService service;
//注册学生
@RequestMapping("/addStudent.do")
public ModelAndView addStudent(Student student) {
ModelAndView mv = new ModelAndView();
String tips = "注册失败";
//调用service处理student
int nums = service.addStudent(student);
if(nums > 0) {
//注册成功
tips = "学生[" + student.getName() + "]注册成功";
}
//指定结果页面
mv.addObject("tips", tips);
//指定结果页面
mv.setViewName("result");
return mv;
}
//处理查询,响应ajax
@RequestMapping("/queryStudent.do")
@ResponseBody
public List<Student> queryStudent() {
//参数检查,简单的数据处理
List<Student> students = service.findStudents();
return students;
}
}

定义视图-首页文件— index.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<html>
<head>
<title>功能入口</title>
<base href="<%=basePath%>" />
</head>
<body>
<div align="center">
<p>SSM整合的例子</p>

<table>
<tr>
<td><a href="addStudent.jsp">注册学生</a> </td>
</tr>
<tr>
<td><a href="listStudent.jsp">浏览学生</a></td>
</tr>
</table>
</div>
</body>
</html>

注册学生页面 — addStudent.jsp

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
<html>
<head>
<title>注册学生</title>
<base href="<%=basePath%>" />
</head>

<body>
<div align="center">
<form action="student/addStudent.do" method="post">
<table>
<tr>
<td>姓名:</td>
<td> <input type="text" name="name"> </td>
</tr>
<tr>
<td>年龄:</td>
<td> <input type="text" name="age"> </td>
</tr>
<tr>
<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
<td><input type="submit" value="注册" ></td>
</tr>
</table>
</form>
</div>
</body>
</html>

浏览学生页面 — listStudent.jsp

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
<html>
<head>
<title>查询学生ajax</title>
<base href="<%=basePath%>" />
<script type="text/javascript" src="js/jquery-3.6.0.js"></script>
<script type="text/javascript">
$(function() {
//在当前页面dom对象加载后,执行loadStudentData()
loadStudentData()
$("#btnLoader").click(function() {
loadStudentData();
})
})
function loadStudentData() {
$.ajax({
url:"student/queryStudent.do",
type:"get",
dataType:"json",
success:function(data) {
$("#info").html("")

$.each(data, function(i, n) {
$("#info").append("<tr>")
.append("<td>" + n.id + "</td>")
.append("<td>" + n.name + "</td>")
.append("<td>" + n.age + "</td>")
.append("</tr>")
})
}
})
}
</script>
</head>
<body>
<div align="center">
<table>
<thead>
<tr>
<td>学号</td>
<td>姓名</td>
<td>年龄</td>
</tr>
</thead>
<tbody id="info">

</tbody>
</table>
<input type="button" id="btnLoader" value="查询数据">
</div>
</body>
</html>

注册结果页面

1
2
3
4
5
6
7
8
9
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
result.jsp 结果页面,注册结果:${tips}
</body>
</html>

SpringMVC 核心技术

请求重定向和转发

当处理器对请求处理完毕后,向其它资源进行跳转时,有两种跳转方式:请求转发与重定向。而根据所要跳转的资源类型,又可分为两类:跳转到页面与跳转到其它处理器。

注意,对于请求转发的页面,可以是WEB-INF中页面;而重定向的页面,是不能为WEB-INF中页的。因为重定向相当于用户再次发出一次请求,而用户是不能直接访问 WEB-INF 中资源的。

SpringMVC 框架把原来 Servlet 中的请求转发和重定向操作进行了封装。现在可以使用简单的方式实现转发和重定向。

forward:表示转发,实现 request.getRequestDispatcher(“xx.jsp”).forward()

redirect:表示重定向,实现 response.sendRedirect(“xxx.jsp”)

请求转发

处理器方法返回 ModelAndView 时,需在 setViewName()指定的视图前添加 forward:,且此时的视图不再与视图解析器一同工作,这样可以在配置了解析器时指定不同位置的视图。 视图页面必须写出相对于项目根的路径。forward 操作不需要视图解析器。

处理器方法返回 String,在视图路径前面加入 forward: 视图完整路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 处理器方法放回ModelAndView,实现转发forward
* 语法:setViewName("forward:视图文件完整路径");
* forward特点:不和视图解析器一同使用,就当项目中没有视图解析器。
* @return
*/
@RequestMapping(value="doForward.do")
public ModelAndView doSome() {
ModelAndView mv = new ModelAndView();
mv.addObject("msg", "欢迎使用springmvc做web开发");
mv.addObject("fun", "执行的是doSome()方法");
mv.setViewName("forward:/WEB-INF/view/show.jsp");
return mv;
}

请求重定向

在处理器方法返回的视图字符串的前面添加 redirect:,则可实现重定向跳转。

处理器方法定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 处理器方法返回ModelAndView,实现重定向redirect
*
* 框架对重定向的操作:
* 1. 框架把Model中的简单类型数据,转为string使用,作为hello.jsp的get请求参数使用
* 目的是在 doRedirect.do 和 hello.jsp 两次请求中传递数据
* 2. 在目标hello.jsp页面可也使用参数集合对象${param}获取请求参数
* ${param.myname}
* 3. 重定向不能访问/WEB-INF下的资源
*/
@RequestMapping(value="/doRedirect.do")
public ModelAndView doWithRedirect(String name, Integer age) {
ModelAndView mv = new ModelAndView();
//数据放入request作用域
mv.addObject("myname", name);
mv.addObject("myage", age);
//重定向
mv.setViewName("redirect:/hello.jsp");
return mv;
}

`

hello.jsp

1
2
3
4
5
6
7
8
9
10
11
<head>
<title>Redirect</title>
</head>
<body>
<h3>/hello.jsp 从request作用域获取对象</h3>
<h3>myname : ${param.myname} </h3> <br/>
<h3>age: ${param.myage} </h3>
<h3>取参数数据: <%=request.getParameter("myname")%> </h3>
</body>
</html>

异常处理

SpringMVC框架采取的是统一,全局的异常处理

把controller中的所有异常处理集中到一个地方。采用的是aop的思想,把业务逻辑和异常处理代码分开,解耦合

使用两个注解:

  1. @ExceptionHandler
  2. @ControllerAdvice

SpringMVC 框架处理异常的常用方式:使用@ExceptionHandler 注解处理异常。

异常处理步骤

  1. 新建maven web项目
  2. 加入依赖
  3. 新建一个自定义异常类,MyUserException, 再定义它的子类NameException, AgeException
  4. 在controller中抛出NameException, AgeException
  5. 创建一个普通类, 作为全局异常处理类
    • 再类上面加入@ControllerAdvice
    • 在类中定义方法,方法的上面加入@ExceptionHandler
  6. 创建处理异常的视图页面
  7. 创建springmvc配置文件
    • 组件扫描器,扫描@Controller注解
    • 组件扫描器,扫描@ControllerAdvice所在的包名
    • 声明注解驱动

@ExceptionHandler 注解

使用注解@ExceptionHandler 可以将一个方法指定为异常处理方法。该注解只有一个可 选属性 value,为一个 Class数组,用于指定该注解的方法所要处理的异常类,即所要匹配的异常。

而被注解的方法,其返回值可以是 ModelAndView、String,或 void,方法名随意,方法参数可以是 Exception 及其子类对象、HttpServletRequest、HttpServletResponse 等。系统会自动为这些方法参数赋值。

对于异常处理注解的用法,也可以直接将异常处理方法注解于 Controller 之中。

自定义异常类

定义三个异常类:NameException、AgeException、MyUserException。其中 MyUserException 是另外两个异常的父类。

1
2
3
4
5
6
7
8
public class MyUserException extends Exception {
public MyUserException() {
super();
}
public MyUserException(String message) {
super(message);
}
}
1
2
3
4
5
6
7
8
9
10
//当年龄有问题时,抛出的异常
public class AgeException extends MyUserException{
public AgeException() {
super();
}

public AgeException(String message) {
super(message);
}
}
1
2
3
4
5
6
7
8
9
10
//当用户的姓名有异常时,抛出NameException
public class NameException extends MyUserException{
public NameException() {
super();
}

public NameException(String message) {
super(message);
}
}

修改 Controller 抛出异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Controller
public class MyController {
@RequestMapping(value="/some.do")
public ModelAndView doSome(String name, Integer age) throws MyUserException {
ModelAndView mv = new ModelAndView();
//根据请求参数,抛出异常
if(!"zs".equals(name)) {
throw new NameException("姓名不正确");
}
if(age == null || age > 80) {
throw new AgeException("年龄比较大!");
}
mv.addObject("myname", name);
mv.addObject("myage", age);
mv.setViewName("show");
return mv;
}
}

定义全局异常处理类

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
/**
* @ControllerAdvice: 控制器增强(也就是给控制器类增加功能-- 异常处理功能)
* 位置:在类的上面
* 特点:必须让框架知道这个注解所在的包名,需要在springmvc配置文件中声明组件扫描器。
* 指定@ControllerAdvice所在的包名
*/
@ControllerAdvice
public class GlobalExceptionHandler {
//定义方法,处理发生的异常
/*
处理异常的方法和控制器定义的方法一样
可以有多个参数,可以有ModelAndView, String, void, 对象类型的返回值
形参:Exception: 表示Controller中抛出的异常对象
通过形参可以获取发生的异常信息
@ExceptionHandler(异常的class): 表示异常的类型,当发生此类型异常时候,
由当前方法来处理
*/
@ExceptionHandler(value = NameException.class)
public ModelAndView doNameException(Exception exception) {
//处理NameException异常
/**
* 异常发生处理的逻辑:
* 1. 需要把异常记录下来,记录到数据库,日志文件
* 记录异常发生的时间,哪个方法发生的,异常的错误内容。
* 2. 发送通知,把异常的信息通过邮件,短信,微信发送给开发人员
* 3. 给用户友好的提示。
*/
ModelAndView mv = new ModelAndView();
mv.addObject("msg", "姓名必须是zs, 其他用户不能访问");
mv.addObject("ex", exception);
mv.setViewName("nameError");
return mv;
}
@ExceptionHandler(value = AgeException.class)
public ModelAndView doAgeException(Exception exception) {
//处理AgeException异常
/**
* 异常发生处理的逻辑:
* 1. 需要把异常记录下来,记录到数据库,日志文件
* 记录异常发生的时间,哪个方法发生的,异常的错误内容。
* 2. 发送通知,把异常的信息通过邮件,短信,微信发送给开发人员
* 3. 给用户友好的提示。
*/
ModelAndView mv = new ModelAndView();
mv.addObject("msg", "您的年龄不能大于80");
mv.addObject("ex", exception);
mv.setViewName("ageError");
return mv;
}
//处理其他异常
@ExceptionHandler
public ModelAndView doOtherException(Exception exception) {
//处理其他异常
ModelAndView mv = new ModelAndView();
mv.addObject("msg", "其他的异常");
mv.addObject("ex", exception);
mv.setViewName("defaultError");
return mv;
}
}

创建异常响应页面

ageError.jsp

1
2
3
4
5
6
7
8
9
10
11
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
ageError.jsp <br/>
提示信息:${msg} <br/>
系统异常消息:${ex.message}
</body>
</html>

nameError.jsp

1
2
3
4
5
6
7
8
9
10
11
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>nameError.jsp</title>
</head>
<body>
nameError.jsp <br/>
提示信息:${msg} <br/>
系统异常消息:${ex.message}
</body>
</html>

default.jsp

1
2
3
4
5
6
7
8
9
10
11
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
defaultError.jsp <br/>
提示信息:${msg} <br/>
系统异常消息:${ex.message}
</body>
</html>

定义springmvc配置文件

1
2
3
<!--    处理异常需要的两步-->
<context:component-scan base-package="com.bjpowernode.handler"/>
<mvc:annotation-driven />

拦截器

SpringMVC 中的 Interceptor 拦截器是非常重要和相当有用的,它的主要作用是拦截指定的用户请求,并进行相应的预处理与后处理。其拦截的时间点在“处理器映射器根据用户提交的请求映射出了所要执行的处理器类,并且也找到了要执行该处理器类的处理器适配器, 在处理器适配器执行处理器之前”。当然,在处理器映射器映射出所要执行的处理器类时, 已经将拦截器与处理器组合为了一个处理器执行链,并返回给了中央调度器。

拦截器:

  • 拦截器是springmvc中的一种对象,需要实现HandlerInterceptor接口。

  • 拦截器和过滤器类似,功能方向侧重点不同,过滤器是用来过滤请求参数,设置编码字符集等工作。

    拦截器是拦截用户的请求,做请求判断处理的。

  • 拦截器是全局的,可以对多个Controller做拦截

    一个项目中可以有0个或多个拦截器,他们在一起拦截用户的请求

  • 拦截器常用在:用户登录处理,权限检查,记录日志

拦截器的使用步骤:

  1. 定义类实现HandlerInterceptor接口
  2. 在springmvc配置文件中,声明拦截器,让框架知道拦截器的存在

拦截器的执行时间

  1. 在请求处理之前,也就是 controller 类的方法执行之前先被拦截
  2. 在控制器方法执行之后也会执行拦截器
  3. 在请求处理完成后也会执行拦截器

拦截器处理步骤:

  1. 新建maven web项目
  2. 加入依赖
  3. 创建controller类
  4. 创建一个普通类,作为拦截器使用
    1. 实现HandlerInterceptor接口
    2. 实现接口中的三个方法
  5. 创建show.jsp
  6. 创建springmvc的配置文件
    1. 组件扫描器,扫描@Controller注解
    2. 声明拦截器,并指定拦截的uri地址

一个拦截器的执行

自定义拦截器,需要实现 HandlerInterceptor 接口。而该接口中含有三个方法:

preHandle(request,response, Object handler)

该方法在处理器方法执行之前执行。其返回值为 boolean,若为 true,则紧接着会执行处理器方法,且会将 afterCompletion()方法放入到一个专门的方法栈中等待执行。

postHandle(request,response, Object handler,modelAndView):

该方法在处理器方法执行之后执行。处理器方法若最终未被执行,则该方法不会执行。 由于该方法是在处理器方法执行完后执行,且该方法参数中包含 ModelAndView,所以该方法可以修 改处理器方法的处理结果数据,且可以修改跳转方向。

afterCompletion(request,response, Object handler, Exception ex)

当 preHandle()方法返回 true 时,会将该方法放到专门的方法栈中,等到对请求进行响应的所有工作完成之后才执行该方法。即该方法是在中央调度器渲染(数据填充)了响应页面之后执行的,此时对 ModelAndView 再操作也对响应无济于事。

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
//拦截器类,拦截用户的请求
public class MyInterceptor implements HandlerInterceptor {
private long btime = 0;
/**
* preHandler叫预处理方法
* 重要:是整个项目的入口,门户,当preHandle返回true, 请求可以被处理
* preHandle返回false, 请求到此方法就截止了
* 参数:
* Object handler: 被拦截的控制器对象
* 返回值是boolean
* true: 请求是通过拦截器验证,可以执行处理器方法
* false: 请求没有通过处理器的验证,请求到达拦截器就截止了,请求没有被处理
* 特点:
* 1. 方法在控制器方法(MyController的doSome)之前先执行的
* 用户的请求首先到达此方法
* 2. 在这个方法中,可以获取请求的信息,验证请求是否符合要求。
* 可以验证用户是否登录,验证用户是否有权限去访问某个连接地址(url)
* 如果验证失败,可以截断请求。请求不能被处理
* 如果验证成功,可以放行请求,此时控制器方法才能执行
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
btime = System.currentTimeMillis();
System.out.println("拦截器MyInterceptor的preHandler()");
// 给浏览器一个返回结果
// 计算的业务逻辑,根据计算结果,返回true或者false
// request.getRequestDispatcher("/tips.jsp").forward(request, response);
return true;
}

/**
* postHandle: 后处理方法
* 参数:
* Object handler: 被拦截的处理器对象Controller
* ModelAndView mv: 处理器方法的返回值
* 特点:
* 1. 在处理器方法之后执行的(MyController.doSome())
* 2. 能够获取到处理器方法的返回值ModelAndView, 可以修改ModelAndView中的数据和视图,
* 可以影响最后的执行结果
* 3. 主要是对原来的执行结果进行二次修正
*
*/
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView mv) throws Exception {
System.out.println("拦截器MyInterceptor的postHandle()");
//对原来的doSome()执行结果进行调整,需要调整。
if(mv != null) {
//修改数据
mv.addObject("mydate", new Date());
//修改视图
mv.setViewName("other");
}
}

/**
* afterCompletion: 最后执行的方法
* 参数:
* Object handler: 被拦截的处理器对象
* Exception ex: 程序中发生的异常
* 特点:
* 1. 在请求处理完成后执行的,框架中规定的是当你的视图处理完成后,对视图执行了forward,
* 就认为请求处理完成
* 2. 一般做资源回收工作的,程序请求过程中创建了一些对象,在这里可以删除
* 把占用的内存回收
*/
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) throws Exception {
System.out.println("拦截器MyInterceptor的afterCompletion()");
long etime = System.currentTimeMillis();
System.out.println("计算从preHandler到请求处理完成的时间:" + (etime - btime));

}
}

拦截器: 可以看作多个Controller中共有的功能,集中到拦截器统一处理,使用aop的思想

步骤

注册拦截器

在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
<?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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 声明组件扫描器-->
<context:component-scan base-package="com.bjpowernode.controller"/>
<!-- 声明springmvc框架中的视图解析器,帮助开发人员设置视图文件的路径-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前缀:视图文件的路径-->
<property name="prefix" value="/WEB-INF/view/"/>
<!-- 后缀:表示视图文件的扩展名-->
<property name="suffix" value=".jsp"/>
</bean>
<!-- 声明拦截器: 拦截器可以有0个或多个-->
<mvc:interceptors>
<!-- 第一个拦截器-->
<mvc:interceptor>
<!-- 指定拦截的uri地址的
path: 就是uri地址,可以使用统配符 **
**: 表示任意的字符,文件或多级目录和目录中的文件
http://localhost:8080/myweb/user/listUser.do
-->
<mvc:mapping path="/**"/>
<!-- 声明拦截器对象-->
<bean class="com.bjpowernode.handler.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
</beans>

用于指定当前所注册的拦截器可以拦截的请求路径,而/**表示拦截所 有请求。

修改index页面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<base href="http://localhost:8080/ch10_interceptor/"/>
</head>
<body>
<p>一个拦截器</p>
<form action="some.do" method="post">
姓名:<input type="text" name="name"> <br/>
年龄: <input type="text" name="age"> <br/>
<input type="submit" value="提交请求">
</form>

</body>
</html>
修改处理器
1
2
3
4
5
6
7
8
9
10
11
12
@Controller
public class MyController {
@RequestMapping(value="/some.do")
public ModelAndView doSome(String name, Integer age) {
System.out.println("======= 执行的是MyController中的doSome() ========");
ModelAndView mv = new ModelAndView();
mv.addObject("myname", name);
mv.addObject("myage", age);
mv.setViewName("show");
return mv;
}
}
修改 show 页面
1
2
3
4
5
6
7
8
9
10
11
12
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>show</title>
</head>
<body>
<h3>/WEB-INF/view/show.jsp 从request作用域获取对象</h3>
<h3>myname : ${myname} </h3> <br/>
<h3>myage: ${myage} </h3>
</body>
</html>

多个拦截器的执行

再定义一个拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//拦截器类,拦截用户的请求
public class MyInterceptor2 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("22222222-拦截器MyInterceptor的preHandler()");
return true;
}


@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView mv) throws Exception {
System.out.println("22222222-拦截器MyInterceptor的postHandle()");
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) throws Exception {
System.out.println("22222222 - 拦截器MyInterceptor的afterCompletion()");
}
}

多个拦截器的注册与执行

在springmvc.xml中加入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--    声明拦截器: 拦截器可以有0个或多个
在框架中保存多个拦截器是ArrayList,
按照声明的先后顺序放入到ArrayList
-->
<mvc:interceptors>
<!-- 第一个拦截器-->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.bjpowernode.handler.MyInterceptor"/>
</mvc:interceptor>
<!-- 声明第二个拦截器-->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.bjpowernode.handler.MyInterceptor2"/>
</mvc:interceptor>
</mvc:interceptors>

控制台执行结果

当有多个拦截器时,形成拦截器链。拦截器链的执行顺序,与其注册顺序一致。需要再次强调一点的是,当某一个拦截器的 preHandle()方法返回 true 并被执行到时,会向一个专门的方法栈中放入该拦截器的 afterCompletion()方法。

多个拦截器中方法与处理器方法的执行顺序如下图:

从图中可以看出,只要有一个 preHandle()方法返回 false,则上部的执行链将被断开, 其后续的处理器方法与 postHandle()方法将无法执行。但,无论执行链执行情况怎样,只要方法栈中有方法,即执行链中只要有 preHandle()方法返回 true,就会执行方法栈中的 afterCompletion()方法。最终都会给出响应。

换一种表现方式,也可以这样理解:


拦截器和过滤器的区别:

  1. 过滤器是Servlet规范中的对象,拦截器是框架中的对象

  2. 过滤器实现Filter接口的对象,拦截器实现HandlterInterceptor

  3. 过滤器是用来设置request, response 参数,属性的,侧重对数据进行过滤

    拦截器是用来验证请求的,能截断请求

  4. 过滤器是在拦截器之前先执行的

  5. 过滤器是tomcat服务器创建的对象,拦截器是springmvc容器中创建的对象

  6. 过滤器是一个执行时间点

    拦截器有三个执行时间点

  7. 过滤器可以处理jsp, js, html

    拦截器是侧重拦截对Controller的请求。如果你的请求不能被DispatcherServlet接受,这个请求就不会执行拦截器内容

  8. 拦截器拦截普通类方法执行,过滤器过滤servlet请求响应

权限拦截器举例

只有经过登录的用户方可访问处理器,否则,将返回“无权访问”提示

本例的登录,由一个 JSP 页面完成。即在该页面里将用户信息放入 session 中。也就是说,只要访问过该页面,就说明登录了。没访问过,则为未登录用户。

修改 index 页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<base href="http://localhost:8080/ch12_interceptor_permission/"/>
</head>
<body>
<p>一个拦截器</p>
<form action="some.do" method="post">
姓名:<input type="text" name="name"> <br/>
年龄: <input type="text" name="age"> <br/>
<input type="submit" value="提交请求">
</form>

</body>
</html>

定义 Controller

1
2
3
4
5
6
7
8
9
10
11
12
@Controller
public class MyController {
@RequestMapping(value="/some.do")
public ModelAndView doSome(String name, Integer age) {
System.out.println("======= 执行的是MyController中的doSome() ========");
ModelAndView mv = new ModelAndView();
mv.addObject("myname", name);
mv.addObject("myage", age);
mv.setViewName("show");
return mv;
}
}

定义 show 页面

1
2
3
4
5
6
7
8
9
10
11
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>show</title>
</head>
<body>
<h3>/WEB-INF/view/show.jsp 从request作用域获取对象</h3>
<h3>myname : ${myname} </h3> <br/>
<h3>myage: ${myage} </h3>
</body>
</html>

定义权限拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//拦截器类,拦截用户的请求
public class MyInterceptor implements HandlerInterceptor {
//验证登录的用户信息,正确return true, 其它return false
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("111111-拦截器MyInterceptor的preHandler()");
String loginName = "";
//从session中获取name的值
Object attr = request.getSession().getAttribute("name");
if(attr != null) {
loginName = (String)attr;
}
if(!"zs".equals(loginName)) {
//不能访问系统
request.getRequestDispatcher("/tips.jsp").forward(request, response);
return false;
}
//zs登录
return true;
}
}

定义 fail 页面

1
2
3
4
5
6
7
8
9
10
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
tips.jsp <br/>
非zs不能访问系统
</body>
</html>

注册权限拦截器

在springmvc.xml中声明

1
2
3
4
5
6
7
    <mvc:interceptors>
<!-- 第一个拦截器-->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.bjpowernode.handler.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>

定义 login 页面

用户模拟用户登录

1
2
3
4
5
6
7
8
9
10
11
12
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
模拟登录
<%
session.setAttribute("name", "zs");
%>
</body>
</html>

定义 logout 页面

1
2
3
4
5
6
7
8
9
10
11
12
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
退出系统,从session中删除数据
<%
session.removeAttribute("name");
%>
</body>
</html>
求大佬赏个饭