0 、概述
服务器端分成三层架构。
一、环境搭建
1.1 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<!-- 版本锁定 -->
<spring.version>5.0.2.RELEASE</spring.version>
<!-- 配置依赖 -->
<dependencies>
<!-- spring IOC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- spring web -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
</dependencies>配置核心的控制器(类似
servlet
类 –dispatcherServlet
)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<web-app>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
当<url-pattern>/</url-pattern>
时,默认就是拦截所有路径连静态资源也不能访问。但是Controller
中配置@RequestMapping
的路径是不会被拦截的,配置了@RequestMapping
就相当于在web.xml
中注<servlet>
。
1.2 第一个执行程序
1.2.1 引入SpringMVC配置文件
在resources
下新建springMVC.XML
配置文件:
1 |
|
1.2.2 编写控制器
1 | // 控制器 |
- 控制器类配置
@Controller
注解,表明是控制器。 - 在具体的方法上配置
@RequestMapping
注解,参数即为访问时的资源路径 return
中的字符串,方法执行完需要跳转的页面
1.2.3 完善springMVC.xml
配置文件
- 控制器中加入注解,那么就需要配置需要扫描的包;
- 配置文件解析器,创建
IOC
容器对象,由Tomcat
负责调用; - 配置
spring
开启注解MVC
的支持
1 |
|
WEB-INF
目录下的内容是对客户端不可见,只对服务端可见,即客户端不能直接对其进行访问。
1.2.4 完善web.xml
配置文件
由于上方的springMVC.XML
配置文件时在resources
目录下的,我们启动的是web
项目,就需要将该配置文件加载进Tomcat
服务器进行读取。
即:配置Servlet
的初始化参数,读取springMVC
的配置文件,创建spring
容器,用于加载配置文件。
当MVC
配置文件加载成功,那么其中的扫描就能够成功,继而将控制器中的类加载成对象。
1 |
|
二、Spring MVC 详解
2.1 执行过程及组件分析
2.1.1 执行过程
- 当启动
Tomcat
服务器的时候,因为配置了load-on-startup
标签,所以会创建DispatcherServlet
对象,就会加载springmvc.xml
配置文件。这里是服务器启动,应用被加载。读取到web.xml
中的配置创建spring
容器并且初始化容器中的对象。 - 在
springmvc.xml
配置文件中开启了注解扫描,那么相应的controller
对象(HelloController
)对象就会被创建。 - 从
index.jsp
发送请求,请求会先到达DispatcherServlet
核心控制器,根据配置@RequestMapping
注解找到执行的具体方法。浏览器发送请求,被DispatcherServlet
捕获,该Servlet
并不处理请求,而是把请求转发出去。转发的路径是根据请求URL
,匹配@RequestMapping
中的内容。 - 根据执行方法的返回值,再根据配置的视图解析器,去指定的目录下查找指定名称的
JSP
文件。即:根据方法的返回值,借助InternalResourceViewResolver
找到对应的结果视图。 Tomcat
服务器渲染页面,做出响应。
2.1.2 组件分析
DispatcherServlet
:前端控制器用户请求到达前端控制器,它就相当于
MVC
模式中的c
,dispatcherServlet
是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet
的存在降低了组件之间的耦合性。HandlerMapping
:处理器映射器HandlderMapping
负责根据用户请求找到Handler
,即处理器,SpringMVC
提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。Handler
:处理器它就是开发中编写的具体业务控制器。由
DispatcherServlet
把用户请求转发到Handler。由Handler
对具体的用户请求进行处理。Handler
:处理器适配器通过
HandlerAdapter
对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。View Resolver
:视图解析器View Resolver
负责将处理结果生成View
视图,View Resolver
首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View
视图对象,最后对View
进行渲染将处理结果通过页面展示给用户。View
:视图将数据视图展示给客户端,即用户。
2.1.3 <mvc:annotation-driver>
说明
在SpringMVC
的各个组件中,处理器映射器、处理器适配器、视图解析器成为SpringMVC
的三大组件。使用<mvc:annotation-driver>
自动加载RequestMappingHandlerMapping
(处理器映射器)和RequestMappingHandlerAdapter
(处理器适配器)。
2.2 常用注解说明
2.2.1 RequestMapping
作用:用于建立请求URL
和处理请求方法之间的对应关系。
2.2.2 RequestParam
作用:把请求中指定名称的参数给控制器中的形参赋值。(适用于请求名称与属性名不相同的情况)
属性:
value
:请求参数的名称。required
:请求参数中是否必须提供此参数。默认值:true
。表示必须提供,如果不提供将报错。
1 | "/useRequestParam") ( |
2.2.3 RequestBody
作用:用于获取请求体内容。直接使用得到是key=value&key=value...
结构的数据。get
请求方式不适用。
属性:
required
:是否必须有请求体。默认值是:true
。当取值是true
时,get
请求方式会报错。如果取值为false
,get
请求得到是null
。
1 | "/useRequestBody") ( |
2.2.4 PathVariable
作用:用于绑定url
中的占位符。例如:请求url
中 /delete/{id}
,这个{id}
就是url
占位符。url
支持占位符是spring3.0
之后加入的。是springmvc
支持rest
风格URL
的一个重要标志。
属性:
value
:用于指定url
中占位符名称。required
:是否必须提供占位符。
重点:restful
风格。
1 | "/usePathVariable/{id}") ( |
2.2.5 RequestHeader
作用:用于获取请求消息头
属性:
value
:提供消息头名称,用于指定获取消息头中的哪一部分。required
:是否必须有此消息头。
1 | "/useRequestHeader") ( |
将请求头内容的信息封装到requestHeader
参数中。
2.2.6 CookieValue
作用:用于把指定cookie
名称的值传入控制器方法参数。
属性:
value
:指定cookie
的名称。键值对的形式,通过键来获取到它的值。required
:是否必须有cookie
1 | "/useCookieValue") ( |
2.2.7 ModelAttribute
作用:该注解是SpringMVC4.3
版本以后新加入的。它可以用于修饰方法和参数。
出现在方法上,表示当前方法会在控制器的方法执行之前,先执行。它可以修饰没有返回值的方法,也可以修饰有具体返回值的方法。
出现在参数上,获取指定的数据给参数赋值。
属性:
value
:用于获取数据的key
。key
可以是POJO
的属性名称,也可以是Map
结构的key
。
应用场景:
当表单提交数据不是完整的实体类数据时,保证没有提交数据的字段使用数据库对象原来的数据。
例如:
当我们在编辑一个用户时,用户有一个创建信息字段,该字段的值是不允许被修改的。在提交表单数据时肯定没有此字段的内容,一旦更新会把该字段内容置为null
,此时就可以使用此注解解决问题。
示例1:
1 |
|
即先执行showModel
方法,再执行控制器中的方法testModelAttribute
。
2.2.7.1 ModelAttribute 修饰方法带返回值
需求:修改用户信息,要求用户密码不能修改
前端代码:
1 | <form action="springmvc/updateUser" method="post"> |
- 模拟查询数据库中用户信息
1 |
|
- 模拟修改用户方法
1 | "/updateUser") ( |
- 模拟去数据库查询
1 | private User findUserByName(String username) { |
首先通过前端请求,获取username
,而在showModel
方法中参数,就是由前端请求的参数username
。然后showModel
方法体中通过数据库查询对象,再将对象进行返回。返回的对象是数据中的相对应的原有对象。(类似过滤器,后续在控制器中将需要修改的值,进行修改,不变的值,就不需要再次改动)。
2.2.7.2 ModelAttribute 修饰方法不带返回值
需求:修改用户信息,要求用户的密码不能修改
- 前端代码
1 | <!-- 修改用户信息 --> |
- 查询数据库中用户信息– 模拟去数据库查询
1 |
|
- 模拟修改用户方法
1 | "/updateUser") ( |
- 模拟去数据库查询
1 | private User findUserByName(String username) { |
第二种不带返回值的方式更容易理解,即将原有的对象存放在map
集合中,再通过注解,获取指定(根据map
中的键)的数据给参数赋值。最后,将前端请求的参数对应的封装到这个参数中,就能够保证这个参数对象的所有属性都能够有值。
2.2.8 SessionAttributes
作用:用于多次执行控制器方法间的参数共享。只能作用在类对象(Class
)上。
属性:
value
:用于指定存入的属性名称type
:用于指定存入的数据类型
- 前端代码
1 | <!-- SessionAttribute 注解的使用 --> |
- 控制器中的代码
1 | "sessionAttributeController") ( |
通过model
对象进行值的存储操作,底层会将值存储在request
域中。而又使用@SessionAttributes
注解,具体有@SessionAttributes(value ={"username","password"},types={Integer.class})
。此时通过values
数组的指定 – username
、password
,则表明model
在存储操作时,不仅会将这两个的值存储在request
域对象中,同时也会存储在session
域对象中。
取值时,使用的model
实现类 – ModelMap
。
删除时,使用SessionStatus
。
2.3 请求参数的绑定
前端向后端进行请求时,表单中请求参数都是基于key=value
的。SpringMVC
绑定请求参数的过程是通过把表单请求参数,作为控制器中方法参数进行绑定的。
2.3.1 支持的数据类型
基本数据类型
- 基本数据类型
String
类型
POJO
类型参数(实现序列化接口)- 实体类
- 关联的实体类
数组和集合类型参数
包括
List
结构和Map
结构的集合(包括数组)
SpringMVC
绑定请求参数是自动实现的,但是想要使用,必须遵循使用要求。
2.3.2 使用要求
- 基本数据类型或者是String类型:
要求我们的参数名称必须和控制器中方法的形参名称保持一致。(严格区分大小写)
POJO
类型或者及其关联对象
如果表单中参数名称和POJO
类的属性名称保持一致。并且控制器方法的参数类型是POJO
类型。
如果是集合类型
第一种
要求集合类型的请求参数必须在
POJO
中。在表单中请求参数名称要和POJO
中集合属性名称相同。给
List
集合中的元素赋值,使用下标。给
Map
集合中的元素赋值,使用键值对。第二种
接收的请求参数是
json
格式数据。需要借助一个注解实现。
SpringMVC
可以实现一些数据类型自动转换。其内置转换器全部都在:org.springframework.core.convert.support
包下。
2.3.3 使用实例
2.3.3.1 POJO类型作为参数
- 实体类代码
1 | public class Account implements Serializable { |
以上是Account
类。其中关联Address
类:
1 | package cn.lizhi.domain; |
- 前端代码
1 | <form action="account/saveAccount" method="post"> |
通过对象.属性
的方式进行赋值。
- 控制器代码
1 | "/saveAccount") ( |
2.3.3.2 POJO类中包含集合类型参数
- 实体类 –
User
类
1 | package cn.lizhi.domain; |
- 前端代码
1 | <!-- POJO 类包含集合类型演示 --> |
集合、列表赋值的方式。三要素:属性
、下标
、参数(属性)
- 控制器代码
1 | "/updateAccount") ( |
2.4 请求参数乱码问题
在tomcat8
以后get
请求方式,中文正常显示;post
请求方式,中文会出现乱码问题。
在之前的serlvet
学习中,对乱码解决的方式,是通过Filter
过滤器。而现在SpringMVC
提供好现有的类供我们使用。
首先post
请求方式,在web.xml
中配置一个过滤器。
1 | <!-- 配置解决中文乱码的过滤器 --> |
如果想不过滤静态资源,在 Springmvc
的配置文件中可以配置,静态资源不过滤:
1 | <!-- location 表示路径,mapping 表示文件,**表示该目录下的文件以及子目录的文件 --> |
get
请求方式:
tomcat
对GET
和POST
请求处理方式是不同的,GET
请求的编码问题,要改tomcat
的server.xml
配置文件,如下:
1 | <Connector connectionTimeout="20000" port="8080" |
2.5 自定义类型转换器
例如,在前端输入日期格式时:
1 | <!-- 特殊情况之:类型转换问题 --> |
可以看出,请求参数中,date=2018-01-01
。
当我们在后端接收到请求时:
1 | "/deleteAccount") ( |
输出:删除了账户:2018-01-01
。如果当我们将接收参数的类型改为Date
类型时:
1 | "/deleteAccount") ( |
此时,前端再次请求时,会报400
。
下面便是我们自定义类型转化的方式:
首先,定义一个类,实现Convertet
接口,该接口有两个泛型。
1 | public interface Converter<S, T> {//S:表示接受的类型,T:表示目标类型 |
其中,S
表示接受的类型,T
表示目标类型。
其实现类:
1 | public class StringToDate implements Converter<String, Date> { |
其次,在Spring
配置文件中配置类型转换器。
Spring
配置类型转换器的机制是,将自定义的转换器注册到类型转换服务中去。
1 | <bean id="converterService" class="org.springframework.context.support.ConversionServiceFactoryBean"> |
通过查看源码:
1 | public class ConversionServiceFactoryBean implements FactoryBean<ConversionService>, InitializingBean { |
其中converters
是集合类型,并且有setter
方法,通过上方的spring
配置文件,给工厂注入一个新的类型转换器(不会覆盖原有的转换器)。
这样,在我们后面的使用中,便能够识别并对应转换成我们相应数据格式。
总之,实现转换器的方法以及对spring
的配置,就是在原有的转换器集合中,再加入一种我们自己编写的转换器,以供我们使用。
2.6 响应数据
2.6.1 方法返回值的分类
2.6.1.1 String 类型
Controller
方法返回字符串可以指定逻辑视图的名称,根据视图解析器为物理视图的地址。
即为前面所写的常规的返回值为String
的方法,返回值为所需要跳转的物理视图的名称(即为跳转页面名称 –>地址)。
1 | "/hello") (value= |
模拟应用 – 模拟对数据库中的数据进行查询
- 前端代码
index.jsp页面
1 | <!-- 模拟用户的修改 --> |
模拟update.jsp页面
1 | <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> |
- 后端控制器代码
1 | "userController") ( |
返回值是update
,即最后跳转到update
页面。
2.6.1.2 void类型
如果控制器的方法返回值是void
,在执行程序报404的异常,默认查找jsp
页面没有找到。
1 | Type Status Report |
从上面的错误信息可以看出,它会默认跳转到控制器中@RequestMapping("/example")
中initUpdate
的页面。
在SpringMVC
中Servlet
原始API
可以作为控制器中方法的参数,即在controller
方法形参上可以定义request
和response
,使用request
或response
指定相应结果。
使用
request
(即为存储转发)转向页面1
2request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request,
response);在存储转发中不需要写虚拟目录,但这里的资源路径需要写
webApp
中的全路径,没有像前面只写一个success
的原因是,前一种是交给了视图解析器进行管理;而这里没有用到视图解析器,是单纯由request
进行实现,故需要写出资源的全路径。使用
response
进行页面的重定向1
response.sendRedirect(request.getContextPath() + "/page.jsp");
需要加上虚拟目录。
通过
response
指定响应结果,例如响应json
数据:1
2
3response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("json串");
2.6.1.3 关键字的转发或重定向
使用关键字进行转发或重定向时,Spring
不会再通过视图解析器帮我们解析路径,需要我们自己手动配置。即就像上一小结中,资源路径需要写全。
Forward
转发
controller
方法在提供了String
类型的返回值之后,默认就是请求转发。当然,我们可以手动进行设置:
1 | "/testForward") ( |
如果用了forward:
,则路径必须写成实际视图url
,不能写逻辑视图。
它相当于它相当于request.getRequestDispatcher("**url**").forward(request,response)
。使用请求转发,既可以转发到jsp
(到视图解析器中),也可以转发到其他的控制器方法。
Redirect
重定向
controller
方法提供了一个String
类型返回值之后,它需要在返回值里使用:redirect:
1 | "/testRedirect") ( |
它相当于response.sendRedirect(url)
。需要注意的是,如果是重定向到jsp
页面,则jsp
页面不能写在WEB-INF
目录中,否则无法找到。
原因:
重定向是客户端的,而转发是服务端内部的。
重定向是让客户端去访问重定向的地址,而WEB-INF下的文件是不能通过外部访问的!
2.6.1.4 ResponseBody响应json数据
作用:该注解用于将Controller
的方法返回的对象,通过HttpMessageConverter
接口转换为指定格式的数据。如:json
、xml
等,通过Response
响应给客户端。
需求:使用@ResponseBody
注解实现将controller
方法返回对象转换为json
响应给客户端。
前置:SpringMVC
默认用MappingJacksonHttpMessageConverter
对json
数据进行转换,需要加入jackson
的包。我们的项目中通过导入依赖的方法进行包的管理。
1 | <!-- 对json处理的包,即能够使用@ResponseBody --> |
需要配置前端控制器不拦截静态资源。
DispatcherServlet
会拦截到所有的资源,导致一个问题就是静态资源(img、css、js)也会被拦截到,从而
不能被使用。解决问题就是需要配置静态资源不进行拦截,在springmvc.xml
配置文件添加如下配置:
1 | <!-- 设置静态资源不过滤 --> |
其中,mvc:resources
标签配置不过滤谁。
location
元素表示webapp
目录下的包下的所有文件.mapping
元素表示以/static
开头的所有请求路径,如/static/a
或者/static/a/b
.
客户端发送ajax
的请求,传的是json
字符串,后端把json
字符串封装到user
对象中。
响应:将对象转换成json
字符串。
- 前端部分代码
1 | <input type="button" value="异步请求测试" id="testJson"> |
- 控制器中的代码
1 | "/testResponseBody") ( |
在控制器中的代码,其中@RequestBody
将前端发送的ajax
请求的json
格式数据封装成JavaBean
。@ResponseBody
将后端返回值JavaBean
对象,格式化成json
数据类型返回给客户端,直接响应。
总结:首先通过@RequestBody
获取请求体数据,将前端请求的json
字符穿转换成JavaBean
对象,最后使用@ResponseBody
注解把JavaBean
对面转换成Json
字符串,直接响应。
2.6.1.5 ModelAndView对象
ModelAndView
对象是Spring
提供的一个对象,可以用来调整具体的JSP
视图,即可以用作控制方法的返回值。该对象中有两个方法:
方法1:addObject
1 | public ModelAndView addObject(String attributeName, Object attributeValue) { |
添加模型到该对象中,通过上面的代码可以看出,和前面所讲的请求参数封装中用到的对象是同一个。即jsp
页面中同样可以使用EL
表达式从request
域中获取值。
方法2:setViewName
1 | public void setViewName(@Nullable String viewName) { |
用于设置逻辑视图名称,视图解析器会根据名称前往指定的视图。这也ModelAndView
和Model
最大的不同,它可以设置跳转的逻辑视图名称。
使用示例:
1 | "/testModelAndView") ( |
- 可以将对象存储到
request
域中。mv.addObject("key",value)
- 可以存储跳转到哪个页面中。
mv.setViewName("success")
– 选择视图解析器,进行解析。
前端代码:可以直接通过EL
表达式进行值的获取,${username} --> 张三
。
ModelAndView
和Model
两者的不同:
Model
是每次请求中都存在的默认参数,利用其addAttribute()
方法即可将服务器的值传递到jsp
页面中;ModelAndView
包含model
和view
两部分,使用时需要自己实例化,利用ModelMap
用来传值,也可以设置view
的名称。- 总而言之,就是
ModelAndView
除了同Model
一样包含存储的键值外,它还存储着跳转页面的名称地址,直接将ModelAndView
对象整体作为返回值,其对象中存储的地址自动由SpringMVC
进行视图解析。
2.7 SpringMVC实现文件上传
文件上传的前提:
form
表单的enctype
取值必须是:multipart/form-data
。默认值是:application/x-www-form-urlencoded
。enctype
:是表单请求正文的类型。method
属性取值必须是POST
- 提供一个文件选择域
<input type="file"/>
文件上传的原理:
1 | 当form 表单的enctype取值不是默认值后,request.getParameter()将失效。 |
使用Commons-fileupload
组件实现文件上传,需要导入该组件相应的支撑jar
包:
Commons-fileupload
Commons-io
Commons-io
不属于文件上传组件的开发jar
文件,但Commons-fileupload
组件从1.1版本开始,它工作时需要commons-io
包的支持。
导入依赖:
1 | <!-- 文件上传依赖 --> |
前端页面:
1 | <form action="/user/testUpload" method="post" enctype="multipart/form-data"> |
后端页面:
1 | "/testUpload") ( |
备注:request.getSession().getServletContext()
获取servlet
容器对象(最大的域对象),也可以理解为项目真正部署到tomcat
时获得的tomcat
(服务器)对象。因为session
是服务器端对象。而request.getSession().getServletContext().getRealPath()
用于获取指定目录在服务器端所真正部署的路径。
2.7.1 SpringMVC传统方式的文件上传
SpringMVC
传统方式的文件上传,指的是我们上传的文件和访问的应用存在于同一台服务器上,并且上传完成以后,浏览器可能跳转。
SpringMVC
框架提供了MultipartFile
对象,该对象表示上传的文件,要求变量名称必须和表单file
标签的name
属性名称相同。
- 配置文件解析器
1 | <!-- 配置文件解析器对象,要求id名称必须是multipartResolver --> |
- 控制器代码
1 | "/testSpringMVCUpload") ( |
SpringMVC
文件上传的主要步骤图:
首先前端请求,经过前端控制器将请求交给配置文件解析器,由相应的处理器进行处理。
2.7.2 SpringMVC跨服务器方式的文件上传
在实际开发中,我们可能会处理很多不同功能的服务器。例如:
- 应用服务器:负责部署我们的应用
- 数据库服务器:运行我们的数据库
- 缓存和消息服务器:负责处理大并发访问的缓存和消息
- 文件服务器:负责存储用户上传文件的服务器
即每个服务器负责每一个功能模块,而不是由一个服务器承担全部。分服务器处理的目的是让服务器各司其职,从而提高我们项目的运行效率。请求都由应用服务器进行处理转发,而需要的功能都分发到每个单独的服务器进行处理。见下图:
现在模拟图片上传:
- 导入开发需要的
Jar
包
1 | <!-- 跨服务器上传需要的依赖 --> |
- 需要在图片服务器上的
web.xml
添加如下配置文件
1 | <servlet> |
即:接收文件的目标服务器可以支持写入操作。
- 编写应用服务器端的控制器代码
1 | "/testSpringMVCUpload2") ( |
2.8 SpringMVC的异常处理
系统中的异常包括两类:预期异常和运行时异常RuntimeException
,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发、测试通过手段减少运行时异常的发生。
系统的dao
、service
、controller
出现都通过throws Exception
向上抛出,最后由SpringMVC
前端控制器交由异常处理器进行异常处理。SpringMVC
异常处理见下图:
控制器中捕获异常,从下向上抛出异常。
其目的是为了在出现异常的时候,跳到另外一个“友好”页面。模拟应用步骤:
自定义一个异常类。(继承
Exception
)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package cn.lizhi.other;
public class SysException extends Exception {
// 用于定义异常信息
private String message;
public SysException(String message) {
this.message = message;
}
public SysException() {
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}编写异常处理器类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class SysExceptionResolver implements HandlerExceptionResolver {
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler, Exception ex) {
// 异常信息打印
ex.printStackTrace();
SysException sysException = null;
if (ex instanceof SysException) { // 如果抛出的是自定义异常则直接转换
sysException = (SysException) ex;
} else {
//如果抛出的不是系统自定义异常则重新构造一个系统错误异常。
sysException = new SysException("系统错误,请于管理员联系");
}
ModelAndView mv = new ModelAndView();
mv.addObject("message", sysException.getMessage());
mv.setViewName("error");
return mv;
}
}即异常处理器,实现接口
HandlerExceptionResolver
,其中ex
参数表示接收到的异常对象。其中创建的ModelAndView
用于存入异常信息,键值对;再设置跳转路径。这里我们可以通过判断不同的异常类型跳转到不同的页面显示。
bean
配置文件中配置异常处理器类。1
2<!-- 配置自定义异常处理器 -->
<bean id="handlerExceptionResolver" class="cn.lizhi.other.SysExceptionResolver"/>模拟出现异常
1
2
3
4
5
6"testException") (
public String testException() {
System.out.println("模拟异常处理器的处理方式");
int i = 3 / 0;
return "success";
}页面出现结果 –> 系统错误,请于管理员联系
2.9 SpringMVC中的拦截器
Spring MVC
的处理器拦截器类似于 Servlet
开发中的过滤器 Filter
,用于对处理器进行预处理和后处理。
用户可以自己定义一些拦截器来实现特定的功能。
谈到拦截器,还要向大家提一个词——拦截器链(Interceptor Chain)
。拦截器链就是将拦截器按一定的顺
序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。
说到这里,可能大家脑海中有了一个疑问,这不是我们之前学的过滤器吗?是的它和过滤器是有几分相似,但
是也有区别,接下来我们就来说说他们的区别:
过滤器是
servlet
规范中的一部分,任何java web
工程都可以使用。拦截器是
SpringMVC
框架自己的,只有使用了SpringMVC
框架的工程才能用。过滤器在
url-pattern
中配置了/*
之后,可以对所有要访问的资源拦截。拦截器它是只会拦截访问的控制器方法,如果访问的是
jsp
,html
,css
,image
或者js
是不会进行拦
截的。它也是
AOP
思想的具体应用。我们要想自定义拦截器, 要求必须实现:
HandlerInterceptor
接口。HandlerInterceptor
拦截的是请求地址,所以针对请求地址做一些验证、预处理等操作比较合适。
2.9.1 自定义拦截器步骤
创建类,实现
HandlerInterceptor
接口,需要重写需要的方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public class MyInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("拦截器执行了...前");
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("拦截器执行了...后");
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("页面跳转完毕后...");
}
}preHandler
方法是在
controller
方法执行前,进行拦截的方法;返回结果为true
表示放行,返回结果为false
表示拦截(配合条件判断使用),因为提供了request
、response
参数,所以可以使用转发或者重定向直接跳转到指定的页面。调用:按拦截器定义顺序调用
作用:如果决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是需要业务处理器进行处理,则返回
true
;如果不需要再调用其他的组件去处理请求,则返回false
。postHandler
后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以通过
modelAndView
(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView
也可能为null
。按拦截器定义逆序调用;在拦截器链内所有拦截器返回成功后进行调用。
作用:在业务处理器处理完请求后,但是
DispatcherServlet
向客户端返回响应前被调用,处理ModelAndView
中的值。afterCompletion
调用顺序:按拦截器定义逆序调用
调用时机:对拦截器链内所有拦截器内返回成功的拦截器才调用它的
afterCompletion
方法。作用:在业务处理器处理完请求后,但是
DispathcerServlet
向客户端返回响应前被调用。可以在该方法中进行一些资源清理的操作。在
view
视图渲染完毕以后执行,不能再进行页面的跳转。
一旦在
preHandler
方法中返回了false
(等同于出现了异常),后续的拦截器和controller
方法就都不会再执行,也不会再执行postHandler
方法,只会倒叙执行afterCompletion
方法。
拦截器只能拦截Controller
中的方法。
在
springmvc.xml
配置文件中配置拦截器类1
2
3
4
5
6
7
8
9
10
11
12<!-- 配置拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<!-- 哪些方法进行拦截 -->
<mvc:mapping path="/user/*"/>
<!-- 哪些方法不进行拦截
<mvc:exclude-mapping path=""/>
-->
<!-- 注册拦截器对象 -->
<bean id="myInterceptor" class="cn.lizhi.utils.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>可以配置多个拦截器,拦截器执行的顺序就是配置文件中注册的拦截器对象的顺序。
2.9.2 HandlerInterceptor接口中的方法总结
preHandle
方法是controller
方法执行前拦截的方法可以使用
request
或者response
跳转到指定的页面return true
放行,执行下一个拦截器,如果没有拦截器,执行controller
中的方法。return false
不放行,不会执行controller
中的方法。
postHandle
是controller
方法执行后执行的方法,在JSP
视图执行前。可以使用
request
或者response
跳转到指定的页面如果指定了跳转的页面,那么
controller
方法跳转的页面将不会显示。
postHandle
方法是在JSP
执行后执行request
或者response
不能再跳转页面了
三、SpringMVC扩展
3.0 启动搭建
SpringMVC是Spring的web模块;所有模块的运行都是依赖核心模块(IOC模块)
- 核心容器模块
- web模块
SpringMVC思想是由一个前端控制器能拦截所有请求,并智能派发;这个前端控制器是一个Servlet;应该在web.xml中配置servlet来拦截所有请求。
前端控制器:
1 | <!-- 前端控制器 --> |
然后在springMVC.xml
中配置扫描组件。
配置视图解析器,能帮我们拼接页面地址,即进行地址映射。方式,通过前缀和后缀的方式进行映射。
1 | <!-- 配置spring创建容器时要扫描的包 --> |
SpringMVC中的HelloWorld的运行流程:
运行流程:
- 客户端点击链接发送请求
- 来到tomcat服务器
- SpringMVC的前端控制器收到所有请求
- 来看请求地址和
@RequestMapping
标配的哪个匹配,来找到到底使用哪个类的哪个方法 - 前端控制器找到了目标处理器类和目标方法,直接利用返回执行目标方法;
- 方法执行完成以后会有一个返回值;SpringMVC认为这个返回值就是要去的页面地址
- 拿到方法返回值以后;用视图解析器进行拼串得到完整的页面地址;
- 拿到页面地址,前端控制器帮我们转发到页面;
@RequestMapping
:就是告诉SpringMVC,这个方法用来处理什么请求。如果不指定配置文件位置:
/WEB-INF/springDispatcherServlet-serlvet.xml
如果不指定也会默认去找一个文件;/WEB-INF/xxx-serlvet.xml,其中xxx是指定的前端控制器的名称,因此也可以创建这么一个xml文件作为SpringMVC的配置文件,直接放在web下。
3.1 前端控制器路径问题
SpringMVC中关于前端控制器路径配置的问题:
在整个Tomcat服务器中,服务器大web.xml
中有一个DefaultServlet
是url-pattern=/
,我们自定义配置中的前端控制器也设置的url-pattern=/
,如果访问静态资源,就会来到DispatcherServlet(前端控制器)看那个方法的RequestMapping是这个index.html
。
在Tomcat中的DefaultServlet
是Tomcat中处理静态资源的。除过Servlet和jsp都是静态资源;我们的前端控制设置了/
禁用了tomcat
服务器中的DefaultServlet。
为什么jsp又能访问;因为我们没有覆盖服务器中的JspServlet
的配置。
关于上面这一块可以看下Tomcat总的web.xml文件,里面配置了jsp的servlet,静态资源的servlet,动态资源servlet,它们都设定了各种路径,来映射到这些对应个的servlet上进行处理。但是,一旦我们的DispatcherServlet
进行了配置,会优先交给它进行拦截请求,到Controller
中进行处理,其实本质上DispatcherServlet
也是servlet
,只是它将所有拦截到的请求都映射到给它自己来进行请求,交给Controller
进行处理。
所以/*
直接就是拦截所有请求;我们写/
;也是为了迎合后面的Rest风格的URL
地址。
3.2 字符编码
在web.xml中配置字符过滤。
1 | <!-- 配置解决中文乱码的过滤器 --> |
GET的请求编码,设置在服务器端,在server.xml的8080处添加URIEncoding=”UTF-8”
字符编码Filter一定要在其它Filter之前。
1 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { |
3.3 SpringMVC数据传递
思考:SpringMVC除过在方法行传入原生的request和session外还能怎么样在数据带给页面?
可以在方法处传入Map、或者Model或者ModelMap。给这些参数里面保存的所有数据都会放在域中。可以在页面获取。
方式1:
1 | "/handle01") ( |
这样前端,可以通过在request域中获取到这个map中存储的值。
方式2/3:采用Model和ModelMap
1 | "/handle02") ( |
同方式一一样,都可以通过在前端中的request域中获取到值。
通过三种的类型打印:
1 | class org.springframework.validation.support.BindingAwareModelMap |
都是同一种类型。
所以,Map、Model,ModelMap,最终都是BindingAwareModelMap工作。相当于给BindingAwareModel中保存的东西都会被放在请求域中。
通过源码分析,他们之间的关系:
方法的返回值可以变为ModelAndView类型:
1 | "/handle04") ( |
这种方式,同时将view和model数据进行返回;而且数据是放在请求域中。可以为页面携带数据。
SpringMVC提供了一种可以临时给Session域中保存数据的方式;使用注解:@SessionAttributes
。只能使用在类上。例如:@SessionAttributes(value = {"msg"},types={String.class})
;给BindingAwareModelMap中保存的数据,或者ModelAndView中的数据。同时给session中放一份。value指定保存数据时要给session中方的数据的key。分别以指定value和types两种方式进行保存session域。
3.4 ModelAttribute
1 | "/handle05") ( |
在全字段更新中,首先根据Id
从数据库中查询出Account对象,再根据前端的传过来的字段值进行相应的覆盖,而不是创建一个新的对象,对其赋值,否则里面空值就会覆盖原来的数据库中的值。
就是通过ModelAttribute达到这个效果。
思想核心:
- SpringMVC要封装请求参数的Book对象不应该是自己new出来的。而应该是从数据库中拿到的准备好的对象。
- 再来使用这个对象封装请求参数。
@ModelAttribute
,可以标记在方法和对象上。
标记在方法上:
- 这个方法就会提前于目标方法先运行,即提前在数据中查询对应的对象信息。
- 将这个对象信息保存起来(方便下一个方法还能使用),保存的方式,可以通过map进行保存(保存在request域中)
- 可以告诉SpringMVC要封装的请求参数不应该是自己new出来的,而是从数据库中查询出来的。再使用这个对象封装参数
- 在接收请求参数的对象地方再标记上
@ModelAttribute
,其中的value就是前面保存在map中指定的Key,目的是做映射,这样就能在请求参数封装的对象中,取出从数据库中查询到的值。
- 在接收请求参数的对象地方再标记上
1 | "/handle05") ( |
会在每个controller执行之前,先执行ModelAttribute目标方法,然后绑定在对应的参数上,这个参数并不是新new出来的,而是map中保存的。
3.4.1 原理
这里ModelAttribute
中存放的map和Model,都是同一个对象。
3.5 SpringMVC源码解析
3.5.1 前端控制器架构 —— DispatcherServlet
当请求到来的时候,其执行流程:
再进一步细看doDispatch:
ha.handle执行目标方法,即处理(控制)器的方法被调用。
processDispatchResult
总流程:
- 所有请求过来DispatcherServlet收到请求;
- 调用doDispatch()方法进行处理
- 检查是否是文件上传请求
- 如果是文件上传请求,就包装新的request
- 通过getHandler(processedResult)根据当前请求地址找到哪个类可以进行处理,即找到处理这个请求的目标处理器,就是找到对应的Controller
- 根据当前请求在HandlerMapping中找到这个请求的映射信息,获取到目标处理器类
- 如果没有找到对应的处理器,就抛出异常。
- 通过getHandlerAdapter(),根据当前处理器你类获取到能执行这个处理器方法的适配器。就是确定当前类下哪个方法能够处理这个请求。即拿到能执行这个类的所有方法的适配器(反射工具)。(这里是注解方式的适配器)
- 根据当前处理器类,找到当前类的HandlerAdapter(适配器),即具体的方法。
- 使用适配器执行目标方法,将目标方法执行完成后的返回值作为视图名,并设置到modelAndView中。目标方法无论怎么写,最终适配器执行完成以后都会执行后的信息封装成ModelAndView
- 如果没有视图名设置一个默认的视图名
- 根据ModelAndView的信息转发到具体的页面。processDispatchResult()方法;转发到目标页面,根据方法最终执行完成后封装到ModelAndView;转发到对应页面,而且ModelAndView中的数据可以从请求域中获取。
3.5.2 getHandler()细节
怎么根据当前请求就能找到哪个类来处理。进一步进行验证,进入该方法进行一探究竟。
该方法会返回目标处理器类的执行链;
HandlerMapping:处理器映射:他里面保存了每一个处理器能处理哪些请求的映射信息。
- DefaultAnnotationHandlerMapping:下面的handlerMap保存了每个请求能够通过哪个controller进行处理。通过遍历,找到请求对应的处理器。(在IOC容器一启动,就创建controller对象,再进行扫描,每个处理器都能进行处理什么请求,并保存到handlerMap属性中;下一次请求过来,就来看哪个HandlerMapping中有这个请求)
3.5.3 getHandlerAdapter
根据前面得到的处理器,找到其对应请求的适配器。
1 | ``` |
组件的初始化:去容器中找这个组件,如果没有找到就用默认的配置;(有些组件在容器是使用类型找的,有些组件是使用id找的)
3.5.5 ha.handle(…)深化
难点是方法执行。该方法是如何通过反射确定程序传入的参数,进行的方法执行?
以下列该方法为例:
1 | "/handle05") ( |
handler执行,是如何通过反射能够确定上面的参数,并执行该方法的?
该方法内部,最终是通过,invokHandlerMethod(request,response,handler)
执行的目标方法。其大致流程:
拿到方法的解析器
方法解析器根据当前请求地址找到真正的目标方法
创建一个方法执行器
包装原生的request,response
创建一个BindingAwareModelMap,创建一个隐含模型
执行目标方法 ——
Object result = methodInvoker.invokeHandlerMethod(...)
;真正执行目标方法;目标方法利用反射执行期间确定参数值,提前执行modelAttribute等所有的操作都是在方法中;找到所有@ModelAttribute注解标注的方法
args
;确定modelAttribute
方法执行时要使用的每一个参数的值,当前返回值的这个方法。创建了一个和参数个数一样长度数组,用于保存
找到目标方法这个参数的所有注解,如果有注解就解析并保存注解的信息;
如果没有找到注解
- resolveCommonArgument,解析普通参数值
- 继而进入,resolveStandarArgument(解析标准参数),即用来确定当前参数,是否是原生API。
- 查看该参数是否是未解析参数
- 查看该参数是否是默认值
- 获取到参数类型,判断该参数类型是否Model旗下,或者参看是否是Map类型旗下。如果是的话,就将隐含模型将其赋值给
args[i]
参数。
- resolveCommonArgument,解析普通参数值
将目标方法参数通过暴力反射,做成可访问的。
attributeMethodToInvoke
执行该目标方法。如果方法上标注的ModelAttribute注解如果有value值,就是attrName的值。如果没设置,其值就会变为返回值类型首字母小写,比如void,或者account等。
@ModelAttribute
标在方法上的另一个作用;可以把方法运行后的返回值按照方法上@ModelAttribute("value")
指定key
放到隐含模型中,如果没有设置,就用返回值首字母小写。把提前运行的ModelAttribute方法的返回值也放在隐含模型中。
对
@ModelAttribute
方法执行完以后,再次解析目标方法参数是哪些值。如果参数标注了注解,保存是哪个注解的详细信息,
- 如果参数有ModelAttribute注解,拿到ModelAttribute注解的值让attrName保存,其保存的是注解的value值,否则是返回值小写。
如果没标注解
- 先看是否普通参数(是否是原生api)
- 如果不是,再看是否Model或者Map,如果是就传入隐含模型。如果操作map,使用的隐含模型在前面的
@ModelAttribute
如果自定参数没有注解
- 先看是否是原生API
- 不是,就再看是否model或者map
- 再看是否是其他类型的,比如:SessioinStatus、Http
- 如果不是,就判断是否是简单类型的属性,比如:Integer,String,基本类型等
- 如果是,paramName=””
- 否者,就给attrName=””
如果是自定类型对象,最终产生两个效果;
- 如果这个参数标注了ModelAttribute注解就给attrName赋值为这个注解的value值
- 如果这个参数没有标注ModelAttribute注解就给attrName赋值””;
确定自定义类型参数的值;还要将请求中的每一个参数赋值给这个对象。
WebDataBinder对象,即resolveModelAttribute(…)方法对数据进行绑定。
如果attrName是空串;就将参数类型的首字母小写作为值
确定对象目标值(SpringMVC确定POJO值的三步)
如果隐含模型中有这个key(标了ModelAttribute注解就是注解指定的value,没标就是参数类型的首字母小写)指定的值;如果有讲这个值赋值给bindObject
如果没有,再判断是否是SessionAttributes标注的属性,就从session中拿
如果都不是,就利用反射创建对象。
最后在return处,执行目标方法
总结:
运行流程简单版
确定方法每个参数的值
标了注解:保存注解的信息;最终得到这个注解应该对应解析的值;
没标注解
看是否原生API
看是否Model或者是Map,xxx
都不是,看是否是简单类型;paramName=””
给attrName赋值;attrName(参数标了
@ModelAttribute("")
就是指定的,没标就是””)确定自定义类型参数:
- attrName使用参数的类型首字母小写;或者使用之前
@ModelAttribute("")
的值 - 先看隐含模型中有这个attrName作为key对应的值;如果有就从隐含模型中获取并赋值
- 看是否
@SessionAttributes(value="xxx")
;标注的属性,如果是从session中拿;- 如果存在该注解,但是拿不到,就会报出异常
- 否者,通过反射创建类型参数对应的对象
- attrName使用参数的类型首字母小写;或者使用之前
拿到之前创建好的对象,使用数据绑定器(WebDataBinder)将请求中的每个数据绑定到对象。
@ModelAttribute
标注的方法会提前运行并把方法的运行结果放在隐含模型中;- 放的时候会使用一个key;
- 如果
@ModelAttribute("value")
指定了,就用指定的value; - 如果没有指定,就用返回值类型的首字母小写作为Key;
- 如果
- 放的时候会使用一个key;
@SessionAttributes(value="xxx")
最好不要使用- 如果用的话,隐含模型中存在需要的值,这样就不会跳转到这一步
- 如果隐含模型中不存在,那就需要
session
域中存在需要的值,否者会报错。
3.5.6 视图和视图解析器
上一小节中,能够让模型进行了数据处理,那该如何对视图进行渲染呢。
视图解析器中的拼串,是从当前web项目下的,/WEB-INF/..进行拼接,资源请求。
如果采用转发:
1 | "/handle06") ( |
采用转发,是从当前项目开始资源请求(\
web项目的root目录下,不加就是相对路径),而不是/WEB-INF/..
。
采用这种方式,可以将请求转发给另外一个controller进行请求处理。
1 | "/handle05") ( |
foward
:前缀的转发,不会由我们配置的视图解析器进行解析。
如果采用重定向:
redirect:重定向的路径
原生的Servlet
重定向需要加上项目名才能成功。
1 | response.sendRedirect("demoName/hello.jsp") |
而使用SpringMVC会为路径自动的拼接上项目名(写法同转发)。
总结:有前缀的转发和重定向操作,配置的视图解析器就不会进行拼串。
3.5.7 视图解析源码解析
- 方法执行后的返回值会作为页面地址参考,转发或重定向到页面
- 视图解析器会进行页面地址的拼串
任何方法的返回值,都会包装成ModelAndView
核心方法processDispatchResult(...)
进行页面的渲染,就是将域中的互数据在页面进行展示。下面对该方法的分析:
调用render(…)进行渲染页面。
得到View与ViewResolver;
- ViewResovler的作用是根据视图名(方法的返回值)得到View对象,具体体现在它的
resolverViewName(...)
方法上
再进行探究,如何通过这个方法的返回值(视图名)得到View对象:
- 首先遍历所有的viewResolvers,调用
resolverViewName(...)
视图解析器根据方法的返回值,得到一个View
对象。即:所有配置的视图解析器都来尝试根据视图名(返回值)得到View(视图)对象;如果能得到就返回,得不到就换下一个视图解析器。 resolverViewName(...)
的具体实现createView(...)
- 是否重定向
- 如果是转发
- 如果没有前缀,就使用父类默认创建一个View
- 返回View对象
- 调用View对象的render方法;
- 最终方法落地在
renderMergeOutputModel(...)
方法。- 期间方法中
exposeModelAsRequestAttributes(...)
方法将隐藏模型中的数据设置到request
中的请求域中。
- 期间方法中
结论:视图解析器只是为了得到视图对象;视图对象才能真正的转发(将模型数据全部放在请求域中)或者重定向到页面。视图对象才真正渲染视图。
1 | <mvc:view-controller path="/handle" view-name="login"/> |
配置在mvc下的该标签,进行view时,经过mvc处理(不重要)。但是,只有当前映射的好用,其它的不好使。所以看下面这个高级功能:
1 | <mvc:annotation-driver/> |
3.5.8 自定义视图和视图解析器
自定义视图解析器工作的整体流程:
- 让我们的视图解析器工作
- 得到我们的视图对象
- 我们的视图对象自定义渲染逻辑 - render负责渲染
1 | response.getWriter().write("..") // |
自定义视图和视图解析器的步骤
编写自定义的视图及解析器
视图解析器必须放在IOC容器中
为了让自定义的视图解析器先执行,还需要实现Order接口(默认的视图解析器优先级最高)
实现代码:
1 | public class MyViewResolver implements ViewResolver, Ordered { |
视图解析器装配:
1 | <bean id="myResolver" class="cn.lizhi.view.MyViewResolver"> |
3.5.9 数据转换
SpringMVC封装自定义类型对象的时候,是如何封装并绑定请求参数的?
JavaBean要和页面提交的数据进行一一绑定的过程。
牵扯到以下操作:
- 数据绑定期间的数据类型转换(例如:前端传递过来的Key=value都是字符串类型,就需要对其进行类型转换)
- 数据绑定期间的数据格式化问题(例如:日期格式转化)
- 数据校验,即我们提交的数据必须是合法的。
核心方法:
1 | bindRequestParameters(...) // 请求参数解析与绑定 |
WebDataBinder:数据绑定器负责数据类型转化和数据校验
ConversionService组件:负责数据类型的转换以及格式化功能
Validators:负责数据校验工作
bindingResult:负责保存以及解析数据绑定期间数据校验产生的错误
自定义类型转换器:
不同类型的转换和格式化用它自己的
converter
,ConversionService
存在多个converter
ConversionService
是一个接口,它里面有converter进行工作;步骤:
实现Converter接口,写一个自定义的类型转换器;
两个泛型:
- S:Source:原数据类型
- T:Target:需要转换的数据类型
在convert方法中写转换的逻辑
Converter是ConversionService中的组件
- 将自己编写的converter放进ConversionService中
- 将WebDataBinder中的ConversionService设置成我们这个加了自定义类型转换器的ConversionService
配置出ConversionService,配置其对应的ConversionServiiceFactory的Bean,通过set注入配置
1
2
3
4
5
6
7
8
9
10
11
12<!-- 告诉SpringMVC别用默认的ConversionService,用我们自定义的ConversionService、这里包含我们自定义的Converter -->
<!-- 配置spring开启注解mvc的支持 -->
<mvc:annotation-driven conversion-service="converterService" /> // 1
<bean id="converterService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<!-- 给工厂注入一个新的类型转换器,并使用我们自己配置的类型转换组件 -->
<property name="converters">
<array>
<!-- 配置自定义类型转换器 -->
<bean class="cn.lizhi.utils.StringToDate"/>
</array>
</property>
</bean>
3.5.10 <mvc:annotation-driven/>
标签
1 | <!-- 静态资源能访问,动态映射的请求就不行 自己映射的请求就进行处理,不能处理的直接交给tomcat --> |
通过:
只有1时,DefaultAnnotationHandlerMapping
中的handlerMap中保存了每一个资源的映射信息;
静态资源不能访问:就是handlerMap
中没有保存静态资源映射的请求
handlerAdapter
:方法执行的适配器;
只有2时,动态映射HandlerMapping对应的映射没有了DefaultAnnotationHandlerMapping
没有了;使用SimpleUrlHandlerMapping替换了,他的作用就是将所有请求交给tomcat,而tomcat中,只配置了dispatcherServlet,没有其他映射的servlet,所以动态请求无法处理。
当1,2都添加时,会有RequestMappingHandlerMapping:动态资源可以访问;handlerMethods属性保存了每一个请求用哪个方法来处理。
- HandlerAdapters:存在RequestMappingHandlerAdapter,原来的AnnotationMethodHandlerAdapter被换成RequestMappingHandlerAdapter。
四、SSM框架的整合
核心思想:通过Spring
整合另外两个框架。
整合方式:配置文件加注解的方式。
整合的思路:
- 搭建整合的环境
Spring
的配置搭建完成Spring
整合SpringMVC
框架Spring
整合Mybatis
框架
4.1 环境搭建
- 数据库创建
1 | create database ssm; |
pom.xml
依赖导入
1 |
|
- 实体类编写
1 | public class Account implements Serializable { |
dao
接口编写
由于我们是使用Mybatis
框架,所以不需要编写其实现类,只需要写接口即可。
1 | public interface AccountDao { |
- 编写
Service
接口和实现类
1 | public interface AccountService { |
4.2 Spring框架代码的编写
4.2.1 搭建和测试Spring的开发环境
在项目中创建
applicationContext.xml
的配置文件,编写具体的配置信息。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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: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.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 开启注解扫描,要扫描的是service层和dao层的注解,要忽略web层注解,因为web层(Controller层)让SpringMVC框架去管理 -->
<context:component-scan base-package="cn.lizhi">
<!-- 配置要忽略的注解 -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
</beans>编写测试方法,进行测试
1
2
3
4
5
6
7
public void testSpring() {
// 获取Spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
AccountService accountService = ac.getBean("accountService", AccountService.class);
accountService.findAll();
}
4.3 Spring整合SpringMVC框架
在
web.xml
中配置DispatcherServlet
前端控制器1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<!-- 配置前端控制器:服务器启动必须加载,需要加载springmvc.xml配置文件 -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<!-- 配置初始化参数,创建完DispatcherServlet对象,加载springmvc.xml配置文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param> <!-- 服务器启动的时候,让DispatcherServlet对象创建 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>在
web.xml
中配置DispatcherServlet
过滤器解决中文乱码1
2
3
4
5
6
7
8
9
10
11
12
13<!-- 配置解决中文乱码的过滤器 -->
<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.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
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 扫描Controller(web层)的注解,别的不扫描 -->
<context:component-scan base-package="cn.lizhi">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 配置视图解析器 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- JSP文件所在的目录 -->
<property name="prefix" value="/WEB-INF/pages/"/> <!-- 文件的后缀名 -->
<property name="suffix" value=".jsp"/>
</bean>
<!-- 设置静态资源不过滤 -->
<mvc:resources location="/css/" mapping="/css/**"/>
<mvc:resources location="/images/" mapping="/images/**"/>
<mvc:resources location="/js/" mapping="/js/**"/> <!-- 开启对SpringMVC注解的支持 -->
<mvc:annotation-driven/>
</beans>测试
SpringMVC
的框架搭建是否成功编写
index.jsp
和list.jsp
前端页面1
2
3
4<!-- index页面 -->
<a href="/account/findAll" >查询所有</a>
<!-- list页面 -->
<h3>查询所有</h3>编写控制器方法
1
2
3
4
5
6
7
8
9"accountController") (
"/account") (
public class AccountController {
"/findAll") (
public String findAll() {
System.out.println("表面层:查询所有用户...");
return "list";
}
}结果:查询所有 。
Spring
整合SpringMVC
框架目的:在
Controller
层中能成功调用service
对象中的方法。如果想在服务器启动的时候,获取到
Spring
的容器,那么就需要在项目启动的时候就去加载applicationContext.xml
的配置文件。在web.xml
中配置ContextLoaderListener
监听器(该监听器只能加载WEB-INF
目录下的applicationContext.xml
的配置文件)。监听器的作用:监听器的作用是监听一些事件的发生从而进行一些操作,比如监听
ServletContext
,HttpSession
的创建,销毁,从而执行一些初始化加载配置文件的操作,当Web容器启动后,Spring
的监听器会启动监听,监听是否创建ServletContext
的对象,如果发生了创建ServletContext
对象这个事件(当web容器启动后一定会生成一个ServletContext
对象,所以监听事件一定会发生),ContextLoaderListener
类会实例化并且执行初始化方法,将Spring
的配置文件中配置的bean
注册到Spring
容器中,监听的操作是读取WEB-INF/applicationContext.xml
,但是我们可以在web.xml
中配置多个需要读取的配置文件,如下方所示,读取完成后所有的配置文件中的bean都会注册到spring容器中。1
2
3
4
5
6
7
8
9
10
11
12
13
14<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/config/application-context.xml
/WEB-INF/config/cache-context.xml
/WEB-INF/config/captcha-context.xml
/WEB-INF/config/jeecms/jeecore-context.xml
/WEB-INF/config/jeecms/jeecms-context.xml
/WEB-INF/config/shiro-context.xml
/WEB-INF/config/plug/**/*-context.xml
/WEB-INF/config/quartz-task.xml
/WEB-INF/config/zxw/zxw-context.xml
</param-value>
</context-param>web.xml
中对监听器的配置:1
2
3
4
5
6
7
8
9<!-- 配置Spring的监听器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 配置加载类路径的配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>在
Controller
中注入service
对象,调用service
对象的方法进行测试1
2
3
4
5
6
7
8
9
10
11
12
13
14"accountController") (
"/account") (
public class AccountController {
private AccountService accountService;
"/findAll") (
public String findAll() {
List<Account> accounts = accountService.findAll();
System.out.println("表现层:查询所有用户...");
return "list";
}
}输出结果:
业务层:查询所有用户
表现层:查询所有用户…
4.4 Spring整合Mybatis框架
4.4.1 搭建和测试MyBatis的环境
在
web
项目中编写SqlMapConfig.xml
的配置文件,编写核心配置文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<configuration>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///ssm?useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- 使用的是注解 -->
<mappers>
<!-- <mapper class="cn.itcast.dao.AccountDao"/> -->
<!-- 该包下所有的dao接口都可以使用 -->
<package name="cn.itcast.dao"/>
</mappers>
</configuration>在
AccountDao
接口的方法上添加注解,编写SQL
语句1
2
3
4
5
6
7
8public interface AccountDao {
@Insert("insert into account (name,money) values(#{name},#{money})")
public void saveAccount(Account account);
@Select("select * from account")
public List<Account> findAll();
}编写测试方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void saveAccount() throws IOException {
Account account = new Account();
account.setName("小黑");
account.setMoney(234d);
// 加载配置文件
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
// 创建工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
// 获取sqlSession对象
SqlSession session = factory.openSession();
// 常见代理对象
AccountDao accountDao = session.getMapper(AccountDao.class);
accountDao.saveAccount(account);
// 提交事务
session.commit();
// 释放资源
session.close();
is.close();
}
4.4.2 Spring整合MyBatis框架
目的:把SqlMapConfig.xml
配置文件中的内容配置到applicationContext.xml
配置文件中。由Spring
为我们进行对象的管理。从上面的测试文件中,可以看出我们需要将工厂对象,session
对象代理对象交由Spring
容器进行管理。即:把Mybatis
配置文件(SqlMapConfig.xml
)中内容配置到spring
配置文件中。
注意:
- 当我们使用的是代理
dao
的模式,dao
具体实现类由Mybatis
使用代理方式创建,此时Mybatis
配置文件不能删除。 - 整合
Spring
和Mybatis
时,Mybatis
创建的Mapper.xml
文件名必须和dao
接口文件名一致。
- 配置文件
1 | <!-- 配置C3P0的连接池对象 --> |
可以删除SqlSessionMap
配置文件。
- 给
dao
接口加上注解@Repository
- 在
service
中注入dao
对象,进行测试。 - 配置
Spring
框架声明式事务管理
配置事务管理器
1
2
3
4<!-- 配置Spring的声明式事务管理 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>配置事务通知
1
2
3
4
5
6
7<!-- 配置事务的通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>配置
AOP
增强1
2
3
4
5
6
7<!-- 配置 aop -->
<aop:config>
<!-- 配置切入点表达式 -->
<aop:pointcut expression="execution(* cn.lizhi.service.impl.*.*(..))" id="pt1"/>
<!-- 建立通知和切入点表达式的关系 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>
</aop:config>
五、附常用配置文件
5.1 web.xml
配置文件
1 |
|
5.2 pom.xml配置文件
1 |
|
5.3 SpringMVC配置文件
1 |
|
5.4 Spring配置文件 – applicationContext.xml
1 |
|
5.5 Mybatis全局配置文件 – mybatis-config.xml
1 |
|
5.6 Mybatis逆向工程的配置文件 – mbg.xml
1 |
|
5.7 日志配置文件 – log4j.properties
1 | Set root category priority to INFO and its only appender to CONSOLE. |
5.8 数据库连接池 – dbconfig.properties
1 | jdbc.driverClass=com.mysql.jdbc.Driver |