四、SpringMVC核心技术
4.1 请求重定向
SpringMVC框架把原来Servlet表中的请求转发和重定向操作进行了封装,现在可以可以使用简单的方式实现转发和重定向
forward:表示转发,实现request.getRequestDispatcher("xx.jsp").forward(request,response);,转发在服务器内部进行操作,一次请求
redirect:表示重定向,实现response.sendRedirect("xxx.jsp"),用户地址栏变化,二次请求,由于是用户发起,不能访问WEB-INF下的内容
请求
格式:
modelAndView.setViewName("forward:/hello.jsp");
使用封装的请求转发,可以不通过视图解析器,这里路径名使用的是全路径名
转发的显示页面可以自定义
案例:
请求页面:
<p>SpringMVC封装的请求转发</p>
<form action="doForward.do" method="post">
名称:<input type="text" name="name"> <br>
年龄:<input type="text" name="age"> <br>
<input type="submit" value="提交">
</form>
后端控制器:
@Controller
public class MyController {
@RequestMapping(value = "/doForward.do")
public ModelAndView doForward(String name,String age){
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("name","姓名 - " +name);
modelAndView.addObject("age","年龄 - " + age);
modelAndView.setViewName("forward:/hello.jsp");
return modelAndView;
}
}
配置文件:
<?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 http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 声明注解扫描-->
<context:component-scan base-package="com.GeekRose.controller"/>
<!-- 声明视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
显示效果:
重定向
重定向的语法格式:
modelAndView.setViewName("redirect:/hello.jsp");
实现重定向的步骤:
- 发出请求的前端资源
<p>SpringMVC封装的请求重定向</p>
<form action="doRedirect.do" method="post">
名称:<input type="text" name="name"> <br>
年龄:<input type="text" name="age"> <br>
<input type="submit" value="提交">
</form>
- 后端控制器接收请求,添加数据,请求重定向
@RequestMapping("/doRedirect.do")
public ModelAndView doRedirect(String name,String age){
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("name","姓名 - " +name);
modelAndView.addObject("age","年龄 - " + age);
modelAndView.setViewName("redirect:/hello.jsp");
return modelAndView;
}
- 重定向页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>显示</title>
</head>
<body>
<p>this is webapp / hello.jsp</p>
得到的数据: <br>
name: ${name} <br>
age: ${age} <br>
</body>
</html>
- 显示效果:
-
分析:
请求重定向后页面地址栏发生变化
加入的数据没有得到
在地址栏中发现数据出现在地址栏的请求参数上
-
结论:
请求重定向是两次请求,request域中无法得到上一个request的数据,重定向的页面地址栏会变化,加入的数据作为请求参数出现
-
得到数据的解决方案
显示页面中将el表达式默认request域改为从param参数中农获取
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>显示</title>
</head>
<body>
<p>this is webapp / hello.jsp</p>
得到的数据: <br>
name: ${param.name} <br>
age: ${param.age} <br>
</body>
</html>
- 显示效果
${param.name}相当于
数据相当于:<%=request.getParameter("name")%>
-
当我们要访问WEB-INF下的数据,会发生404
重定向不能访问受保护资源
框架对重定向的操作:
-
框架会把Model中的简单类型数据,转为string使用,作为hello.jsp的get请求参数使用
目的:在doRedirect.do 和 hello.jsp两次请求之间可以传递数据
在目标hello.jsp页面可以使用参数集合对象{param.myname}
注意:
关键:SpringMVC的请求转发和请求重定向不和视图解析器一起使用
4.2 异常处理
业务功能和异常处理进行解耦合,使用的aop思想,SpringMVC框架采用的是统一全局异常处理方案,把Controller中的所有异常处理都集中到一个地方。采用的是aop的思想,把业务逻辑和异常处理代码分开。
使用两个注解:
@ExceptionHandler
@ControllerAdvice
异常处理步骤:
新建Maven web项目
加入依赖
新建一个自定义异常类 MyUserException 再定义它的子类NameException AgeException
MyUserException
public class MyUserException extends Exception{
public MyUserException() {
super();
}
public MyUserException(String message) {
super(message);
}
}
NameException
public class NameException extends MyUserException{
public NameException() {
super();
}
public NameException(String message) {
super(message);
}
}
AgeException
public class AgeException extends MyUserException{
public AgeException() {
super();
}
public AgeException(String message) {
super(message);
}
}
- 在controller抛出NameException,AgeException
@Controller
public class MyController {
@RequestMapping(value = "/doSome.do")
public ModelAndView doSome(String name,Integer age) throws MyUserException {
ModelAndView modelAndView = new ModelAndView();
if (!"zs".equals(name)){
throw new NameException("姓名不正确!");
}
if (age == null || age > 90){
throw new AgeException("年龄不正确!");
}
modelAndView.addObject("name","姓名 - " +name);
modelAndView.addObject("age","年龄 - " + age);
modelAndView.setViewName("show");
return modelAndView;
}
}
-
创建一个普通类,作用全局异常处理类
在类上面加入@ControllerAdvice
在类中定义方法,方法上面加入@ExceptionHandler
//@ControllerAdvice 注解表示控制器通知 / 增强
@ControllerAdvice
public class GlobalExceptionHandler {
// @ExceptionHandler 注解映射解决具体异常的方法
// 有一个参数 就是要解决异常的class类
// 形参Exception,表示Controller中跑出的异常对象,通过形参可以获取发生的异常信息
// 方法返回值和对应的切入点的返回值一样
@ExceptionHandler(value = NameException.class)
public ModelAndView doNameException(Exception ex){
// 处理Name异常
/*
异常处理逻辑:
1\. 需要把异常记录下来,记录到数据库,日志文件
记录日志发生的时间,哪个方法发生的,异常错误内容
2\. 发送通知,把异常信息通过邮件 / 短信 / 微信发生给相关人员
3\. 给用户友好的提示
*/
ModelAndView mv = new ModelAndView();
mv.addObject("msg","姓名不准确");
mv.addObject("ex",ex);
mv.setViewName("errorName");
return mv;
}
@ExceptionHandler(value = AgeException.class)
public ModelAndView doAgeException(Exception ex){
ModelAndView mv = new ModelAndView();
mv.addObject("msg","年龄不正确");
mv.addObject("ex",ex);
mv.setViewName("errorAge");
return mv;
}
@ExceptionHandler
public ModelAndView doOtherException(Exception ex){
ModelAndView mv = new ModelAndView();
mv.addObject("msg","这是其他类型的异常");
mv.addObject("ex",ex);
mv.setViewName("otherError");
return mv;
}
}
当我们的异常不是name和age产生,即其他异常就会映射到不加value的@ExceptionHandler注解下的方法来处理
- 创建处理异常的视图页面
errorName.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>errorName</title>
</head>
<body>
<h3>this is name error</h3>
友情提示:${msg} <br>
系统提示:${ex.message}
</body>
</html>
errorAge.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>errorAge</title>
</head>
<body>
<h3>this is age error</h3>
友情提示:${msg} <br>
用户提示:${ex.message}
</body>
</html>
otherError.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>other error</title>
</head>
<body>
<h3>this is other error</h3>
友情提示:${msg} <br>
系统提示:${ex.message}
</body>
</html>
-
创建SpringMVC的配置文件
组件扫描器,扫描@Controller注解
组件扫描器,扫描@ControllerAdvice所在的包名
声明注解驱动
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 声明注解扫描-->
<context:component-scan base-package="com.GeekRose.controller"/>
<!-- 声明视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 实现全局异常处理还需要两步-->
<mvc:annotation-driven />
<context:component-scan base-package="com.GeekRose.handler" />
</beans>
这里命名空间记得导入正确
- 效果:
当我们输入错误数据 例如 姓名为zhangsan时
得到的处理结果为:
使用全局异常处理,主要是为了实现解耦合,不用自己不断写try catch,异常处理代码和业务逻辑代码实现分离
4.3 拦截器
SpringMVC中的Interceptor拦截器是非常重要和相当有用的,它的主要作用是拦截指定的用户请求,并进行相应的预处理和后处理。其拦截的时间点在“处理器映射器根据用户提交的请求映射出了所要执行的处理器类,并且也找到了要执行该处理器类的处理器适配器,在处理器适配器执行处理器之前”。当然,在处理器映射器映射出所要执行的处理器类时,已经将拦截器与处理器组合为了一个处理器执行链,并返回给了中央调度器。
实现了HandlerInterceptor接口的都是拦截器
拦截器和过滤器类似,但是侧重点不同 功能方向不同。
过滤器用来过滤请求参数,设置编码字符集等工作。
拦截器是拦截用户的请求,对请求做判断处理的。
-
拦截器是全局的,可以对多个Controller做拦截。
一个项目可以有0个或多个拦截器,他们在一起拦截用户的请求。
拦截器常用在用户登录处理和权限检查记录日志。
拦截器的使用步骤:
定义类实现HandlerInerterceptor接口
在SpringMVC配置文件中声明拦截器,让框架知道拦截器的存在
拦截器的执行时间:
在请求处理之前,也就是controller类中的方法执行之前先被拦截
在控制器方法执行之后也会执行拦截器
在请求处理完成后也会执行拦截器
预处理方法
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return false;
}
返回值:布尔值
true
false
方法参数:
HttpServletRequest 请求
HttpServletResponse 响应
Object Handler 处理器对象(MyController对象)
预处理方法在执行处理器方法前执行(MyController.doSome),一般用作请求判断,验证请求是否符合要求。可以验证用户是否登录,验证用户是否有权限访问某个连接地址(URL)
如果验证失败,可以截断请求,请求不能被处理
如果验证成功,可以方形请求,此时控制器方法才能执行
后处理方法
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
返回值:void
方法参数:
HttpServletRequest 请求
HttpServletResponse 响应
Object handler 处理器对象
ModelAndView modelAndView 数据与视图对象
后处理方法在处理器方法(MyController.doSome)执行后执行,一般用作数据的二次修订
最后执行的方法
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
返回值:void
请求参数:
HttpServletRequest 请求
HttpServletResponse 响应
Object handler 处理器对象
Exception ex 异常对象
最后执行的方法在请求完成后执行(当请求进行forward,发送到视图时被认定为请求完成),一般用作资源回收处理,程序中创建的一些对象在这里进行删除,释放占用的内存
配置文件声明拦截器
拦截器的声明
<!-- 声明拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<!-- 映射地址 /** 表示工程目录下的所有请求都会被拦截
/user/** 表示user下的所有请求都会被拦截
-->
<mvc:mapping path="/user/**"/>
<bean class="com.GeekRose.interceptor.MyInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
拦截器:看做多个Controller中公用的功能,集中到拦截器统一处理,使用的aop思想
拦截器图解:
多个拦截器
多个拦截器的执行顺序
拦截器的执行顺序为拦截器的声明顺序
图解:
当我们定义两个拦截器:
拦截器定义:
拦截器1:
package com.GeekRose.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
public class MyInterceptor1 implements HandlerInterceptor {
private long btime;
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor1-preHandle");
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor1-postHandle");
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor1-afterCompletion");
}
}
拦截器2:
package com.GeekRose.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
public class MyInterceptor2 implements HandlerInterceptor {
private long btime;
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor2-preHandle");
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor2-postHandle");
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
long etime = System.currentTimeMillis();
System.out.println("MyInterceptor2-afterCompletion");
}
}
拦截器声明:
<!-- 声明拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<!-- 映射地址 /** 表示工程目录下的所有请求都会被拦截
/user/** 表示user下的所有请求都会被拦截
-->
<mvc:mapping path="/user/**"/>
<bean class="com.GeekRose.interceptor.MyInterceptor1" />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/user/**"/>
<bean class="com.GeekRose.interceptor.MyInterceptor2" />
</mvc:interceptor>
</mvc:interceptors>
两个拦截器的预处理方法都为true
控制台输出:
MyInterceptor1-preHandle
MyInterceptor2-preHandle
---MyController-doSome---
MyInterceptor2-postHandle
MyInterceptor1-postHandle
MyInterceptor2-afterCompletion
MyInterceptor1-afterCompletion
多个拦截器执行链:
当拦截器1的预处理方法为true,拦截器2的预处理为false
处理结果:
MyInterceptor1-preHandle
MyInterceptor2-preHandle
MyInterceptor1-afterCompletion
拦截器1预处理为true,进入拦截器2的预处理为false,此时无法进行执行,截断请求。因为拦截器1的预处理为true,所以拦截器1的最终执行方法会执行。
当拦截器1的预处理为false,拦截器2的预处理为true
处理结果:
MyInterceptor1-preHandle
直接截断请求,不会到拦截器2
只要有一个拦截器为false,请求到此结束不会到控制器中
拦截器和过滤器的区别
过滤器是Servlet规范中的对象,拦截器是框架中的对象
过滤器实现Filter接口的对象,拦截器是实现HandlerInterceptor接口
过滤器用来设置request,response的参数,属性的,侧重对数据过滤的,拦截器是用来验证请求的,能截断请求。
过滤器是在拦截器之前先执行的
过滤器是tomcat服务器创建的对象,拦截器是SpringMVC容器中创建的对象
-
过滤器是一个执行时间点
拦截器有三个执行时间点
-
过滤器可以处理jsp、js、html等
拦截器是侧重拦截对Controller的请求,如果你的请求不能被中央调度器接收,这个请求不会执行拦截器内容
拦截器拦截普通方法执行,过滤器过滤Servlet请求响应
拦截器权限登录案例
实现步骤:
新建maven项目
修改web.xml注册中央调度器
新进index.jsp发起请求
创建MyController处理请求
创建结果show.jsp
创建一个login.jsp,模拟登录(把用户信息放入session中)创建一个logout.jsp,模拟退出系统(从session中删除数据)
创建拦截器,从session中获取用户的登录数据,验证能否访问系统
创建一个验证的jsp,如果验证失败,给出提示
-
创建springmvc配置文件
声明组件扫描器
声明拦截器
实现效果:
当我们没有登录权限(session没有数据时),会被拦截器阶段请求调到提示页面,当我们有登录权限,就会访问到数据
这里登录登出在jsp中实现
代码实现:
发出请求的index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<%
String basePath = request.getScheme() + "://" + request.getServerName() + ":"
+ request.getServerPort() + request.getContextPath() + "/";
%>
<head>
<title>Title</title>
<base href="<%=basePath%>>">
</head>
<body>
<h3>这是主页</h3>
<p>登录验证</p>
<form action="user/doSome.do" method="post">
名称:<input type="text" name="name"> <br>
年龄:<input type="text" name="age"> <br>
<input type="submit" value="提交">
</form>
获取登录权限 login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录</title>
</head>
<body>
<h3>log in</h3>
<%
session.setAttribute("name","zs");
%>
</body>
</html>
登出删除权限 logout.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登出</title>
</head>
<h3>log out</h3>
<body>
<%
session.removeAttribute("name");
%>
</body>
</html>
提示页面 tips.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
你的请求已经被FBI拦截,等着open the door吧
</body>
</html>
拦截器 MyInterceptor1
public class MyInterceptor1 implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object key = request.getSession().getAttribute("name");
String keyVal = (String) key;
if (!"zs".equals(keyVal)){
request.getRequestDispatcher("/tips.jsp").forward(request,response);
return false;
}
return true;
}
}
控制器
@Controller
@RequestMapping("/user")
public class MyController {
@RequestMapping(value = "/doSome.do")
public ModelAndView doSome(String name,Integer age) {
System.out.println("---MyController-doSome---");
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("name","姓名 - " +name);
modelAndView.addObject("age","年龄 - " + age);
modelAndView.setViewName("show");
return modelAndView;
}
}
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 声明注解扫描-->
<context:component-scan base-package="com.GeekRose.controller"/>
<!-- 声明视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/"/>
<property name="suffix" value=".jsp"/>
</bean>
<mvc:annotation-driven />
<!-- 声明拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<!-- 映射地址 /** 表示工程目录下的所有请求都会被拦截
/user/** 表示user下的所有请求都会被拦截
-->
<mvc:mapping path="/user/**"/>
<bean class="com.GeekRose.interceptor.MyInterceptor1" />
</mvc:interceptor>
</mvc:interceptors>
</beans>
效果展示:
没有登录权限:提交后
获取权限:
此时我们再进行提交
结果:
当我们删除权限登出
我们再提交数据
结果:
4.4 SpringMVC的内部处理流程
ApplicationContext ctx = new ClassPathXmlApplication("beans.xml");
StudentService service = (StudentService) ctx.getBean("service");
SpringMVC接收请求到处理完成的过程
用户发起请求some.do
-
DispatcherServlet接收请求some.do,把请求转交给处理器映射器,
处理器映射器是SpringMVC框架的一种对象,框架把实现了HandlerMapping接口的类都叫映射器
处理器映射器作用:根据请求,从SpringMVC容器对象中获取处理器对象(MyController controller = ctx.getBean("some.do"))
框架把找到的处理器对象放到一个叫做处理器执行链的类保存(HandlerExecutionChain)
HandlerExectutionChain:类中保存着
处理器对象(MyController)
项目中所有的拦截器(List<HandlerInterceptor> interceptorList)
-
DispatcherServlet把HandlerExecutionChain中的处理器对象交给了处理器适配器对象(多个)
处理器适配器是框架中的一种对象,是SpringMVC框架中的对象,需要实现HandlerAdapter接口
处理器适配器作用:执行处理器方法(调用MyController.doSome() 得到返回值ModelAndView)
得到的结果再通过处理器适配器返回给中央调度器
-
DispatcherServlet把3中获取的ModelAndView交给视图解析器对象
视图解析器:SpringMVC中的对象,需要实现ViewResolver接口(可以有多个)
视图解析器作用:组成视图完整路径,使用前缀、后缀,并创建View对象。view是一个接口,表示视图的,在框架中jsp、html不是string字符串表示,而是使用view和它的实现类表示视图
InternalResourceView:视图类,表示jsp文件,视图解析器会创建InternalResourceView类对象,这个对象里面,有一个属性url=/WEB-INF/view/show.jsp
DispathcerServlet把4步骤中创建的View对象获取到,调用View类自己的方法,把Model数据放入到request作用域,执行对象视图的forward。请求结束
执行流程图解: