目录
一、SpringMVC简介
1. 什么是MVC
2. 什么是SpringMVC
3. SpringMVC的特点
二、SpringMVC入门案例
1. 开发环境
2. 创建maven工程
3. 配置web.xml
4. 创建SpringMVC的配置文件
5. 创建请求控制器
6. 测试Demo工程
7. 总结
三、@RequestMapping注解
1. @RequestMapping注解的功能
2. @RequestMapping注解的位置
3. @RequestMapping注解的value属性
4. @RequestMapping注解的method属性
5. @RequestMapping注解的params属性
6. @RequestMapping注解的headers属性
7. SpringMVC支持ant风格的路径
8. SpringMVC支持路径中的占位符(重点)
四、SpringMVC获取请求参数
1. 通过ServletAPI获取
2. 通过控制器方法的形参获取请求参数
3. @RequestParam
4. @RequestHeader
5. @CookieValue
6. 通过POJO获取请求参数
7. 解决获取请求参数的乱码问题
五、域对象共享数据
1. 使用ServletAPI向request域对象共享数据
2. 使用ModelAndView向request域对象共享数据
3.使用Model向request域对象共享数据
4. 使用map向request域对象共享数据
5. 使用ModelMap向request域对象共享数据
6. Model、ModelMap、Map的关系
7. 向session域共享数据
8. 向application域共享数据
六、SpringMVC的视图
1. ThymeleafView
2. 转发视图
3. 重定向视图
4. 视图控制器view-controller
七、RESTful
1. RESTful简介
2. RESTful的实现
3. HiddenHttpMethodFilter
4. CharacterEncodingFilter和HiddenHttpMethodFilter的配置顺序
八、RESTful案例
1. 功能清单
2. 准备工作
3. 功能实现
3.1. 访问首页
3.2. 查询所有员工信息
3.3. 删除功能
3.4. 添加功能
3.5. 修改功能
九、HttpMessageConverter
1. @RequestBody
2. RequestEntity
3. @ResponseBody
4. SpringMVC处理json
5. SpringMVC处理ajax
6. @RestController注解
7. ResponseEntity
十、文件上传和下载
1. 文件下载
2. 文件上传
十一、拦截器
1. 拦截器的配置
2. 拦截器的三个抽象方法
3. 多个拦截器的执行顺序
十二、异常处理器
1. 基于配置的异常处理
2. 基于注解的异常处理
十三、注解配置SpringMVC
1. 创建初始化类,代替web.xml
2. 创建SpringConfig配置类,代替spring的配置文件
3. 创建WebConfig配置类,代替SpringMVC的配置文件
4. 测试功能
十四、SpringMVC执行流程
1. SpringMVC常用组件
2. DispatcherServlet初始化过程
3. DispatcherServlet调用组件处理请求
4. SpringMVC的执行流程
注:三层架构分为表述层(或表示层)、业务逻辑层、数据访问层,表述层表示前台页面和后台Servlet
SpringMVC是一个基于 MVC模式的轻量级Web框架,是Spring框架的一个模块,和Spring可以直接整合使用。SpringMVC 是 Spring 为表述层开发提供的一整套完备的解决方案。在表述层框架历经 Strust、 WebWork、Strust2 等诸多产品的历代更迭之后,目前业界普遍选择了 SpringMVC 作为 Java EE 项目表述层开发的首选方案。SpringMVC代替了Servlet技术,它通过一套注解,让一个简单的Java类成为处理请求的控制器,而无须实现任何接口。
2.1. 通过idea的模板创建
说明:如果是手动创建的maven工程(没有勾选Create from archetype),需要自行添加web模块:首先要创建webapp目录,创建完webapp目录后,还需要添加一个web.xml,web.xml作为web工程的描述符,是web工程的一个入口配置文件,每当启动Tomcat服务器,首先要加载的文件就是web.xml,这个配置文件里面可以注册Servlet、过滤器、监听器。
2.2. 打包方式为war
2.3. 引入依赖
org.springframework
spring-webmvc
5.3.1
ch.qos.logback
logback-classic
1.2.3
javax.servlet
javax.servlet-api
3.1.0
provided
org.thymeleaf
thymeleaf-spring5
3.0.12.RELEASE
DispatcherServlet
org.springframework.web.servlet.DispatcherServlet
DispatcherServlet
/
DispatcherServlet
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:SpringMVC.xml
1
DispatcherServlet
/
我们都知道,web.xml中主要就是来注册Servlet、过滤器、监听器,那为什么使用SpringMVC的时候还需要配置web.xml呢?
因为SpringMVC是基于原生的Servlet,它为我们提供了一个封装处理请求过程的前端控制器DispatcherServlet,Servlet要想处理请求,必须在web.xml中注册。
可以这么理解,SpringMVC对Servlet进行了封装,其实本质就是一个Servlet,封装了Servlet之后就有了一个功能非常强大的前端控制器DispatcherServlet。原先浏览器发送的请求需要我们自己去写Servlet来处理,而对于SpringMVC,浏览器发送的请求都会被SpringMCV提供的前端控制器DispatcherServlet进行统一的处理。这个时候,它就会将请求和响应中很多的一些过程(执行步骤)进行封装,例如,获取请求参数、向域对象设置值、实现页面跳转、转发和重定向的时候,这个时候,DispatcherServlet就会对这些操作进行统一处理。
注册的原因:服务器上的每一项资源,都有一个路径与之对应,而浏览器不能直接访问一个类,因为并不是所有的类都能够接受请求和响应。JavaEE平台下的技术标准提供了Servlet,可以通过访问Servlet来实现,要想访问Servlet,必须要给它设置一个匹配路径,每当我们所访问的路径与我们当前所设置路径匹配的时候,那这个时候当前的请求就会被我们的Servlet来进行处理,所以我们要配置web.xml。
编写SpringMVC核心配置文件springMVC.xml,该文件和Spring配置文件写法一样。
说明:框架简单的说,其实就是配置文件+jar包,引入依赖之后,jar包有了,要想实现相对应功能,还需要去创建SpringMVC的配置文件
@Controller
public class DemoController {
}
/**
* @RequestMapping注解:处理请求和控制器方法之间的映射关系
* 当客户端发送的请求和@RequestMapping注解的value值匹配的时候,就会执行当前注解所标识的方法
* "/"表示匹配的是当前工程的应用上下文路径:localhost:8080/SpringMVC/
* "/"--> 跳转到/WEB-INF/templates/index.html
*/
@RequestMapping("/")
public String index() {
//返回视图名称,被视图解析器进行解析
//每当实现页面跳转的时候,如果视图名称符合条件的话,它就会被当前的视图解析器解析, 找到相对应的页面,实现页面跳转
return "index";
}
首页
首页
访问目标页面welcome.html
在请求控制器中创建处理请求的方法
@RequestMapping("/toTarget")
public String toWelcome() {
return "welcome";
}
关于绝对路径的说明:
以"/"开头的路径叫做绝对路径,而绝对路径又分为浏览器解析的和服务器解析的,浏览器解析的绝对路径会把"/"解析成:localhost:8080/
超链接中的绝对路径就是由浏览器解析的,这个时候的"/"表示的不是从应用上下文路径下访问,而是从项目的部署目录开始访问(Servlet容器:Tomcat的webapps目录下开始找,即从localhost:8080下访问)。
关于Application context
localhost:8080/projectName或者是localhost:8080就是我们平常所说的应用上下文,项目中的路径名都是相对于这个应用上下文来说的。
在idea下开发的时候,有时候我们可能需要让访问路径带上项目名,但是,idea默认是为每个项目单独配置tomcat的,要想让idea的应用上下文改变,只需将这个Application context改成你需要的项目名就行了。
/**
* @RequestMapping可以加在类上,给不同模块的控制器设置@RequestMapping,主要用于区分不同业务与层次的模块,实现路径识别与管理
* 如果不加,有可能在不同的控制器中出现相同的请求映射,即有多个方法上面的@RequestMapping,配了相同的请求地址
*
* 说明:在所有的控制器中,@RequestMapping所能匹配到的请求地址必须是唯一的,也就是说相同的请求地址,一定只有一个@RequestMapping与之匹配 */
@Controller
//@RequestMapping("/requestMappingController")
public class RequestMappingController {
/**
* RequestMappingController类(控制器)加上@RequestMapping("/requestMappingController")
* 此时请求映射所映射的请求的请求路径为:/requestMappingController/testMethod
*/
@RequestMapping("/testMethod")
public String testRequestMapping(){
return "success";
}
}
测试RequestMapping的value属性:"/testValuePropertyForRequestMapping"
测试RequestMapping的value属性:"/testValue"
/**
* @RequestMapping注解的value属性 通过客户端请求的请求地址来匹配请求映射
* 该请求映射能够匹配多个请求地址所对应的请求
*/
@RequestMapping(value = {"/testValuePropertyForRequestMapping", "/testValue"})
public String testValueProperty() {
return "success";
}
/**
* @RequestMapping注解的method属性
*/
@RequestMapping(value ="/testMethodForRequestMapping",method = {RequestMethod.GET})
public String testMethodProperty() {
return "success";
}
/**
* @RequestMapping注解的params属性
*/
@RequestMapping(value = "/testParamsPropertyForRequestMapping", method = RequestMethod.GET,
params = {"loginname", "password!=123456"})
public String testParams() {
return "success";
}
@RequestMapping(value = "/testHeadersPropertyForRequestMapping", method = RequestMethod.GET,
headers = {"Host=localhost:8080"})
public String testHeaders() {
return "success";
}
测试@RequestMapping可以匹配ant风格的路径:使用?
测试@RequestMapping可以匹配ant风格的路径:使用*
测试@RequestMapping可以匹配ant风格的路径:使用**
/**
* SpringMVC支持ant风格的路径
* ?,表示任意的单个字符; *,表示任意的0个或多个字符, **,表示任意的一层或多层目录
* @RequestMapping("/a?t/testAnt")
* @RequestMapping("/a*t/testAnt")
*/
@RequestMapping("/**/testAnt")
public String testAnt() {
return "success";
}
/**
* SpringMVC支持路径中的占位符,如果用的是占位符,在发送请求的时候,请求地址必须要和@RequestMapping()中的value进行匹配
* 也就是说,@RequestMapping中有占位符,那么在请求地址中也必须有这一层
* "/"在路径里面表示路径的分隔符,路径里面也是用"/"表示一层一层的目录
*/
@RequestMapping("/testPath/{id}/{name}")
public String testPath(@PathVariable("id") Integer id, @PathVariable("name") String name)
{
System.out.println("id:" + id + "name:" + name);
return "success";
}
测试使用ServletAPI获取请求参数
/**
* 这里可以直接使用HttpServletRequest对象
* 控制器方法是DispatcherServlet底层中调用的,它可以根据当前控制器方法的形参,为它们注入不同的值
* 当前形参是HttpServletRequest对象,它就可以将当前DispatcherServlet中所获得的表示当前请求request的对象赋值给形参request
* 形参位置的request表示当前请求
*/
@RequestMapping("/testServletAPI")
public String testServletAPI(HttpServletRequest request){
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println("username:"+username+",password:"+password);
return "success";
}
测试使用控制器形参获取请求参数
/**
*若请求参数中出现多个同名的请求参数(例如复选框勾选多个选项),在控制器方法相应的形参位置上声明字符串类型或字符串数组类型的参数来接收此请求参数
*若使用字符串数组类型的形参,此参数的数组中包含了每一个数据
*若使用字符串类型的形参,最终接收到的值为:同名的请求参数值之间使用逗号进行拼接,例如:"sing,swim,basketball"
*/
@RequestMapping("/testParam")
public String testParam(String username, String password,String hobby) {
System.out.println("username:" + username + ",password:" + password + ",hobby:" + hobby);
return "success";
}
创建session
/**
* HttpSession是一种保存少量信息至服务器端的一种技术,第一请求时,服务器会创建HttpSession,
* 我们可以在HttpSession对象中保存一些关于用户的状态信息,并将HttpSession的JSESSIONID以Cookie形式响应给浏览器,
* 第二次请求,浏览器会携带之前的JSESSIONID的Cookie,发送给服务器,服务器根据JSESSIONID获取对应的HttpSession对象.
*/
@RequestMapping("/testCreateSession")
public String getSession(HttpServletRequest request){
HttpSession session = request.getSession();
return "success";
}
/**
* 在此请求之前需要先通过上面请求映射为@RequestMapping("/testCreateSession")的控制器方法,创建HttpSession对象,
* 并将HttpSession的JSESSIONID以Cookie形式响应给浏览器,这里才能获取到JSESSIONID
*/
@RequestMapping("/testRequestParam")
public String testReqParam(@RequestParam(value = "username",required = false,defaultValue = "admin") String user_name,
String password, String hobby,
@RequestHeader("Host") String host,
@CookieValue("JSESSIONID") String JSESSIONID)
{
System.out.println("username:" + user_name + ",password:" + password + ",hobby:" + hobby);
System.out.println("host:" + host);
System.out.println("JSESSIONID:" + JSESSIONID);
return "success";
}
/**
* 通过POJO获取请求参数
*/
@RequestMapping("/testBean")
public String testBean(User user) {
System.out.println(user);
return "success";
}
解决获取请求参数的乱码问题,可以使用SpringMVC提供的编码过滤器CharacterEncodingFilter,但是必须在web.xml中进行注册
CharacterEncodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
UTF-8
forceResponseEncoding
true
CharacterEncodingFilter
/*
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String encoding = this.getEncoding();
if (encoding != null) {
if (this.isForceRequestEncoding() || request.getCharacterEncoding() == null) {
request.setCharacterEncoding(encoding);
}
if (this.isForceResponseEncoding()) {
response.setCharacterEncoding(encoding);
}
}
filterChain.doFilter(request, response);
}
关于乱码问题
在设置请求对象的编码的时候有一个前提条件:如果在此之前,已经获取了某一个请求参数,那我们设置的编码是没有任何作用的。由于在DispatcherServlet中就会获取客户端的请求参数,所以,我们在控制器方法中设置编码是不起作用的,因此,要在DispatcherServlet获取请求参数之前来设置编码。那有什么组件比Servlet更早执行呢?答案是过滤器 。Web服务器中三大组件:监听器、过滤器、Servlet,加载时间最早的是监听器,其次是过滤器,最后才是Servlet。对于过滤器,我们设置了过滤路径,只要我们访问的请求地址满足过滤路径,都会被过滤器进行过滤。
使用ServletAPI向request域对象共享数据
@RequestMapping("/testRequestScope")
public String testRequestScopeByServletAPI(HttpServletRequest request){
request.setAttribute("testRequestScope","RequestScope");
//这里就属于请求转发,转发到success.html页面
return "success";
}
/**
* 实现页面跳转,应该是由控制器方法来设置视图名称,视图名称由视图解析器解析之后,找到最终的页面
* 所以,在控制器方法中,针对视图部分只需要设置一个视图名称即可
*/
@RequestMapping("/testModuleAndView")
public ModelAndView testModelAndView(){
//ModelAndView有Model和View的功能,Model主要用于向请求域共享数据,View主要用于设置视图,实现页面跳转
ModelAndView mav = new ModelAndView();
//处理模型数据,即向请求域request共享数据
mav.addObject("testRequestScope","ModelAndView");
//设置视图,实现页面跳转
mav.setViewName("success");
//将封装了模型和视图的ModelAndView对象作为方法的返回值,返回给DispatcherServlet,DispatcherServlet去解析
return mav;
}
使用Model向request域对象共享数据
@RequestMapping("/testModel")
public String testModel(Model model) {
model.addAttribute("testRequestScope", "Model");
//都要在控制器方法中创建相对应的形参,而形参具有向request域中共享数据的功能
System.out.println(model.getClass().getName());
return "success";
}
使用Map向request域对象共享数据
@RequestMapping("/testMap")
public String testMap(Map map){
map.put("testRequestSocpe","Map");
//都要在控制器方法中创建相对应的形参,而形参具有向request域中共享数据的功能
System.out.println(map.getClass().getName());
return "success";
}
使用ModelMap向request域对象共享数据
public String testModelMap(ModelMap modelMap){
modelMap.addAttribute("testRequestSocpe","ModelMap");
//都要在控制器方法中创建相对应的形参,而形参具有向request域中共享数据的功能
System.out.println(modelMap.getClass().getName());
return "success";
}
public interface Model{}
public class ModelMap extends LinkedHashMap {}
public class ExtendedModelMap extends ModelMap implements Model {}
public class BindingAwareModelMap extends ExtendedModelMap {}
@RequestMapping("/testSession")
public String testSession(HttpSession session){
session.setAttribute("testSessionScope","HttpSession");
return "success";
}
使用ServletAPI向applications域对象共享数据
/**
* 使用ServletAPI向application域共享数据
*/
@RequestMapping("/testApplication")
public String testApplication(HttpSession session){
ServletContext servletContext = session.getServletContext();
servletContext.setAttribute("testApplicationScope","ServletContext");
return "success";
}
@RequestMapping("/testThymeleafView")
public String testThymeleafView(){
return "success";
}
@RequestMapping("/testInternalResourceView")
public String testInternalResourceView(){
return "forward:/testThymeleafView";
}
@RequestMapping("/testInternalResourceView")
public String testInternalResourceView(){
return "forward:/WEB-INF/templates/success.html";
}
以上写法是错误的,我们在使用html页面时,一般会结合Thymeleaf处理后端发来的数据,因此,多数情况必不可少的会使用"th:"有关的标签。因为在html页面中,只有"th:"所对应的属性中的内容才会被Thymeleaf解析。当视图名称以"forward:"为前缀,生成的不是ThymeleafView视图,而是InternetResourceView视图。如果是直接转发到该页面,那就不能被配置的Thymeleaf视图解析器解析到,也不能生成ThymeleafView,而页面又使用了Thymeleaf,所以,"th:"所对应的属性中的内容不会被解析,也就不能访问到该页面了。
如果使用的是jsp页面,那么就不需要考虑有关Thymeleaf的视图解析器了,正常使用即可。
@RequestMapping("/testRedirectView")
public String testRedirectView(){
return "redirect:/testThymeleafView";
}
说明:重定向视图在解析时,会先将redirect:前缀去掉,然后会判断剩余部分是否以/开头,若是则会自动拼接上下文路径
注意点与转发类似,但是重定向是两次请求,如果要重定向到WEB-INF目录下的页面,那就相当于从浏览器直接发送了一次访问WEB-INF目录的请求,而浏览器是无法直接访问WEB-INF目录下的页面的,所以只能通过重定向到其它方法,在由其它方法使用转发,经过ThymeleafView的视图解析器解析来到WEB-INF目录下。
@Controller
public class TestController {
@RequestMapping("/")
public String index() {
return "index";
}
}
说明:当SpringMVC中设置任何一个view-controller时,其它控制器中的请求映射将全部失效,此时需要在SpringMVC的核心配置文件中设置开启mvc注解驱动的标签:
当我们操作同一个资源,例如,操作的资源为用户信息,可以对其添加、删除、修改,无论是添加用户信息、删除用户信息还是修改用户信息,我们所操作的资源都是用户信息,用了RESTful后,既然操作的资源相同,那请求路径就应该相同。请求路径相同,怎么表示操作这些资源的方式3?具体说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。 它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源。 REST 风格提倡 URL 地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数,而是将要发送给服务器的数据作为 URL 地址的一部分,以保证整体风格的一致性。
操作
|
传统方式
|
REST 风格
|
查询操作
|
getUserById?id=1
|
user/1-->get请求方式
|
保存操作
|
saveUser
|
user-->post请求方式
|
删除操作
|
deleteUser?id=1
|
user/1-->delete请求方式
|
更新操作
|
updateUser
|
user-->put请求方式
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest requestToUse = request;
if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
if (ALLOWED_METHODS.contains(method)) {
requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter((ServletRequest)requestToUse, response);
}
● HiddenHttpMethodFilter过滤器需要在web.xml中注册
实现步骤:
HiddenHttpMethodFilter
org.springframework.web.filter.HiddenHttpMethodFilter
HiddenHttpMethodFilter
/*
2. 编写控制器方法
/**
* 使用RESTful模拟操作用户资源(增删改查)
* /user GET 查询所有用户信息
* /user/1 GET 根据用户id查询用户信息
* /user POST 添加用户信息
* /user/1 DELETE 删除用户信息
* /user PUT 修改用户信息
*
* 说明:RESTful是一种风格,REST风格提倡 URL地址使用统一的风格设计
* 无论是增加、删除、修改、还是查询我们访问的都是用户资源,既然访问的资源相同,那么访问路径也应该相同
* 根据请求方式的不同来实现对用户的不同操作
*/
@Controller
public class UserController {
@RequestMapping(value = "/user",method = RequestMethod.GET)
public String getAllUsers(){
System.out.println("查询所有用户信息");
return "success";
}
@RequestMapping(value = "/user/{id}",method = RequestMethod.GET)
public String getUserById(@PathVariable("id") String id) {
System.out.println("根据"+ id + "查询用户信息");
return "success";
}
@RequestMapping(value = "/user",method = RequestMethod.POST)
public String addUser(String userName,String password){
System.out.println("添加用户信息为:"+userName+","+password);
return "success";
}
@RequestMapping(value = "/user",method = RequestMethod.PUT)
public String updateUser(String userName,String password){
System.out.println("修改用户信息为:"+userName+","+password);
return "success";
}
}
3. 配置页面请求
查询所有用户信息
根据id查询用户信息
当有多个过滤器的时候,过滤器的执行顺序由
CharacterEncodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
UTF-8
forceResponseEncoding
true
CharacterEncodingFilter
/*
HiddenHttpMethodFilter
org.springframework.web.filter.HiddenHttpMethodFilter
HiddenHttpMethodFilter
/*
功能
|
URL 地址
|
请求方式
|
访问首页
|
/
|
GET
|
查询全部数据
|
/employee
|
GET
|
删除
|
/employee/2
|
DELETE
|
跳转到添加数据页面
|
/toAdd
|
GET
|
执行保存
|
/employee
|
POST
|
跳转到更新数据页面
|
/employee/2
|
GET
|
执行更新 √
|
/employee
|
PUT
|
package bean;
public class Employee {
private Integer id;
private String lastName;
private String email;
//1 male, 0 female
private Integer gender;
public Employee() {
}
public Employee(Integer id, String lastName, String email, Integer gender) {
super();
this.id = id;
this.lastName = lastName;
this.email = email;
this.gender = gender;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
}
package com.cgc.mvc.dao;
import bean.Employee;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@Repository
public class EmployeeDao {
private static Map employees;
private static Integer initId = 107;
static{
employees = new HashMap<>();
employees.put(101,new Employee(101, "张先生", "[email protected]", 1));
employees.put(102,new Employee(102, "王女士", "[email protected]", 0));
employees.put(103,new Employee(103, "李先生", "[email protected]", 1));
employees.put(104,new Employee(104, "赵先生", "[email protected]", 1));
employees.put(105,new Employee(105, "孙女士", "[email protected]", 0));
employees.put(106,new Employee(106, "李女士", "[email protected]", 0));
}
public Collection getAll(){
return employees.values();
}
public Employee get(Integer id){
return employees.get(id);
}
public void delete(Integer id){
employees.remove(id);
}
public void save(Employee employee){
if(employee.getId() == null){
employee.setId(initId++);
}
employees.put(employee.getId(),employee);
}
}
● 创建页面,在templates下创建index.html
首页
首页
查询员工信息
● 控制器方法
/**
* 注解+扫描的方式,当前注解所标识的类才会被作为组件进行管理
*/
@Controller
public class EmployController {
@Autowired
private EmployeeDao employeeDao;
@RequestMapping(value = "/employee",method = RequestMethod.GET)
public String getAllEmployees(Model model){
Collection employeeList = employeeDao.getAll();
model.addAttribute("employeeList",employeeList);
return "employees";
}
}
● 创建employees.html页面
员工信息
Employee Info
id
name
email
gender
options(add)
delete
update
删除某员工信息,需要使用超链接时,可以使用vue来阻止超链接跳转并且控制表单的提交,从而实现删除功能。通过超链接的点击事件【vue】来提交表单【post+隐藏域put/delete】,然后请求被filter拦截,再被前端控制器解析,解析过后来到Controller进行匹配。
① 删除超链接,绑定点击事件
● 在webapp文件夹下创建static目录和js子目录,用于存放静态资源,放入vue.js文件,并在index.html中引入vue.js
● 删除超链接
delete
● 创建处理delete请求方式的表单
● 通过vue处理点击事件
!--超链接控制表单提交,可以使用vue-->
● 在SpringMVC.xml中,增加配置项:开放对静态资源的访问
说明:加上
SpringMVC.xml中,如果没有
在Console(控制台)中,发现vue.js找不到
原因:通过请求路径 http://localhost:8080/SpringMVC/employee 查询所有员工信息,返回employees.html视图页面,由于在employees.html视图页面中引入了vue.js,浏览器会再次发送请求获取vue.js,而在SpringMVC工程中,由于配置了前端控制器DispatcherServlet,并且设置匹配的请求路径为"/",会拦截除JSP之外的所有请求,因此,访问静态资源vue.js,也会由SpringMVC的前端控制器DispatcherServlet来处理,而静态资源是不能交给SpringMVC的前端控制器DispatcherServlet来处理的(控制器中没有静态资源的请求映射),需要由默认的Servlet:DefaultServlet来处理静态资源。DispatcherServlet处理请求的方式是根据请求地址去控制器中找到相对应的请求映射,而控制器中是没有写访问静态资源的请求映射的,所以找不到。
关于在SpringMVC.xml中配置
在SpringMVC.xml中配置
一般Web应用服务器默认的Servlet名称是"default",因此DefaultServletHttpRequestHandler可以找到它。如果你所用的Web应用服务器的默认Servlet名称不是"default",则需要通过default-servlet-name属性显示指定:
② 控制器方法
@RequestMapping(value = "/employee/{id}",method = RequestMethod.DELETE)
public String deleteEmployee(@PathVariable("id") Integer id){
employeeDao.delete(id);
return "redirect:/employee";
}
① 跳转到添加数据页面
add employee
② 执行保存
@RequestMapping(value = "/employee",method = RequestMethod.POST)
public String addEmployee(Employee employee){
employeeDao.save(employee);
return "redirect:/employee";
}
① 跳转到更新数据页面
● 修改超链接
update
● 控制器方法
@RequestMapping(value = "/employee/{id}",method =RequestMethod.GET)
public String getEmployeeById(@PathVariable("id") Integer id,Model model){
Employee employee = employeeDao.get(id);
model.addAttribute("employee",employee);
return "employee_update";
}
● 在templates下创建employee_update.html
update Employee
② 执行更新
@RequestMapping(value = "/employee",method = RequestMethod.PUT)
public String updateEmployee(Employee employee){
employeeDao.save(employee);
return "redirect:/employee";
}
/**
* 使用@RequestBody注解实现将请求报文转化为java对象(这里转化为String类型对象)
*/
@RequestMapping(value = "/testRequestBody")
public String testRequestBody(@RequestBody String requestBody){ //请求体是字符串
System.out.println("requestBody:"+requestBody);
return "success";
}
@RequestMapping(value = "/testRequestEntity")
public String testRequestEnty(RequestEntity requestEntity){
//当前requestEntity表示整个请求报文的信息,泛型为String,表示以字符串的方式获取请求报文
System.out.println("请求头:"+requestEntity.getHeaders());
System.out.println("请求体:"+requestEntity.getBody());
return "success";
}
注:RequestEntity封装的是整个请求报文。
输出结果:
请求头:[host:"localhost:8080", connection:"keep-alive", content-length:"27", cache-control:"max-age=0", sec-ch-ua:""Google Chrome";v="113", "Chromium";v="113", "Not-A.Brand";v="24"", sec-ch-ua-mobile:"?0", sec-ch-ua-platform:""Windows"", upgrade-insecure-requests:"1", origin:"http://localhost:8080", user-agent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36", accept:"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", sec-fetch-site:"same-origin", sec-fetch-mode:"navigate", sec-fetch-user:"?1", sec-fetch-dest:"document", referer:"http://localhost:8080/SpringMVC/", accept-encoding:"gzip, deflate, br", accept-language:"zh-CN,zh;q=0.9", Content-Type:"application/x-www-form-urlencoded;charset=UTF-8"]
请求体:username=admin&password=123
概要说明:下面有关response,我们在学习完JavaWeb之后,我们可以清楚的感受到request的主要作用其实就是获取客户端请求的信息,有些信息是程序员需要看的,但是有的信息是计算机需要看的。程序员需要处理的就是类似于登录案例的username和password请求信息,而像浏览器版本信息之类的请求信息以及被高度封装的计算机处理,所以我们要处理的request信息并不多。但是,我们响应给浏览器的方式有很多,内容也十分复杂。比如,我们可以通过response的getwriter方法获取字符输出流将信息反馈输出到前端页面,这是一种响应,这种方法我们在学习Javaweb阶段一般不是响应页面显示时使用的,而是和异步请求AJAX(以及json)联合使用。另外一种响应就是response的重定向setRedirect以及request的转发。所以终点在于我们根据前台信息的逻辑判断反馈给浏览器的各种服务,response这块也就自然的变成了这部分学习的重点。
通过ServletAPI的Response对象响应浏览器数据
通过@ResponseBody响应浏览器数据
/**
* 在通过控制器处理请求时,要想对浏览器进行响应,有几种方式:
* 1.通过实现页面跳转(转发、重定向),响应给浏览器一个完整的页面
* 2.通过response.getWriter()获取一个PrintWriter对象,再通过其中的write()或print()响应浏览器
*/
@RequestMapping("/testResponse")
public void testResponseBody(HttpServletResponse response) throws IOException {
//操作响应体, 将print()中的内容:"测试Response",直接作为响应报文的响应体响应到浏览器
response.getWriter().print("测试Response");
}
/**
* 也就是说加了@ResponseBody注解,当前方法的返回值就是要响应到浏览器的数据
* 不加@ResponseBody, return "success", sucesss会作为视图名称,被视图解析器解析
* 加了@ResponseBody, return "success", sucesss表示的不再是视图名称,而是当前响应的响应体
*/
@RequestMapping("/testResponseBody")
@ResponseBody
public String testResponseBody(){
return "success";
}
com.fasterxml.jackson.core
jackson-databind
2.12.1
2)在SpringMVC的核心配置文件中开启mvc的注解驱动,此时在HandlerAdaptor中会自动装配一个消息转换器:MappingJackson2HttpMessageConverter,可以将响应到浏览器的Java对象转换为Json格式的字符串
3)在处理器方法上使用@ResponseBody注解进行标识
4)将Java对象直接作为控制器方法的返回值返回,就会自动转换为Json格式的字符串
/**
* 服务器是不能直接响应浏览器对象的,因为浏览器并不能识别我们响应的java对象,只能接收文本(浏览器不知道服务器用的是什么语言)
* 而http协议就是将浏览器端和服务器端做一个统一的规范,和浏览器的种类、编程语言的种类无关,所以,要按照请求报文和响应报文的格式发送请求和响应才行
*
* 当前既要保留Student对象中的各个数据,还要让它能够正常的响应到浏览器,怎么做?转换为json
* 这里要将控制器方法的返回值转换为json格式的,json是一种数据交互格式,区分json对象和数组:最外层是{}是json对象,最外层是[]是json数组
*/
@RequestMapping("/testResponseStudent")
@ResponseBody
public Student testResponseStudent(){
return new Student(1,"admin",20,"1001");
}
注:json是一种数据交互格式,xml也是一种数据交互格式。数据交互格式现在用的最多的是json,xml用的较多的是作为配置文件。
1)请求超链接
2)通过vue和axios处理点击事件
/**
* ajax:页面不刷新与服务器进行交互,所以在服务器中不能用转发和重定向
* 只能使用响应浏览器数据
*/
@RequestMapping("/testAjax")
@ResponseBody
public String testAjax(String username, String password){
System.out.println("username:"+username+",password:"+password);
return "hello,ajax";
}
补充:
● AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。
● AJAX 不是新的编程语言,而是一种使用现有标准的新方法。
● AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。
● AJAX 不需要任何浏览器插件,但需要用户允许 JavaScript 在浏览器上执行。
● XMLHttpRequest 只是实现 Ajax 的一种方式
/**
* 文件上传和下载本质上是一个文件复制的过程
*/
@Controller
public class FileUpLoadAndDownLoadController {
@RequestMapping("/testDownload")
public ResponseEntity testResponseEntity(HttpSession session) throws
IOException {
//获取ServletContext对象
ServletContext servletContext = session.getServletContext();
//获取服务器中文件的真实路径
String realPath = servletContext.getRealPath("/static/img/1.jpg");
System.out.println(realPath);
//创建输入流
InputStream is = new FileInputStream(realPath);
//创建字节数组, is.available():获取输入流所对应的文件所有的字节
byte[] bytes = new byte[is.available()];
//将输入流所对应的文件中的所有字节读到字节数组中
is.read(bytes);
//创建HttpHeaders对象设置响应头信息
MultiValueMap headers = new HttpHeaders();
//设置要下载方式以及下载文件的名字, attachment:以附件的方式来下载文件 filename:为下载的文件设置的默认的名字
//只有filename可以改,其它都是固定的
headers.add("Content-Disposition", "attachment;filename=1.jpg");
//设置响应状态码
HttpStatus statusCode = HttpStatus.OK;
//创建ResponseEntity对象, ResponseEntity可以自定义一个响应报文响应浏览器
//bytes存放了当前要下载的文件中所有的字节,也就是响应体
ResponseEntity responseEntity = new ResponseEntity<>(bytes, headers, statusCode);
//关闭输入流
is.close();
return responseEntity;
}
}
commons-fileupload
commons-fileupload
1.3.1
/**
* SpringMVC把当前上传的文件封装成了MultipartFile对象
*/
@RequestMapping("/testUp")
public String testUp(MultipartFile photo, HttpSession session) throws
IOException {
//获取上传的文件的文件名
String fileName = photo.getOriginalFilename();
//处理上传相同文件重名问题
String hzName = fileName.substring(fileName.lastIndexOf("."));
fileName = UUID.randomUUID().toString() + hzName;
//通过ServletContext获取服务器中photo目录的路径
ServletContext servletContext = session.getServletContext();
String photoPath = servletContext.getRealPath("photo");
File file = new File(photoPath);
if (!file.exists()) {
file.mkdir();
}
//文件最终上传的路径,上传也是文件复制,先读再写,写的时候需要知道上传到哪个目录,以及这个文件叫什么
String finalPath = photoPath + File.separator + fileName;
//实现上传功能
photo.transferTo(new File(finalPath));
return "success";
}
只是实现HandlerInterceptor接口,SpringMVC并不会将它识别为一个拦截器,SpringMVC的拦截器必须在SpringMVC的配置文件中进行配置
public class MyFirstInterceptor implements HandlerInterceptor {
/**
* 控制器方法执行之前执行, 对控制器方法进行拦截。返回值:false表示拦截,true表示放行
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor的preHandle方法已执行");
return true;
}
/**
* 控制器方法执行之后执行
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor的postHandle方法已执行");
}
/**
* 处理完视图和模型数据,渲染视图完毕之后执行
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor的afterCompletion方法已执行");
}
}
方式2:
@Component
public class MyFirstInterceptor implements HandlerInterceptor {
/**
* 控制器方法执行之前执行, 对控制器方法进行拦截。返回值:false表示拦截,true表示放行
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor的preHandle方法已执行");
return true;
}
/**
* 控制器方法执行之后执行
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor的postHandle方法已执行");
}
/**
* 处理完视图和模型数据,渲染视图完毕之后执行
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor的afterCompletion方法已执行");
}
}
-->
-->
以上两种配置方式都是对DispatcherServlet所处理的所有的请求进行拦截,无法设置拦截规则
方式3:
@Component
public class MyFirstInterceptor implements HandlerInterceptor {
/**
* 控制器方法执行之前执行, 对控制器方法进行拦截。返回值:false表示拦截,true表示放行
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor的preHandle方法已执行");
return true;
}
/**
* 控制器方法执行之后执行
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor的postHandle方法已执行");
}
/**
* 处理完视图和模型数据,渲染视图完毕之后执行
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor的afterCompletion方法已执行");
}
}
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
//执行拦截器的preHandle()
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//执行控制器方法
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
//执行拦截器的postHandle()
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
//执行拦截器的afterComplation()
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
3.2. 若某个拦截器的preHandle()返回了false,那这个拦截器的preHandle()和它之前的拦截器的preHandle()都会执行,postHandle()都不执行,preHandle()返回false的拦截器之前的拦截器的afterComplation()会执行
SpringMVC提供了一个处理控制器方法执行过程中出现异常的接口:HandlerExceptionResolver
SpringMVC中的异常处理器就是在控制器方法执行的过程中,如果出现了异常,就为它返回一个新的ModelAndView,跳转到指定页面。
HandlerExceptionResolver接口的实现类有:DefaultHandlerExceptionResolver和SimpleMappingExceptionResolver
DefaultHandlerExceptionResolver是SpringMVC默认使用的异常处理器,也就是说,在使用过程中出现的一些异常,SpringMVC都已经帮我们处理过了,而DefaultHandlerExceptionResolver中有一个doResolveException()方法。doResolveException()的作用就是:如果在控制器方法执行过程中出现了指定的异常,它就可以代替原来方法要返回的ModelAndView,返回一个新的ModelAndView,跳转到指定的页面,为我们显示异常信息。而ModelAndView就是用来处理模型数据和渲染视图的,所以有了ModelAndView就可以跳转到指定的页面了。
protected ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
try {
if (ex instanceof HttpRequestMethodNotSupportedException) {
return handleHttpRequestMethodNotSupported(
(HttpRequestMethodNotSupportedException) ex, request, response, handler);
}
else if (ex instanceof HttpMediaTypeNotSupportedException) {
return handleHttpMediaTypeNotSupported(
(HttpMediaTypeNotSupportedException) ex, request, response, handler);
}
else if (ex instanceof HttpMediaTypeNotAcceptableException) {
return handleHttpMediaTypeNotAcceptable(
(HttpMediaTypeNotAcceptableException) ex, request, response, handler);
}
else if (ex instanceof MissingPathVariableException) {
return handleMissingPathVariable(
(MissingPathVariableException) ex, request, response, handler);
}
else if (ex instanceof MissingServletRequestParameterException) {
return handleMissingServletRequestParameter(
(MissingServletRequestParameterException) ex, request, response, handler);
}
else if (ex instanceof ServletRequestBindingException) {
return handleServletRequestBindingException(
(ServletRequestBindingException) ex, request, response, handler);
}
else if (ex instanceof ConversionNotSupportedException) {
return handleConversionNotSupported(
(ConversionNotSupportedException) ex, request, response, handler);
}
else if (ex instanceof TypeMismatchException) {
return handleTypeMismatch(
(TypeMismatchException) ex, request, response, handler);
}
else if (ex instanceof HttpMessageNotReadableException) {
return handleHttpMessageNotReadable(
(HttpMessageNotReadableException) ex, request, response, handler);
}
else if (ex instanceof HttpMessageNotWritableException) {
return handleHttpMessageNotWritable(
(HttpMessageNotWritableException) ex, request, response, handler);
}
else if (ex instanceof MethodArgumentNotValidException) {
return handleMethodArgumentNotValidException(
(MethodArgumentNotValidException) ex, request, response, handler);
}
else if (ex instanceof MissingServletRequestPartException) {
return handleMissingServletRequestPartException(
(MissingServletRequestPartException) ex, request, response, handler);
}
else if (ex instanceof BindException) {
return handleBindException((BindException) ex, request, response, handler);
}
else if (ex instanceof NoHandlerFoundException) {
return handleNoHandlerFoundException(
(NoHandlerFoundException) ex, request, response, handler);
}
else if (ex instanceof AsyncRequestTimeoutException) {
return handleAsyncRequestTimeoutException(
(AsyncRequestTimeoutException) ex, request, response, handler);
}
}
catch (Exception handlerEx) {
if (logger.isWarnEnabled()) {
logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", handlerEx);
}
}
return null;
}
SpringMVC提供了自定义的异常处理器SimpleMappingExceptionResolver,让我们自定义异常处理。例如,如果当前控制器方法在执行的过程中出现了某些异常,我们就可以给它指定一个视图进行跳转。使用方式:
error
/**
* @ControllerAdvice注解,是用@Component来进行标识,所以@ControllerAdvice注解是@Component的一个扩展注解
* 所以具有将类标识为组件的功能
*
* @ControllerAdvice将当前类标识为异常处理的组件
*/
@ControllerAdvice
public class ExcepHandler {
/**
* @ExceptionHandler注解中可以添加参数,参数是某个异常类的class,代表这个方法专门处理该类异常,
* 如下此时注解的参数是ArithmeticException.class,NullPointerException.class,表示只有方法抛出ArithmeticException或者NullPointerException时,才会调用该方法
* 这个时候就会将@ExceptionHandler注解所标识的方法作为当前新的控制器方法来执行
*/
@ExceptionHandler(value = {ArithmeticException.class, NullPointerException.class})
public String testException(Exception ex, Model model) {
model.addAttribute("ex", ex);
return "error";
}
}
Title
出现异常
/**
* web工程的初始化类,用来代替web.xml
*/
public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 指定Spring的配置类
*/
@Override
protected Class>[] getRootConfigClasses() {
return new Class[0];
}
/**
* 指定SpringMVC的配置类
*/
@Override
protected Class>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}
/**
* 指定DispatcherServlet的映射规则,即url-pattern
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
/**
* 添加过滤器
*/
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceResponseEncoding(true);
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
return new Filter[]{characterEncodingFilter, hiddenHttpMethodFilter};
}
}
/**
*
* SSM整合之后,Spring的配置信息写在此类中
*/
@Configuration
public class SpringConfig {
}
/**
* 可以在一个类上通过注解@Configuration将它标识为配置类,这个时候它就可以代替Spring的配置文件
* 代替SpringMVC的配置文件,之前在SpringMVC的配置文件中配置的内容有:
* 1.扫描组件 2.视图解析器 3.view-controller 4.default-servlet-handler
* 5.mvc注解驱动 6.文件上传解析器 7.异常处理 8.拦截器
*
* @Configuration将当前类标识为一个配置类
* @ComponentScan扫描组件
* @EnableWebMvc mvc注解驱动
*/
@Configuration
@ComponentScan("com.cgc.mvc")
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
/**
*使用默认的Servlet处理静态资源
*/
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
/**
* 配置拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
MyInterceptor myInterceptor = new MyInterceptor();
registry.addInterceptor(myInterceptor).addPathPatterns("/**");
}
/**
* 配置视图控制 view-controller
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}
/**
* 配置文件上传解析器
*/
@Bean
public MultipartResolver multipartResolver() {
CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
return commonsMultipartResolver;
}
/**
* 配置异常处理器
*/
@Override
public void configureHandlerExceptionResolvers(List resolvers) {
SimpleMappingExceptionResolver exceptionResolver = new
SimpleMappingExceptionResolver();
Properties prop = new Properties();
prop.setProperty("java.lang.ArithmeticException", "error");
//设置异常映射
exceptionResolver.setExceptionMappings(prop);
//设置共享异常信息的键
exceptionResolver.setExceptionAttribute("ex");
resolvers.add(exceptionResolver);
}
/**
* 配置生成模板解析器
*/
@Bean
public ITemplateResolver templateResolver() {
WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
// ServletContextTemplateResolver需要一个ServletContext作为构造参数,可通过WebApplicationContext 的方法获得
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(webApplicationContext.getServletContext());
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setTemplateMode(TemplateMode.HTML);
return templateResolver;
}
/**
* 生成模板引擎并为模板引擎注入模板解析器
* templateEngine()方法是由Spring调用的,Spring在调用的时候会在IOC容器中找ITemplateResolver类型的bean,来为方法的参数赋值,相当于属性注入
* 所以,当前方法所使用的参数,必须符合自动装配的规则,也就是说,方法所能够使用的参数必须是当前SpringIOC容器中所拥有的bean,并且IOC容器中的bean能为此参数赋值
*/
@Bean
public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}
/**
* 生成视图解析器并未解析器注入模板引擎
*/
@Bean
public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}
}
@Controller
public class TestController {
@RequestMapping("/")
public String toIndex() {
return "index";
}
}
● 初始化WebApplicationContext
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();
try {
//对SpringMVC的IOC容器进行初始化
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': request parameters and headers will be " + value);
}
if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
// 创建WebApplicationContext
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
// 刷新WebApplicationContext
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
//将IOC容器在应用域共享
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
Class> contextClass = getContextClass();
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
//
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
//设置父容器
wac.setParent(parent);
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
configureAndRefreshWebApplicationContext(wac);
return wac;
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
//作用:请求处理完成后,返回的视图名称,要被视图解析器解析,找到相对应的视图渲染
initViewResolvers(context);
initFlashMapManager(context);
}
● processRequest()
FrameworkServlet重写HttpServlet中的service()和doXxx(),这些方法中调用了processRequest(request, response)
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
processRequest(request, response);
}
else {
super.service(request, response);
}
}
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
// 执行服务,处理请求和响应,doService()是一个抽象方法,在DispatcherServlet中进行了重写
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(request);
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
RequestPath requestPath = null;
if (this.parseRequestPath && !ServletRequestPathUtils.hasParsedRequestPath(request)) {
requestPath = ServletRequestPathUtils.parseAndCache(request);
}
try {
// 处理请求和响应
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
if (requestPath != null) {
ServletRequestPathUtils.clearParsedRequestPath(request);
}
}
}
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
/*
mappedHandler:调用链
包含handler、interceptorList、interceptorIndex
handler:浏览器发送的请求所匹配的控制器方法
interceptorList:处理控制器方法的所有拦截器集合
interceptorIndex:拦截器索引,控制拦截器afterCompletion()的执行
*/
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
// 根据控制器方法创建相应的处理器适配器,调用所对应的控制器方法
// 在这里会完成很多工作,比如,为控制器方法的形参赋值
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 调用拦截器的preHandle()
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// 由处理器适配器调用具体的控制器方法,最终获得ModelAndView对象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
// 调用拦截器的postHandle()
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 后续处理:处理模型数据和渲染视图
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
//处理模型数据和渲染视图
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
// Exception (if any) is already handled..
// 调用拦截器的afterCompletion()
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
16:27:00.926 [http-nio-8080-exec-9] DEBUG org.springframework.web.servlet.DispatcherServlet - GET "/SpringMVC/demo", parameters={}
16:27:00.926 [http-nio-8080-exec-9] WARN org.springframework.web.servlet.PageNotFound - No mapping for GET /SpringMVC/demo
16:27:00.927 [http-nio-8080-exec-9] DEBUG org.springframework.web.servlet.DispatcherServlet - Completed 404 NOT_FOUND
客户端:
如果有配置,则访问目标资源(一般为静态资源,如:JS,CSS,HTML),找不到客户端也会展示404错误
控制台:
16:32:31.104 [http-nio-8080-exec-4] DEBUG org.springframework.web.servlet.DispatcherServlet - GET "/SpringMVC/demo", parameters={}
16:32:31.112 [http-nio-8080-exec-4] DEBUG org.springframework.web.servlet.handler.SimpleUrlHandlerMapping - Mapped to org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler@1e297064
16:32:31.119 [http-nio-8080-exec-4] DEBUG org.springframework.web.servlet.DispatcherServlet - Completed 404 NOT_FOUND
客戶端:
● 存在,则执行下面的流程