Spring MVC
胜利的曙光就在眼前!
一、概述
1. 简介
- 1)spring MVC是基于spring的一个框架,实际上就是spring的一个模块,是做web开发的。(理解为servlet的一个升级)
- 2)web开发底层是servlet,框架在servlet基础上面加入一些功能,做web开发更方便
- 3)springMVC就是一个spring,spring是容器,ioc能够管理对象。springMVC能够创建对象,放入容器中(SpringMVC容器),springmvc容器中放的是控制器对象
- 4)我们要做的就是使用
@Controller创建控制器对象
,把对象放入springmvc容器中,把创建的对象作为控制器使用,这个控制器对象能接收用户的请求,显示处理结果,就当作一个servlet使用。@Controller注解创建的是一个普通类对象
,不是servlet,springmvc赋予了控制器对象一些额外的功能
- 5)web开发底层是servlet,springmvc中有一个对象是servlet:
DispatherServlet
(中央调度器)DispatherServlet
:负责接收用户的所有请求,用户把请求给了DispatherServlet
之后,DispatherServlet
把请求转发给Controller
对象,最后Controller
对象处理请求。
2. 入门
① 创建maven的web项目,加入依赖
-
1)依赖:
-
<!--servlet--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency> <!--springmvc,间接把spring依赖都加入到项目--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.3</version> </dependency>
-
② 注册springmvc框架的核心对象DispatcherServlet
-
1)声明springmvc核心对象
DispatcherServlet
(中央调度器)-
<!--声明springmvc核心对象DispatcherServlet 需要在tomcat服务器启动后,创建DispatcherServlet的实例 为什么? 因为DispatcherServlet在创建过程中,会同时创建springmvc容器对象 读取springmvc配置文件,把这个配置文件中的对象都创建好,当用户发起请求时就可以直接使用了 servlet会执行初始化方法init()。DispatcherServlet在init()中{ //创建容器,读取配置文件 WebApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml); //把容器对象放入ServletContext中 getServletContext().setAttribute(key, ctx); } --> <servlet> <servlet-name>springmvc</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>:表示tocmat启动后创建对象的顺序。整数值,值越小,tomcat创建对象的时间越早 大于等于0的整数,表示tomcat服务器启动时创建 若为负数,表示第一次访问时创建该对象 --> <load-on-startup>1</load-on-startup> </servlet>
-
springmvc创建容器对象时,读取的配置文件默认是:
/WEB-INF/<servlet-name>-servlet.xml
。这样不好,我们需要通过<init-param>
标签修改。
-
-
2)配置映射
-
<servlet-mapping> <servlet-name>springmvc</servlet-name> <!-- 使用框架时,url-pattern可以使用两种值 1.使用扩展名方式,语法:*.xxx,xxx是自定义扩展名。常用方式*.do *.action *.mvc等 2.使用"/" --> <url-pattern>*.do</url-pattern> </servlet-mapping>
-
③ 创建一个发起请求的页面index.jsp
-
<%@ 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> </body> </html>
④ 创建控制器类
-
1)在类上面加@Controller,创建对象,并放入到springmvc容器
-
/** * @Controller:作用是创建处理器对象,对象是放在springmvc容器中的 * * 和spring中的@Service、@Component一样 * * 能处理请求的都是控制器(处理器),MyController能处理请求,叫做后端控制器 */ @Controller public class MyController { ... }
-
-
2)在类中的方法上面加入@RequestMapping注解
-
@Controller public class MyController { /** * 处理用户提交的请求,springmvc中是使用方法来处理的 * 方法是自定义的,可以有多种返回值,多种参数,方法名称自定义 */ /** * 准备使用doSome()方法处理some.do请求 * @RequestMapping:请求映射,作用是把一个请求地址和一个方法绑定在一起。一个请求一个方法处理 * 属性: * 1.value:string类型,表示请求uri地址。唯一的。使用时推荐地址以“/”开头 * 位置: * 1.方法上 * 2.类上 * 说明:使用@RequestMapping修饰的方法叫做处理器方法或控制器方法 * 使用@RequestMapping修饰的方法可以处理请求,类似Servlet的doGet,doPost * * 返回值:ModelAndView,本次请求的处理结果 * Model:数据,请求处理完成后,要显示给用户的数据 * View:视图,比如jsp等.. */ @RequestMapping(value = "/some.do") public ModelAndView doSome(){ //doGet() --- service请求处理 //处理some。do的请求。相当于service调用处理完成了哦 ModelAndView mv = new ModelAndView(); //添加数据,框架在请求的最后把数据放到request作用域 //相当于request.setAttribute("msg", "欢迎使用springmvc!"); mv.addObject("msg", "欢迎使用springmvc!"); mv.addObject("fun", "执行的是doSome方法"); //指定视图,指定视图的完整路径 //框架对视图执行的是forward操作,request.getRequestDispather("/shos.jsp").forward(...) // mv.setViewName("/show.jsp"); // mv.setViewName("/WEB-INF/view/show.jsp"); //当配置了视图解析器后,可以使用逻辑名称(文件名),指定视图 //框架会使用视图解析器的前缀 + 逻辑名称 + 后缀 组成完整路径,--字符串连接操作 mv.setViewName("show"); //返回mv return mv; } }
-
⑤ 创建一个作为结果的jsp页面
-
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Show页面</title> </head> <body> <h3>show.jsp,从request数据获取数据</h3><br /> <h3>msg数据:${msg}</h3><br> <h3>fun数据:${fun}</h3><br> </body> </html>
⑥ 创建springmvc配置文件(spring配置文件一样)
-
1)声明组件扫描器,指定@Controller所在包
-
<?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="cn.zhangm.controller" /> </beans>
-
-
2)声明视图解析器。帮助处理视图的
-
<!--声明springmvc框架的视图解析器,帮助开发人员设置视图文件路径--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!--前缀:视图文件的路径--> <property name="prefix" value="/WEB-INF/view/" /> <!--后缀:视图文件的扩展名--> <property name="suffix" value=".jsp" /> </bean>
-
3. springmvc处理过程
①简易版
- 1)发起some.do请求
- 2)tomcat接收到(根据web.xml的url-parttern知道*.do的请求给DispatcherServlet)
- 3)DispatcherServlet(根据springmvc.xml配置文件知道 some.do 对应 doSome()方法)
- 4)DispatcherServlet把some.do转发给MyController.doSome()方法
- 5)框架执行doSome()方法,把得到的ModelAndView进行处理,转发到show.jsp
② 深层版
- 1)用户发起请求some.do
- 2)DispatcherServlet接收请求some.do,把请求转发给
处理器映射器
处理器映射器
:springmvc的对象,框架把实现了HandlerMapping接口的类叫做映射器(多个)处理器映射器作用
:- 根据请求,从springmvc容器对象中获取处理器对象
- 框架把找到的处理器对象放到一个
处理器执行链(HandlerExecutionChain)
的类保存HandlerExecutionChain
:类中保存着处理器对象(MyController)、项目中所有的拦截器对象List<HandlerInterceptor>
- 执行链对象返回给中央调度器
- 3)DispatcherServlet把(2)中的HandlerExecutionChain中的处理器对象交给了
处理器适配器对象(多个)
处理器适配器
:springmvc中的对象,需要实现HandlerAdapter接口
处理器适配器作用
:执行处理器方法(调用MyController.doSome()得到返回值ModelAndView),并把结果返回给中央调度器
- 4)DispatcherServlet把(3)中获取的ModelAndView交给
视图解析器对象
视图解析器
:springmvc的对象,需要实现ViewResoler接口
(可以有多个视图解析器)视图解析器作用
:组成视图的完整路径,使用前缀、后缀。并创建View
对象View
是一个接口,表示视图,在框架中jsp、html不是string表示,而是使用View和它的实现类表示视图InternalResourceView
视图类,表示jsp文件,视图解析器会创建它,这个对象里面有url属性,表示视图的路径:url = /WEB-INF/jsp/show.jsp
- 5)DispatcherServlet把(4)中创建的View对象获取到,调用View类自己的方法,把Model数据方到request作用域。执行视图对象的forward。请求结束。
4. 源码分析
① tomcat启动,容器创建过程
-
1)通过
load-on-start
标签指定的1,在服务器启动时创建DispatcherServlet对象,它的父类继承了HttpServlet,故在被创建时,执行init()
方法。在init()
方法中创建容器,读取配置文件-
WebApplicationContext ctx = new ClassPathXmlApplicationContext("springmvc.xml"); this.getServletContext().setAttribute(key, ctx);
-
上面创建容器的作用:
创建@Controller注解所在的类的对象
,创建MyController对象。这个对象放入springmvc容器,map类型的容器,例如map.put("myController", MyController对象)
。
-
② 请求的处理过程
- 1)执行servlet的service()
protected void service(HttpServletRequest request, HttpServletResponse response){...}
protected void doService(HttpServletRequest request, HttpServletResponse response){...}
DispatcherServlet.doDispatch(request, reponse){ 调用MyController的doSome()方法 }
二、注解开发
1. @RequestMapping定义请求规则
-
1)功能
-
@RequestMapping
注解定义在类上,表名该类的一个模块名称,访问该类中的方法的路径为:test/some.do
-
/** * @RequestMapping: * value:所有请求地址的公共部分,叫做模块名称 * 位置:类的上面 */ @Controller @RequestMapping("/test") public class MyController { @RequestMapping(value = "/some.do") public ModelAndView doSome(){ ModelAndView mv = new ModelAndView(); mv.addObject("msg", "欢迎使用springmvc!"); mv.addObject("fun", "执行的是doSome方法"); mv.setViewName("show"); return mv; } @RequestMapping(value = "/other.do") public ModelAndView doOther(){ ModelAndView mv = new ModelAndView(); mv.addObject("msg", "欢迎使用springmvc!"); mv.addObject("fun", "执行的是doOther方法"); mv.setViewName("show"); return mv; } }
-
-
2)属性
method
:指定url的请求方式,值:RequestMethod.GET
RequestMethod.POST
- …
2. 处理器方法参数
处理器方法可以包含以下四类参数,这些参数会在系统调用时由系统自动赋值
-
HttpServletRequest
-
HttpServletResponse
-
HttpSession
-
请求中所携带的请求参数
-
1)逐个参数接收
-
处理器方法的形参名和请求中的参数名必须一致,同名的请求参数给同名的形参,例如:
-
/** * 框架接收请求参数: * 1.使用request对象接收请求参数 * String strName = request.getParameter("name"); * String strAge = request.getParameter("age"); * 2.springmvc框架通过中央调度器调用MyController的doSome方法。调用时,按名称对应,把接收的参数赋值给形参 * doSome(strName,Integer.valueOf(strAge)),框架会提供类型转换的功能 * * 400状态码:客户端错误,表示提交请求参数过程中,发生了问题 */ @RequestMapping(value = "/receiveproperty.do") public ModelAndView doSome(String name, Integer age){ System.out.println("name: " + name); ModelAndView mv = new ModelAndView(); mv.addObject("mname", name); mv.addObject("myage", age); mv.setViewName("show"); return mv; }
-
<form action="receiveproperty.do" method="post"> 姓名:<input type="text" name="name"><br> 年龄:<input type="text" name="age"><br> <input type="submit" value="提交"> </form>
-
-
2)post请求中文乱码
-
配置过滤器,可以使用自定义的,也可以使用框架中提供的过滤器 CharacterEncodingFilter
-
<!--注册声明过滤器:解决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>
-
-
3)校正请求参数名
-
注解
@RequestParam
:处理请求参数名和处理器形参名不一样 -
/** * 请求参数名和形参名不一致 * 使用注解@RequestParam: * 属性:1.value 请求中的参数名称 * 2.required:boolean类型,默认true,表示请求中必须含有此参数 * 位置:处理器方法的形参前 */ @RequestMapping(value = "/receiveproperty2.do") public ModelAndView doSome2(@RequestParam(value = "rname",required = false) String name, @RequestParam(value = "rage",required = false)Integer age){ System.out.println("name: " + name); System.out.println("age: " + age); ModelAndView mv = new ModelAndView(); mv.addObject("rname", name); mv.addObject("rage", age); mv.setViewName("show"); return mv; }
-
属性:
value
:请求中的参数名称required
:boolean类型,默认true,表示请求中必须含有此参数
-
-
4)对象参数接收
-
要求:处理器方法形参是java对象,这个对象的属性名和请求参数中参数名一样,框架会创建形参java对象,给属性赋值,调用set方法
-
@RequestMapping(value = "/receiveproperty3.do") public ModelAndView doSome3(Student mystudent){ System.out.println("name: " + mystudent.getName() + "," + "age:" + mystudent.getAge()); ModelAndView mv = new ModelAndView(); mv.addObject("name", mystudent.getName()); mv.addObject("age", mystudent.getAge()); mv.addObject("mystudent", mystudent); mv.setViewName("show"); return mv; }
-
3. 处理器方法返回值
返回值有四大类
ModelAndView
String
void
Object对象
① ModelAndView
- 1)若处理器方法处理完后,需要跳转到其它资源,且又要在跳转的资源间传递数据,此时
处理器方法返回 ModelAndView 比较好。当然,若要返回 ModelAndView,则处理器方法中
需要定义 ModelAndView 对象。 - 2)在使用时,若该处理器方法只是进行跳转而不传递数据,或只是传递数据而并不向任何
资源跳转(如对页面的 Ajax 异步响应),此时若返回 ModelAndView,则将总是有一部分多
余:要么 Model 多余,要么 View 多余。即此时返回 ModelAndView 将不合适。 - 3)方法
mv.addObject(key, value)
:向request域添加共享数据mv.setViewName(请求转发路径)
:请求转发
② String
-
1)处理器方法返回的字符串可以指定逻辑视图名,通过视图解析器解析可以将其转换为物理视图地址
-
2)若返回值为完整路径,则不能配置视图解析器
-
3)示例
-
<!--声明springmvc框架的视图解析器,帮助开发人员设置视图文件路径--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!--前缀:视图文件的路径--> <property name="prefix" value="/WEB-INF/view/" /> <!--后缀:视图文件的扩展名--> <property name="suffix" value=".jsp" /> </bean>
-
/** * 处理器方法返回String:表示视图逻辑名称,需要配置视图解析器 * */ @RequestMapping(value = "return-view.do") public String doReturnView(HttpServletRequest request,String name, Integer age){ System.out.println("name: " + name + "," + "age: " + age); //可以自己手工添加数据到request域 request.setAttribute("myname", name); request.setAttribute("myage", age); //show:逻辑视图名称,项目中配置了视图解析器 return "show"; }
-
③ void
-
1)对于处理器方法返回 void 的应用场景,AJAX 响应.
-
2)ajax的使用步骤
-
① 导入依赖,加入jquery库
-
<!--jackson--> <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>
-
-
② 前端发起ajax请求
-
<html> <head> <title>Title</title> <script type="text/javascript" src="js/jquery.min.js"></script> <script type="text/javascript"> $(function () { $("#btn").click(function () { $.ajax({ url:"returnVoid-ajax.do", data:{ name:"zhangsan", age:20 }, type: "post", dataType: "json", success:function (data) { //data从服务器端返回的是字符串:{"name":"zhangsan","age":20} //jquery会把json字符串转化为json对象,赋值给data形参 alert(data.name + " " + data.age); } }); }); }); </script> </head> <body> <button id="btn">发起ajax请求</button> </body> </html>
-
-
③ 后端响应ajax
-
//处理器方法返回void,响应ajax请求 @RequestMapping(value = "returnVoid-ajax.do") public void doReturnViewAjax(HttpServletResponse response, String name, Integer age) throws IOException { System.out.println("name: " + name + "," + "age: " + age); //处理ajax,使用json做数据格式 //加入service调用完成了,用Student表示处理结果 Student student = new Student(); student.setName(name); student.setAge(age); //把结果的对象转为json数据 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.write(json); pw.flush(); pw.close(); }
-
-
④ Object
(一)步骤与原理
-
1)实现步骤:
-
现在做ajax,主要使用json的数据格式。故加入jackson依赖。springmvc默认使用jackson
-
在springmvc配置文件加入
<mvc:annotation-driven>
注解驱动。作用:把java对象转为json字符串
-
<!--注解驱动--> <mvc:annotation-driven />
-
-
在处理器方法上面加
@ResponseBody
注解。作用:输出数据(通过HttpServletResponse输出流)
-
-
2)springmvc处理器方法返回Object对象,可以转为json输出到浏览器,响应ajax的内部原理:
-
①
<mvc:annotation-driven>
注解驱动- (1) 注解驱动实现的功能是:完成java对象到json、xml、text、二进制等数据格式的转换
- (2)
<mvc:annotation-driven>
标签加入到springmvc配置文件后,会自动创建HttpMessageConverter接口的7个实现类。包括:MappingJackson2HttpMessageConverter
(使用jackson工具库中的ObjectMapper实现java对象转换为json字符串的) - (3)
HttpMessageConverter接口
:消息转换器。功能:定义了java对象转为json、xml等数据格式的方法。- 这个接口有很多实现类,这些实现类完成java到json、xml等数据格式的转换。
- 若不加注解驱动,只有四个实现类;加上驱动有8个实现类
- (4) 下面两个方法是控制器类把结果输出给浏览器时使用的:
boolean canRead(Class<?> var1, @Nullable MediaType var2);
void write(T var1, @Nullable MediaType var2, HttpOutputMessage var3) throws IOException, HttpMessageNotWritableException;
- canRead作用:检查处理器方法的返回值,能不能转化为var2表示的数据格式,能返回为true
- write:把处理器方法的返回值对象,调用jackson中的ObjectMapper转化为json字符串
-
②
@ResponseBody注解
-
作用:放在处理器方法上,通过HttpServletResponse输出数据,响应ajax请求
-
做的事情:
-
PrintWriter pw = response.getWriter(); pw.write(json); pw.flush(); pw.close();
-
-
(二)代码示例
-
1)处理器方法返回Student,通过框架转为json,响应ajax请求
-
/** * @ResponseBody: * 作用:把处理器方法返回对象转为json后,通过HttpServletResponse输出给浏览器 * 位置:方法上。和其他注解没有顺序要求 * * 返回对象处理流程 * 1.框架会把返回值Student类型,调用ArrayList<HttpMessageConverter>中的每个类的canWrite()方法,检查HttpMessageConverter接口的实现类哪个能处理Student数据的类型,(MappingJackson2HttpMessageConverter可以) * 2.框架会调用实现类的write(),MappingJackson2HttpMessageConverter的write()方法,把student对象转为json,调用的是Jackson的ObjectMapper实现转为json。类型是:contextType:application/json;charset=utf-8 * 3.框架会调用@ResponseBody把(2)的结果数据输出到浏览器,ajax请求完成 */ @RequestMapping(value="/returnStudentJson.do") @ResponseBody public Student doStudentJsonObject(String name, Integer age){ //调用service获取请求结果数据,假如调用方法完成了 Student student = new Student(); student.setName(name); student.setAge(age); //student对象会被框架转为json return student; }
-
-
2)处理器方法返回List集合,例如:
list<Student>
-
/** * 处理器方法返回List集合,例如:list<Student > * * 处理方式与返回Student一样 */ @RequestMapping(value="/returnStudentJsonArray.do") @ResponseBody public List<Student> doStudentJsonObjectArray(String name, Integer age){ //调用service获取请求结果数据,假如调用方法完成了 List<Student> list = new ArrayList<>(); Student student = new Student(); student.setName(name); student.setAge(age); Student student2 = new Student(); student2.setName("花花"); student2.setAge(15); list.add(student); list.add(student2); //list对象会被框架转为json数组:[{},{}] return list; }
-
-
3)返回字符串
String对象
-
处理器方法返回String:String表示数据,不是视图。
-
如何区分返回值类型String是数据还是视图?
- 答:看是否有注解
@ResponseBody
- 有该注解:数据
- 没有该注解:视图
- 答:看是否有注解
-
默认使用“text/plain;charset=ISO-8859-1”作为contextType,导致中文乱码
- 解决方案:给
@RequestMapping
增添一个属性produces
,指定contextType
- 解决方案:给
-
示例:
-
/** * 返回对象处理流程 * 1.框架会把返回值String类型,调用ArrayList<HttpMessageConverter>中的每个类的canWrite()方法,检查HttpMessageConverter接口的实现类能处理String数据的类型,(StringHttpMessageConverter可以) * 2.框架会调用实现类的write(),StringHttpMessageConverter的write()方法,把字符串按照指定编码处理:text/plain;charset=ISO-8859-1(默认) * 3.框架会调用@ResponseBody把(2)的结果数据输出到浏览器,ajax请求完成 */ @RequestMapping(value="/returnStringData.do", produces = "text/plain;charset=utf-8") @ResponseBody public String doStringData(String name, Integer age){ return "hello springmvc 返回Sring对象,表示数据"; }
-
-
4. 解读url-pattern
① 分析请求
-
1)各种请求
http://localhost:8080/springmvc05_url_pattern/index.jsp
:tomcat处理http://localhost:8080/springmvc05_url_pattern/js/jquery.min.js
:tomcat处理http://localhost:8080/springmvc05_url_pattern/images/1.jpg
:tomcat处理http://localhost:8080/springmvc05_url_pattern/some.do
:DispatcherServlet处理- 因为配置了
<url-pattern>*.do</url-pattern>
- 因为配置了
<http://localhost:8080/springmvc05_url_pattern/html/test.html>
:tomcat处理
-
2)tomcat本身能处理静态资源的访问,像html、图片、jsp文件都是静态资源
-
toncat的web.xml文件配置了servlet
-
<!-- The default servlet for all web applications, that serves static --> <!-- resources. It processes all requests that are not mapped to other --> <!-- servlets with servlet mappings (defined either here or in your own --> <!-- web.xml file). --> <servlet> <servlet-name>default</servlet-name> <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> <init-param> <param-name>debug</param-name> <param-value>0</param-value> </init-param> <init-param> <param-name>listings</param-name> <param-value>false</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>default</servlet-name> <!--/表示静态资源和未映射的请求都由defaultServlet处理--> <url-pattern>/</url-pattern> </servlet-mapping>
-
defaultServlet的作用(参见上述注释):
- 处理静态资源
- 处理未映射到其他servlet的请求
-
-
3)当中央调度器的
url-pattern
配置为/
时,会出现静态资源无法访问的问题。-
<servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
-
因为,它会替代tomcat的defaultServlet的映射,导致所有静态资源都给中央调度器处理。默认情况下中央调度器没有处理静态资源的能力,没有控制器对象能够处理静态资源的访问。所以静态资源(html、图片、jsp…)都是404
-
动态资源可以访问,因为有MyContriller控制器对象
-
-
4)如何处理这个问题,请往下看
② < mvc:default-servlet-handler>
-
1)第一个处理静态资源的方式:
- 在springmvc配置文件加入:
<mvc:default-servlet-handler>
标签 - 原理:在加入该标签,框架会创建控制器对象:DefaultServletHttpRequestHandler。这个对象可以把接收到的请求转发给tomcat的default这个servlet处理。
- 在springmvc配置文件加入:
-
2)注意:
default-servlet-handler
和@RequestMapping
有冲突,需要加入annotation-driven
解决问题default-servlet-handler
标签将所有的请求交给tomcat默认的servlet处理了,而我们配置@RequestMapping
注解是期望该注解的方法交给中央调度器处理
-
3)示例
-
<!--default-servlet-handler和@RequestMapping有冲突,需要加入annotation-driven解决问题--> <mvc:annotation-driven /> <mvc:default-servlet-handler />
-
③ <mvc:resources>
-
1)第二种处理静态资源的方式(常用)
- 在springmvc配置文件中加入:
<mvc:resources mapping="" location="" />
标签 - 原理:mvc:resources加入后,会创建一个ResourceHttpRequestHandler处理器对象,让这个对象处理静态资源的访问,不依赖tomcat服务器。
mapping
:访问静态资源的uri地址,使用通配符 **- 例如:/images/**:表示images目录下的任意字符,如,images/1.jpg、images/user/logo.jpg
location
:静态资源在项目中的位置
- 在springmvc配置文件中加入:
-
2)示例
-
<mvc:resources mapping="/images/**" location="/images/" /> <mvc:resources mapping="/html/**" location="/html/" /> <mvc:resources mapping="/js/**" location="/js/" /> <!--mvc:resources和@RequestMapping有一定的冲突--> <mvc:annotation-driven />
-
-
3)开发常用
-
在开发中通常将静态资源放在一个static目录下,这样只需要一条语句就可以指定静态资源了
-
<!--mvc:resources和@RequestMapping有一定的冲突--> <mvc:annotation-driven /> <!--使用一条语句指定多个静态资源的访问--> <mvc:resources mapping="/static/**" location="/static/" />
-
三、路径问题
1. 路径分类
- 1)绝对地址:带有协议名称的地址
- 如:
http://www.baidu.com
、ftp://202.122.23.1
- 如:
- 2)相对地址:没有协议开头的地址
- 如:
user/some.do
、/user/some.do
- 相对地址不能独立使用,必须由一个参考地址。通过参考地址+相对地址本身才能指定资源
- 如:
- 3)参考地址
2. 加不加 / 问题
① 在页面中的访问地址不加 “/” 情况一
- 访问的是:
http://localhost:8080/ch06-path/index.jsp
- 路径:
http://localhost:8080/ch06-path/
- 资源:
index.jsp
- 在index.jsp发起
user/some.do
的请求时,访问地址变成:http://localhost:8080/ch06-path/user/some.do
- 当地址没有斜杠开头,例如
user/some.do
,当点击连接时,访问地址是当前页面的地址 + 连接地址- 例如:
http://localhost:8080/ch06-path/
+user/some.do
- 例如:
② 在页面中的访问地址不加 “/” 情况二
-
在index.jsp 访问 user/some.do,返回后现在的地址
http://localhost:8080/ch06-path/user/some.do
-
路径:
http://localhost:8080/ch06-path/user/
-
资源:
some.do
-
再一次在index.jsp访问
user/some.do
后,地址变为http://localhost:8080/ch06-path/user/user/some.do
-
如何解决这个问题:
-
1-加入
${pageContext.request.contextPath}
El表达式 -
2-加入一个base标签,是html语言中的标签。表示当前页面访问地址的基地址。页面中所有没有"/"开头的地址,都是以base标签中的地址为参考地址
-
例如:
-
<html> <head> <title></title> <!--在这里--> <base href="http://localohos:8080/ch06-path/" /> </head> <body> <!-- 请求地址为: http://localohos:8080/ch06-path/ + user/some.do --> <p><a href="user/some.do">发起user/some.do请求</a></p> </body> </html>
-
jsp中可以动态获取
-
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <% String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/"; %> <html> <head> <title></title> <!--在这里--> <base href="<%=basePath%>" /> </head> <body> <p><a href="user/some.do">发起user/some.do请求</a></p> </body> </html>
-
-
③ 在页面中的访问地址不加"/"
- 访问的是:
http://localhost:8080/ch06-path/index.jsp
- 路径:
http://localhost:8080/ch06-path/
- 资源:
index.jsp
- 点击
/user/some.do
,访问地址变为:http://localhost:8080/user/some.do
- 参考地址是:你的服务器地址
http://localhost:8080/
- 如果想要正确访问资源:在页面中的访问地址添加上项目的虚拟目录即可,如:
/ch06-path/user/some.do
④ 总结
- 页面中的访问地址不加"/":参考地址为
当前页面(资源)的路径
- 页面中的访问地址加"/":参考地址为
服务器地址
四、SSM整合
五、核心技术
1. 请求转发和重定向
-
1)请求转发
-
语法:
setViewName("forward:视图文件完整路径");
-
forward特点:不和视图解析器一起使用,就当项目没有视图解析器
-
示例:
-
@RequestMapping("/doForward.do") public ModelAndView doForward(){ ModelAndView mv = new ModelAndView(); mv.addObject("msg", "springmvc方法返回值ModelAndView实现forward"); //显示转发 mv.setViewName("forward:/WEB-INF/jsp/result.jsp"); return mv; }
-
-
2)重定向
-
语法:
setViewName("redirect:视图文件完整路径");
-
redirect特点:
- 不和视图解析器一起使用,就当项目没有视图解析器
- 不能访问WEB-INF下的资源
- 框架会把Model的简单类型数据,转为String使用,作为重定向页面的get请求参数
-
示例:
-
/** * 框架对重定向的操作: * 1.框架会把Model的简单类型数据,转为String使用,作为result.jsp的get请求参数 * 目的是在doRedirect.do和result.jsp两次请求直接可以传递数据 * 2.在result.jsp页面可以使用参数集合对象${param}获取请求参数值:${param.name} */ @RequestMapping("/doRedirect.do") public ModelAndView doRedirect(String name, Integer age){ ModelAndView mv = new ModelAndView(); //数据放入request作用域 mv.addObject("name", name); mv.addObject("age", age); //重定向:重定向不能访问WEB-INF下的资源 mv.setViewName("redirect:/result.jsp"); //http://localhost:8080/springmvc07_forward_redirect/result.jsp?name=lisi&age=18 return mv; }
-
2. 异常处理
- 1)异常处理:springmvc采用统一的、全局的异常处理。
- 把controller中所有异常处理都集中到一个地方。采用的是aop思想,把业务逻辑和异常处理分开,解耦合
- 2)两个注解
@ExceptionHandler
@ControllerAdvice
异常处理步骤
-
1)新建maven项目、导入依赖
-
2)在controller中抛出异常
-
@RequestMapping("/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("name", name); mv.addObject("age", age); mv.setViewName("result"); return mv; }
-
-
3)创建一个普通类,作为全局异常处理
-
在类上加注解
@ControllerAdvice
-
在类中定义方法,方法上加
@ExcpetionHandler
-
/** * @ControllerAdvice:控制器增强(也就是说给控制器类增强功能 -- 异常处理功能) * 位置:类上 * 特点:必须让框架直到这个注解所在包名,需要在springmvc配置文件声明组件扫描器 */ @ControllerAdvice public class GolbalExceptionHandler { //定义方法,处理发生的异常 /** * 处理异常的方法和控制器方法的定义一样,可以有多个参数,可以有ModelAndView... * * 形参: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; } //处理ageException @ExceptionHandler(value = AgeException.class) public ModelAndView doAgeException(Exception exception){ ModelAndView mv = new ModelAndView(); mv.addObject("msg", "你的年龄不能大于80"); mv.addObject("ex", exception); mv.setViewName("ageError"); return mv; } //处理其他异常:NameException、AgeException以外的不知类型的异常 @ExceptionHandler public ModelAndView doOtherException(Exception exception){ //处理其他异常 ModelAndView mv = new ModelAndView(); mv.addObject("msg", "未知异常"); mv.addObject("ex", exception); mv.setViewName("defaultError"); return mv; } }
-
-
4)创建处理异常的视图页面
-
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>NameError</title> </head> <body> nameError.jsp <br/> 提示信息:${msg} <br/> 系统消息:${ex.message} <br/> </body> </html>
-
-
5)创建springmvc配置文件
- 组件扫描器,扫描@Controller注解
- 组件扫描器,扫描@ControllerAdvice
- 声明注解驱动
3. 拦截器
① 简介
- 1)拦截器:拦截器是springmvc中的一种,需要实现HandlerInterceptor接口
- 拦截器和过滤器类似,功能方向侧重点不同
- 过滤器:用来过滤请求参数,设置字符集编码等
- 拦截器:拦截用户的请求,对请求做判断处理
- 2)拦截器特点
- 拦截器是全局的,可以对多个controller做拦截
- 一个项目中可以有多个拦截器,他们在一起拦截用户的请求
- 常用在用户的登录处理、权限检查、记录日志
- 3)拦截器的执行时间
- 在请求处理之前,也就是controller类中的方法执行之前先被拦截
- 在控制器方法执行之后,也会执行拦截器
- 在请求处理完成后,也会执行拦截器
② 实现步骤
-
1)定义类实现HandlerInterceptor接口
-
//拦截器类,拦截用户的请求 public class MyInterceptor implements HandlerInterceptor { @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)在springmvc配置文件中,声明拦截器
-
<!--声明拦截器--> <mvc:interceptors> <mvc:interceptor> <!--指定拦截的请求uri地址 path:就是uri地址,可以使用通配符 **(任意字符,多级目录和文件) --> <mvc:mapping path="/user/**"/> <!--声明拦截器对象--> <bean class="cn.zhangm.handler.MyInterceptor" /> </mvc:interceptor> </mvc:interceptors>
-
③ 拦截器的三个方法
-
1)
preHandle()
方法-
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("拦截器MyInterceptor的preHandle()方法"); return true; }
-
参数:
Object handler
:被拦截的控制器对象
-
返回值boolean
- true:通过了拦截器的验证,可以执行处理器方法
- false:请求没有通过拦截器的验证,请求没被处理
-
特点
- 该方法在控制器方法(MyController的方法)之前先执行的
- 在这个方法中,可以获取请求的信息,验证请求是否符合要求。
-
-
2)
postHandle()
方法-
@Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("拦截器MyInterceptor的postHandle()方法"); }
-
参数:
Object handler
:被拦截的控制器对象ModelAndView modelAndView
:处理器方法的返回值
-
特点
- 在处理器方法执行之后执行
- 能够获取到处理器方法的返回值ModelAndView,可以修改其数据和视图,可以影响最后执行结果
- 主要是对原来的执行结果做二次修正
-
-
3)
postHandle()
方法-
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("拦截器MyInterceptor的afterCompletion()方法"); }
-
参数:
Object handler
:被拦截的控制器对象Exception ex
:程序中发生的异常
-
特点
- 请求处理完成后执行。框架中规则当视图处理完成后(对视图执行了forward),就认为请求完成了
- 一般做资源回收,程序在请求过程中创建的对象,可以在这里清除
-
④ 多个拦截器执行顺序
-
1)多个拦截器在框架中保存在一个ArrayList集合,按照声明的先后顺序放入ArrayList
-
<mvc:interceptors> <!--11111拦截器--> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="cn.zhangm.handler.MyInterceptor" /> </mvc:interceptor> <!--22222拦截器--> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="cn.zhangm.handler.MyInterceptor2" /> </mvc:interceptor> </mvc:interceptors>
-
-
2)执行顺序
(拦截器1:返回值true;拦截器2:返回值true)
-
11111-拦截器MyInterceptor的preHandle()方法 22222-拦截器MyInterceptor的preHandle()方法 =====执行MyController的doSome()方法===== 22222-拦截器MyInterceptor的postHandle()方法 11111-拦截器MyInterceptor的postHandle()方法 22222-拦截器MyInterceptor的afterCompletion()方法 11111-拦截器MyInterceptor的afterCompletion()方法
-
如图:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S9rOQyFj-1614520386699)(images\多个拦截器.jpg)]
-
-
3)执行顺序
(拦截器1:返回值true;拦截器2:返回值false)
-
11111-拦截器MyInterceptor的preHandle()方法 22222-拦截器MyInterceptor的preHandle()方法 11111-拦截器MyInterceptor的afterCompletion()方法
-
④ 拦截器和过滤器的区别
-
1)过滤器是servlet中的对象;拦截器是框架中的对象
-
2)过滤器是实现Filter接口的对象;拦截器是实现HandlerInterceptor接口的对象
-
3)过滤器是用来设置resquest、response参数、属性的,侧重数据过滤;拦截器是用来验证请求的,能截断请求
-
4)过滤器是在拦截器之前先执行
-
5)过滤器是tomcat服务器创建的对象;拦截器是springmvc容器创建的对象
-
6)过滤器只有一个执行时间点;拦截器有三个执行时间点
st-
<mvc:interceptors> <!--11111拦截器--> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="cn.zhangm.handler.MyInterceptor" /> </mvc:interceptor> <!--22222拦截器--> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="cn.zhangm.handler.MyInterceptor2" /> </mvc:interceptor> </mvc:interceptors>
-
-
2)执行顺序
(拦截器1:返回值true;拦截器2:返回值true)
-
11111-拦截器MyInterceptor的preHandle()方法 22222-拦截器MyInterceptor的preHandle()方法 =====执行MyController的doSome()方法===== 22222-拦截器MyInterceptor的postHandle()方法 11111-拦截器MyInterceptor的postHandle()方法 22222-拦截器MyInterceptor的afterCompletion()方法 11111-拦截器MyInterceptor的afterCompletion()方法
-
如图:
-
-
3)执行顺序
(拦截器1:返回值true;拦截器2:返回值false)
-
11111-拦截器MyInterceptor的preHandle()方法 22222-拦截器MyInterceptor的preHandle()方法 11111-拦截器MyInterceptor的afterCompletion()方法
-
④ 拦截器和过滤器的区别
- 1)过滤器是servlet中的对象;拦截器是框架中的对象
- 2)过滤器是实现Filter接口的对象;拦截器是实现HandlerInterceptor接口的对象
- 3)过滤器是用来设置resquest、response参数、属性的,侧重数据过滤;拦截器是用来验证请求的,能截断请求
- 4)过滤器是在拦截器之前先执行
- 5)过滤器是tomcat服务器创建的对象;拦截器是springmvc容器创建的对象
- 6)过滤器只有一个执行时间点;拦截器有三个执行时间点
- 7)过滤器可以处理jsp、js、html等;拦截器是侧重拦截controller的对象,如果请求不能被DispatcherServlet接收,这个请求不会执行拦截器的内容