-
- 1.1. 新建maven项目
- 1.2. 在web.xml中注册中央调度器
- 1.3. 创建一个发起请求的页面
- 1.4. 创建控制器对象
- 1.5. 创建一个作为结果的jsp
- 1.6. 创建springmvc配置文件
-
- 3.1. 有视图解析器时使用完整路径转发和重定向
-
- 5.1. 逐个接收
- 5.1.1. 逐个接受时参数名不一致的问题
- 5.2. 对象传参
- 5.3. 处理器方法的形参
- 5.1. 逐个接收
-
- 6.1. 返回值为字符串
- 6.2. 返回值ModelAndView
- 6.3. 返回值为void
- 6.4. 返回值为对象
- 6.5. 返回值为List集合对象
- 6.6. 返回一个真正的字符串
-
- 7.1. DefaultServlet
- 7.2. 使用斜杠时处理静态资源的两种方式
- 7.2.1. 依赖tomcat处理
- 7.2.2. 框架创建ResourceHttpRequestHandler处理器处理
-
- 8.1. 问题与解决方法
-
- 10.1. preHandle
- 10.2. postHandle
- 10.3. afterCompletion
- 10.4. 多个拦截器的访问顺序
- 10.5. 过滤器和拦截器的区别
springMVC
spirngMVC首先是一个MVC框架。
什么是MVC框架?
在web模型中,MVC是一种很流行的框架,通过把Model,View,Controller分离,把较为复杂的web应用分成逻辑清晰的几部分,是为了简化开发,减少出错。还是为了组内开发人员之间的配合。总之就是一种分层工作的办法。
spirngMVC是spring的子框架,用ioc管理对象,spirngMVC容器中保存的是控制器对象。
1. 使用步骤
因为web.xml的版本比较老,需要调整web.xml的版本:在项目管理的web中删掉原来的web.xml,然后创建一个新的web1.xml,指定版本4.0,然后再将名字改回即可。
1.1. 新建maven项目
新建maven项目,加入springmvc的依赖,将spring的依赖都加入
1 | <dependencies> |
1.2. 在web.xml中注册中央调度器
springmvc底层仍然是使用的servlet,这个servlet是中央调度器DispatcherServlet,作用:
- 负责接受用户的请求,显示处理结果
- 创建WebApplicationContext对象,读取配置文件,进而创建控制器对象
在web.xml中注册中央调度器,指定springmvc配置文件的位置,中央调度器会在init方法中读取该配置文件
1 |
|
1.3. 创建一个发起请求的页面
创建一个index.jsp页面,用来发起请求
1 | <%@ page contentType="text/html;charset=UTF-8" language="java" %> |
1.4. 创建控制器对象
使用注解@Controller来创建控制器对象放入springmvc容器中,用来处理请求,返回视图,位置放在类的上面。然后还需在方法上面加入@RequestMapping注解来指定方法处理什么请求,方法的返回值是ModelAndView
1 | package com.hjznb.controller; |
1.5. 创建一个作为结果的jsp
当控制器处理完后,会返回一个视图,使用一个jsp来作为结果视图,获取处理结果
1 | <%@ page contentType="text/html;charset=UTF-8" language="java" %> |
可以将结果视图放在WEB-INF目录中,防止用户可以直接访问。
1.6. 创建springmvc配置文件
springmvc配置文件和spring配置文件基本一致,声明注解扫描器和视图解析器InternalResourceViewResolver
1 |
|
然后将中央调度器的读取路径指定为该文件的路径即可
2. springmvc的处理流程
用户发起请求some.do
中央调度器DispatcherServlet接受请求some.do,把请求转交给处理器映射器HandleMapping(有多个)。
处理器映射器会根据请求从springmvc容器中获取处理器对象(MyController),并将其放入叫做处理器执行链HandlerExecutionChain的类中保存。
DispatcherServlet然后把HandlerExecutionChain中的处理器对象交给了处理器适配器对象(多个)。
处理器适配器对象是实现了HandlerAdapter接口的类,其会调用处理器(MyController)中的doSome()方法,得到返回值ModelAndView,将其交给视图解析器
视图解析器InternalResourceViewResolver接受到返回对象,并组成视图完整路径,创建View对象
DispatcherServlet获取到步骤6中的View对象,调用View对象字节的方法,把Model数据放入request作用域中,执行对象视图的forward,响应请求,请求结束
可以看见,自己不需要调用处理器的方法,是框架中的处理器适配器来调用方法。
3. 视图解析器InternalResourceViewResolver
使用视图解析器可以帮助设置路径,否则转发视图时,需要使用绝对路径,而不是视图的逻辑名称
1 | mv.setViewName("/WEB-INF/view/show.jsp"); |
此时比较麻烦,使用视图解析器进行配置
1 | <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> |
之后框架就会使用视图解析器的前缀+逻辑名称+后缀来拼接组成完整路径,更加方便
1 | mv.setViewName("show"); |
3.1. 有视图解析器时使用完整路径转发和重定向
spring中有两个关键词:转发forward和重定向redirect。使用这两个关键词与ModelAndView的setViewName()方法一起使用,可以实现在有视图解析器的情况下使用完整路径
当需要转发到视图解析器所定义之外的地方,可以使用这个forward
1 |
|
使用redirect进行重定向
框架对重定向的操作:
- 框架会把Model中的简单类型的数据,转为String使用,作为hello.jsp的get请求参数使用,目的是在doRedirect.do和hello.jsp两次请求之间传递数据
- 在目标hello.jsp页面可以使用参数集合对象${param}获取请求参数值
${param.msg},不能使用 ${msg}获取对象,因为重定向不在同一个域中,相当于两个不同的request
1 | <%@ page contentType="text/html;charset=UTF-8" language="java" %> |
- 重定向不能访问/WEB-INF/目录
1 |
|
4. @RequestMapping
在Spring MVC 中使用 @RequestMapping 来映射请求,也就是通过它来指定控制器可以处理哪些URL请求。
其位置可以防止类的上面,为公共部分,模块名称,value中指明要处理的url
1 |
|
当访问的是/user/some.do时,该控制器方法doSome才会来处理请求;在类的上面没有该注解时,访问/some.do,方法即会去处理请求。
该注解中还有个属性method,表示请求的方式,它的值是RequestMethod的枚举值
1 |
表示该方法只是用来处理get请求的,RequestMethod.POST表示处理post请求,method方法默认处理两种请求
注意:在这里,斜杠‘/‘表示基于web根目录,即http://localhost:8080/ch05_url_pattern/
5. 参数的接受
当处理器接受到前端来的数据时,该怎么接收呢,有两种接受方式:逐个接收,对象接收
5.1. 逐个接收
要求:
处理器方法的形参名和请求中的参数名必须一致,同名的请求参数赋给同名的形参
框架接收参数并赋值步骤:
- 框架会先使用request对象接收请求参数,如
1 | String name = request.getParameter("name"); |
- springmvc框架通过中央调度器调用MyController的doSome()方法,调用方法时,按名称对应,把接受到的参数赋值给形参,并会进行类型转换。
例如前端页面传来name、age两个参数
1 | <%@ page contentType="text/html;charset=UTF-8" language="java" %> |
而处理器中的方法为
1 |
|
其会把name和age中的值赋给doSome方法中对应的形参并进行类型转换,然后转发到show.jsp视图页面,show.jsp会取出保存在域中的值,进行显示
1 | <%@ page contentType="text/html;charset=UTF-8" language="java" %> |
打开tomcat,部署项目,输入数据
结果页面显示
可以看见,参数成功赋给了控制器中的方法并完成了转发
注意:当输入的类型和方法中的不匹配时,会报400异常
例如,在age中输入字符串
结果页面显示400错误
而idea中也会报错,表示参数转换失败
1 | 17-Sep-2020 13:18:06.290 警告 [http-nio-8080-exec-4] org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver.logException Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.lang.Integer'; nested exception is java.lang.NumberFormatException: For input string: "发放给回复"] |
5.1.1. 逐个接受时参数名不一致的问题
当请求参数和处理器形参名不一致时,需要使用@RequestParam来指明请求参数名
1 |
|
此时前端页面的参数和控制器方法中的参数名不一致
1 | <p>提交参数给Controller,参数不一致</p> |
该注解放在对应的形参前面,value属性指明请求参数名,required属性默认为true,当请求参数为空时会报错,表示请求中必须包含此参数。
最后结果仍然正确,当参数名不一致时,需要用@RequestParam注解绑定
5.2. 对象传参
和mybatis类似,这里也可以使用对象保存参数,请求参数名要和java对象中的参数名一致,其会调用对象中set方法来赋值
首先创建一个对象,用来接收参数,对象的参数名要和请求参数名一致
1 | /*用来保存参数的一个类*/ |
处理器中的方法为
1 |
|
最后结果与上面一致
5.3. 处理器方法的形参
处理器方法的形参除了用户提交的数据之外,还可以有:
- HttpServletRequest
- HttpServletResponse
- HttpSession
参数是可变的,有不同的功能
6. 处理器方法返回值
处理器方法的返回值有多种:
- 字符串
- ModelAndView
- 对象
- void
6.1. 返回值为字符串
返回值为String表示返回视图,String表示视图的逻辑名称,需要配置视图解析器。(未配置时需要使用完整路径,使用完整路径则不能配置视图解析器)
1 |
|
执行此方法后,会转发到show.jsp视图
6.2. 返回值ModelAndView
返回值为ModelAndView时,表示返回数据和视图。该对象能够保存数据,并能够完成转发操作
1 |
|
此时ModelAndView对象使用addObject()方法将数据保存到域中,类似于Request调用setAttribute()方法。然后转发到show.jsp视图中。
6.3. 返回值为void
当返回值为void时,大多数用来响应ajax请求,返回json对象。
要处理ajax,需要加入Jackson依赖
1 | <!--jackson依赖,用来处理json--> |
在前端页面中发起ajax请求
1 | <%@ page contentType="text/html;charset=UTF-8" language="java" %> |
处理器方法处理请求
1 |
|
可以看见,此时处理ajax请求有点复杂,框架提供了一种简单的方式来返回json
6.4. 返回值为对象
框架提供了一个注解@ResponseBody
作用:
- 把处理器方法返回对象转为json后,通过HttpServletResponse输出给浏览器
位置放置方法的上面,与其他注解没有顺序关系
假设此时返回值为Student对象
1 |
|
返回对象框架的处理流程:
- 框架会把返回的Student类型,调用框架中的ArrayList< HttpMessageConverter>中每个类的canWrite()方法,检查哪个实现类能处理Student类型的数据。
- 框架会调用实现类MappingJackson2HttpMessageConverter的write()方法,把张三同学的student对象转为json,调用Jackson的ObjectMapper实现转为json
- 框架会调用@ResponseBody把2的结果数据输出到浏览器,ajax请求处理完成
即框架会将返回值转换为json对象,减少了很多工作。
附:集合ArrayList< HttpMessageConverter>中的类如下
6.5. 返回值为List集合对象
当返回值为List< Student>对象时,和上面的一样,只是返回值会被转换为json数组,在前端页面中可以对返回值进行遍历
1 | /*jquery中的遍历,function中的第一个参数为下标,第二个为对象*/ |
6.6. 返回一个真正的字符串
返回值是String时,如果有@ResponseBody注解,就是返回数据,否则是返回视图
1 |
|
此时返回的是文本数据,不是json,需要在JQuery中的datatype属性改为text。
此时框架会调用StringHttpMessageConverter来对其转换,写出字符串格式的数据,默认使用ISO-8859-1编码,中文有乱码。
解决中文乱码:在 @RequestMapping注解中给属性produces指定新的contentType。
7. 中央调度器的url-pattern
配置中央调度器的url-pattern有两种形式:
- 使用扩展名的方式,语法: * .xxx,xxx是自定义的扩展名。 常用的方式 * .do,* .action,* .mvc等等
- 使用斜杠”/“,当使用斜杠时,它会替代tomcat中的default,使用斜杠可以使用逻辑名称来访问,而不用加后缀
1 | <form ACTION="some" method="post"> |
1 |
|
在URL中使用http://localhost:8080/ch05_url_pattern/some来访问。
但使用斜杠时,会产生一些问题,下面先看看tomcat中的DefaultServlet
7.1. DefaultServlet
tomcat的web.xml文件中有一个servlet 名称是default,在服务器启动时创建,其url-pattern配置为斜杠‘/’,其作用:
- 处理静态资源
- 处理未映射到其他servlet的请求
如果中央调度器的url-pattern也配置为斜杠,会覆盖掉DefaultServlet,但是中央调度器处理不了静态资源!
7.2. 使用斜杠时处理静态资源的两种方式
当使用斜杠配置时,中央调度器尽管能够处理动态资源,但是无法处理静态资源,因此有两种方法来
7.2.1. 依赖tomcat处理
- 需要在springmvc配置文件中加入< mvc:default-servlet-handler />
1 |
|
原理:加入这个标签后,框架会创建控制器对象DefaultServletHttpRequestHandler(类似于我们自己创建的MyController),DefaultServletHttpRequestHandler对象可以把接受的请求转发给tomcat的default这个servlet
这种方式依然是使用tomcat来处理静态资源,中央调度器会将静态资源转发给DefaultServlet来处理
7.2.2. 框架创建ResourceHttpRequestHandler处理器处理
第二种方法,在springmvc配置文件中加入mvc:resources配置
1 | <!--此时我的静态资源放在webapp下的static目录下--> |
第二种处理静态资源的方式 mvc:resources 加入后框架会创建ResourceHttpRequestHandler这个处理器对象,让这个对象处理静态资源的访问,不依赖tomcat服务器
- mapping:访问静态资源的url地址,使用通配符 ** ,** 可以表示一级目录或多级目录。
- location:静态资源在你项目中的位置,在url中访问mapping中的内容时,会在项目文件location中寻找资源。
8. 路径
在前端和后端的路径访问中,有一些地方需要注意
在后端代码中,/some.do就是处理基于项目目录下的some.do请求
8.1. 问题与解决方法
当index.jsp访问user/some.do后又转发回到index.jsp,再次访问user/some.do会产生路径问题,找不到资源。
9. 异常处理
springmvc框架采用的是统一,全局的异常处理,把Controller中的所欲异常处理都集中到一个地方,采用的是aop的思路,把业务逻辑和异常处理代码分开,解耦合。
使用两个注解
- @ControllerAdvice
- @ExceptionHandler
步骤:
- 新建自定义异常类和他的子类(如果有)
1 | //自定义异常类,还有两个子类NameException、AgeException |
- 在控制器方法中抛出该类异常
1 |
|
- 创建一个普通类,作为全局异常处理类,在类的上面加入@ControllerAdvice注解,然后再在类中定义方法,方法的上面加入@ExceptionHandler,指明该方法要处理的异常,方法中的形参Exception表示控制器中抛出的异常对象
1 |
|
- 创建处理异常的视图界面
1 | <%@ page contentType="text/html;charset=UTF-8" language="java" %> |
- 配置springmvc配置文件,声明组件扫描器,扫描@ControllerAdvice所在的包,声明注解驱动
1 | <!--处理异常需要的两步--> |
此时框架就会用异常处理机制来集中处理异常。
9.1. @ControllerAdvice和@ExceptionHandler
@ControllerAdvice: 控制器增强(也就是说给控制器增加功能 – 异常处理功能),该注解指定了这个类为处理异常的类
- 位置:在类的上面
- 特点:必须让框架知道这个注解所在的包名,需要在springmvc配置文件声明组件扫描器。
- 需要指定@ControllerAdvice所在的包名
@ExceptionHandler:定义处理异常的方法
- 位置:在方法的上面
- 属性value:为异常类的class,是一个数组,表示异常的类型,当发生此类类型异常时,由当前方法处理。可以没有value值,表示处理其他异常,但没有value值类的只能有一个
- 所在方法的返回值:和控制器方法一样,可以使用String,void,ModelAndView和对象
- 所在方法的形参:表示Controller中抛出的异常对象,前端可以使用该对象获取异常信息
10. 拦截器
SpringMVC 中的Interceptor 拦截器也是相当重要和相当有用的,它的主要作用是拦截用户的请求并进行相应的处理。比如通过它来进行权限验证,或者是来判断用户是否登陆,或者是像12306 那样子判断当前时间是否是购票时间。
要将一个普通类定义为拦截器类,需要实现HandlerInterceptor接口,接口中有三个方法:
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
- public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
- public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
要使用拦截器,还需要在springmvc文件中声明拦截器
1 | <!--声明拦截器,拦截器可以有0或多个--> |
下面来介绍一下拦截器的三个方法
10.1. preHandle
preHandler叫做预处理方法,是整个项目的入口,门户。
- 返回值boolean:true,请求可以被处理;false,请求到此截止
- 参数Object handler : 被拦截的控制器对象
- 特点:方法在控制器方法(MyController的doSome)之前先执行的,用户的请求首先到达此方法。在这个方法中可以获取请求的信息,验证请求是否符合要求,可以验证用户是否登录,验证用户是否有权限访问某个地址(url)。 如果验证失败,可以截断请求,请求不能被处理,如果验证成功,可以放行请求,此时控制器方法才能执行。
定义一个preHandle,返回false,并给浏览器一个反馈结果
1 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { |
此时请求会被终止,不会执行控制器中的方法。如果返回true,控制器方法会正常执行
10.2. postHandle
postHandle:后处理方法
- 参数Object handler:被拦截的处理器对象MyController
- 参数ModelAndView mv:处理器方法的返回值
- 特点:1.在处理器方法之后执行的。
2.能够获取到处理器方法的返回值,可以修改数 据和视图,可以影响最后的结果。 3.主要是对原来的执行做二次修正
1 | public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { |
此时在原来结果中加入了新的数据:日期。可以在前端页面中查看该信息。
10.3. afterCompletion
afterCompletion:最后执行的方法
- 参数Object handler:被拦截的处理器对象MyController
- 参数Exception ex:程序中发生的异常
- 特点:在请求处理完成后执行的。框架中规定是当你的视图处理完成后,对视图执行了forward。就认为请求处理完成。一般做资源回收工作,程序请求过程中创建了一些对象,在这里可以删除,把占用的内存回收
例如此时需要计算从preHandler到请求处理完成的时间
1 | public class MyInterceptor implements HandlerInterceptor { |
10.4. 多个拦截器的访问顺序
下面用个例子,创建两个拦截器类
1 | //拦截器类,拦截用户的请求 |
在springmvc配置文件中声明拦截器,有先后顺序,其会将创建好的拦截器放入到一个ArrayList中,叫做处理器执行链,中央调度器会取得这个集合,然后分配
1 | <mvc:interceptors> |
控制器方法
1 |
|
然后将项目部署到tomcat,进行测试,结果如下
1 | 拦截器1的preHandle |
可以看见,其方法的执行顺序是先执行1的preHandle,再执行2的,然后结束时先执行2的,再执行1的。
如果1的preHandle返回false,那么就不会执行后面的方法,但是还是会执行afterCompletion方法
10.5. 过滤器和拦截器的区别
如果要使用拦截器来检查用户的登录,需要在前端使用session来将数据保存,拦截器再将数据取出来验证。