文章目录
第四章 SpringMVC核心技术
1.转发和重定向
forward:视图完整路径
redirect:视图完整路径
1.1 java代码
@Controller
public class MyController {
* 控制器方法返回的是MOdelAndView实现转发forward
* 语法: mv.setViewName("forward:视图完整路径")
*
* forward特点 : 不和视图解析器一同工作的。就当项目中没有视图解析器。
*
* 这种方法的意义在于当show.jsp不在视图解析器规定的路径下比如在webapp目录下 就可以用forward来找到
* */
@RequestMapping(value = "/doForward.do")
public ModelAndView doForward(){
System.out.println("执行了MyController的doSome方法");
ModelAndView mv = new ModelAndView();
// 添加数据
mv.addObject("msg","处理了some.do请求");
mv.addObject("fun","执行了doSome方法");
// mv.setViewName("forward:/WEB-INF/view/show.jsp");
mv.setViewName("forward:/hello.jsp");
// 返回结果
return mv;
}
* 当控制器发方法返回ModelAndView实现重定向
* 语法:mv.setViewName("redirect:视图完整路径")
* redirect特点:不和视图解析器一同工作,就当项目中没有视图解析器
*
* 框架提供的重定向的功能
* 1.框架可以实现两次请求之间的数据传递,把第一个请求中的Model里面
* 简单类型的数据,转为字符串,附加到目标页面的后面,做get参数传递。
* 可以在目标页面中获取参数值使用。
*
* 2.在目标页面中,可以使用${para.参数名} 获取参数的值
* */
@RequestMapping("/doRedirect.do")
public ModelAndView doRedirect(String name,Integer age){
System.out.println("doRedirect,name=="+name+",age="+age);
ModelAndView mv = new ModelAndView();
// 这种无法取到值 因为这个放在了第一个request作用域
mv.addObject("myname",name);
mv.addObject("myage",age);
// 这里是第二个request作用域。
mv.setViewName("redirect:/other.jsp");
// http://localhost:8080/ch08_forward_redirect/other.jsp?myname=lisi&myage=22
// 重定向不能访问/WEB-INF
// mv.setViewName("redirect:/WEB-INF/view/other.jsp");
return mv;
}
}
1.2 jsp页面other.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>show</title>
</head>
other.jsp,显示request作用域中的数据<br/>
<H3>myname数据: ${myname}</H3>
<h3>myage数据: ${myage}</h3>
<H3>myname数据: ${param.myname}</H3>
<h3>myage数据: ${param.myage}</h3>
<h3>获取get请求中的参数:<%=request.getParameter("myname")%></h3>
</body>
</html>
<html>
<head>
<title>请求方式</title>
</head>
<body>
<a href="doForward.do">发起doForward.do的get请求</a>
<br/>
<br/>
<p>重定向redirect</p>
<form action="doRedirect.do" method="post">
姓名<input type="text" name="name"><br/>
年龄<input type="text" name="age"><br/>
操作<input type="submit" value="提交"><br/>
</form>
</body>
</html>
2. 异常处理
SpringMVC框架处理异常的常用方式:使用@ExceptionHandler注解处理异常。
框架使用的是集中的异常处理。把各个Controller中抛出的异常集中到一个地方处理。处理异常的叫做异常处理器。框架中使用两个注解完成异常的集中处理。这样每个controller不用单独处理异常了。注解是:
-
@ExceptionHandler:放在方法的上面,表示此方法可以处理某个类型的异常。当异常发生时执行这个方法。
-
@ControllerAdvice :放在类的上面,表示这个类中有异常的处理方法。相当于aop中的@Aspect
@ControllerAdvice :看作是控制器增强,就是给Controller类增加异常(切面)的处理功能。
- 修改springmvc配置
<?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">
<!-- springmvc的配置文件-->
<!-- 声明组件扫描器-->
<context:component-scan base-package="com.sunny.controller"/>
<!-- 声明视图解析器:帮助处理视图-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前缀:指定视图文件的路径-->
<property name="prefix" value="/WEB-INF/view/"/>
<!-- 后缀:视图文件的拓展名-->
<property name="suffix" value=".jsp"/>
</bean>
<!-- 异常处理-->
<context:component-scan base-package="com.sunny.handle"/>
<!-- 注解驱动-->
<mvc:annotation-driven/>
</beans>
- Controller
@Controller
public class MyController{
@RequestMapping(value = "/some.do")
public ModelAndView doSome(String name,Integer age) throws MyUserException {
System.out.println("执行了MyController的doSome方法");
// 抛出异常
if(!"zs".equals(name)){
throw new NameException("姓名不正确");
}
if(age == null|| age.intValue()>80){
throw new AgeException("年龄太大了");
}
ModelAndView mv = new ModelAndView();
// 添加数据
mv.addObject("myname",name);
mv.addObject("myage",age);
// mv.setViewName("forward:/WEB-INF/view/show.jsp");
mv.setViewName("show");
// 返回结果
return mv;
}
}
- exception
public class AgeException extends MyUserException {
public AgeException() {
super();
}
public AgeException(String message) {
super(message);
}
}
public class MyUserException extends Exception {
// ctrl + 0
public MyUserException() {
super();
}
public MyUserException(String message) {
super(message);
}
}
public class NameException extends MyUserException {
// ctrl + o 重写父类的方法
public NameException() {
super();
}
public NameException(String message) {
super(message);
}
}
- handle
package com.sunny.handle;
import com.sunny.exception.AgeException;
import com.sunny.exception.NameException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
import javax.naming.Name;
*@ControllerAdvice : 表示当前类是异常处理类。给controller增加功能的
* 位置 : 在类的上面
* */
@ControllerAdvice
public class GlobalExceptionHandler {
// 定义处理异常的方法,当异常发生后,执行这个方法
// 方法定义和Controller类中方法的定义
* 处理NameException类型的异常
* 参数:
* Exception : 表示Controller抛出的异常对象
*
* @ExceptionHandler: 表示此方法处理异常
* 属性:value 表示异常类型
* 位置:方法的上面
*
* */
@ExceptionHandler(value = NameException.class)
public ModelAndView doNameException(Exception e){
System.out.println("doNameException===="+e);
* 发生异常:
* 1.记录异常,记录到日志文件。
* 2.发送通知,邮件,短信等等
* 3.给用户友好的提示
* */
ModelAndView mv = new ModelAndView();
mv.addObject("tips","姓名只能是zs");
mv.setViewName("nameError");
return mv;
}
// 处理AgeException
@ExceptionHandler(value = AgeException.class)
public ModelAndView doAgeException(Exception e){
System.out.println("doAgeException===="+e);
* 发生异常:
* 1.记录异常,记录到日志文件。
* 2.发送通知,邮件,短信等等
* 3.给用户友好的提示
* */
ModelAndView mv = new ModelAndView();
mv.addObject("tips","年龄不能大于80");
mv.setViewName("ageError");
return mv;
}
// 处理其他异常 不添加value值就能处理任何异常
@ExceptionHandler
public ModelAndView doOtherException(Exception e){
System.out.println("doOtherException===="+e);
* 发生异常:
* 1.记录异常,记录到日志文件。
* 2.发送通知,邮件,短信等等
* 3.给用户友好的提示
* */
ModelAndView mv = new ModelAndView();
mv.addObject("tips","请稍后重试");
mv.setViewName("defaultError");
return mv;
}
}
- jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
ageError.jsp , ${tips}
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>请求方式</title>
</head>
<body>
<a href="doForward.do">发起doForward.do的get请求</a>
<br/>
<br/>
<p>异常处理</p>
<form action="some.do" method="post">
姓名<input type="text" name="name"><br/>
年龄<input type="text" name="age"><br/>
操作<input type="submit" value="提交"><br/>
</form>
</body>
</html>
3.拦截器
拦截器:是springmvc框架中的一种对象,需要实现接口HandlerInterceptor。拦截用户的请求。连接到cintroller的请求。
作用:拦截用户的请求,可以预先对请求做处理,决定是否执行Controller。也可以把多个controller*用的功能定义到拦截器。
特点:
- 拦截器可以分为系统拦截器和自定义拦截器。
- 一个项目可以有多个拦截器。0,或多个自定义拦截器。
- 拦截器侧重拦截用户的请求。
- 拦截器是在请求处理之前先执行的。
拦截器的定义:
- 创建类实现拦截器接口HandlerInterceptor,实现接口中的方法(3个)。
- 在springmvc配置文件中,声明拦截器对象,并指定拦截的uri地址。
3.1 第一个拦截器
package com.sunny.handler;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Date;
/**
* 拦截器
* */
public class MyInterceptor implements HandlerInterceptor {
// ctrl+o
* preHandler:预先处理请求的方法。 总的开关
* 参数 :
* Object handler : 被拦截的控制器对象(MyController)
* 返回值: boolean
* true: 请求是正确的,可以被controller处理的。
* MyInterceptor拦截器的preHandle====
* 执行了MyController的doSome方法
* MyInterceptor拦截器的postHandle====
* MyInterceptor拦截器的afterCompletion====
*
* false:请求不能被处理,控制器方法不会执行。请求到此截止。
* MyInterceptor拦截器的preHandle====
*
* 特点:
* 1.预处理方法他的执行时间:在控制器方法之前先执行的。
* 2.可以对请求做处理,可以做登录的检查,权限的判断,统计数据等等。
* 3.决定请求是否执行。
* */
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
System.out.println("MyInterceptor拦截器的preHandle====");
// request.getRequestDispatcher("/tips.jsp").forward(request,response);
return true;
}
* postHandler:后处理方法
* 参数:
* Object handler : 被拦截的控制器对象(MyController)
* ModelAndView mv: 控制器方法的返回值 (请求的执行结果)
*
* 特点:
* 1.在控制器方法之后执行的。
* 2.能获取到控制器方法的执行结果。可以修改为原来的执行结果。
* 可以修改数据,也可以修改视图。
* 3.可以对请求的二次处理。
*
*
* */
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView mv) throws Exception {
System.out.println("MyInterceptor拦截器的postHandle====");
// 对请求做二次的处理
if(mv != null){
// 修改数据
mv.addObject("mydate",new Date());
// 修改视图
mv.setViewName("other");
}
}
* afterCompletion:最后执行的方法
* 参数:
* Object handler : 被拦截的控制器对象 (MyController)
* Exception ex; 异常对象
*
* 特点:
* 1.在请求处理完成后执行的。
* 请求处理完成的标志是 视图处理完成。对视图执行forward操作后。
*
* 2.可以做程序最后要做的工作,释放内存,清理临时变量。
*
* 3.方法的执行条件:
* 1)当前的拦截器他的preHandle()方法必须执行。
* 2)preHandler()必须返回true
*
* */
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
System.out.println("MyInterceptor拦截器的afterCompletion====");
// 获取数据
HttpSession session = request.getSession();
Object attr = session.getAttribute("attr");
System.out.println("attr="+attr);
// 删除数据
session.removeAttribute("attr");
// 确定数据是否删除
attr = session.getAttribute("attr");
System.out.println("删除后,再次检查数据==="+attr);
}
}
配置文件:springmvc.xml中添加
<!-- 声明拦截器-->
<mvc:interceptors>
<!-- 声明第一个拦截器-->
<mvc:interceptor>
<!-- 指定拦截器的拦截地址
path:拦截的uri地址,使用 ** 通配符。
例如:path = "/user/**"
http://localhost:8080/user/listUser.do
http://localhost:8080/user/query/queryUser.do
-->
<mvc:mapping path="/**"/>
<!-- 指定使用的拦截器-->
<bean class="com.sunny.handler.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
1)preHandle 中可以进行 登录验证 和 权限判断。
当你的preHandle返回true,执行结果:
MyInterceptor拦截器的preHandle====
执行了MyController的doSome方法
MyInterceptor拦截器的postHandle========
MyInterceptor拦截器的afterCompletion====
请求的执行顺序:用户 some.do ---- preHandle-----doSome----postHandler----afterComplietion
当你的preHandle返回false,执行结果:
false:请求不能被处理,控制器方法不会执行。请求到此截止。
MyInterceptor拦截器的preHandle====
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
System.out.println("MyInterceptor拦截器的preHandle====");
request.getRequestDispatcher("/tips.jsp").forward(request,response);
return false;
}
2)postHandle 对请求做二次的处理
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView mv) throws Exception {
System.out.println("MyInterceptor拦截器的postHandle====");
// 对请求做二次的处理
if(mv != null){
// 修改数据
mv.addObject("mydate",new Date());
// 修改视图
mv.setViewName("other");
}
}
4)afterCompletion 最后执行
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
System.out.println("MyInterceptor拦截器的afterCompletion====");
// 获取数据
HttpSession session = request.getSession();
Object attr = session.getAttribute("attr");
System.out.println("attr="+attr);
// 删除数据
session.removeAttribute("attr");
// 确定数据是否删除
attr = session.getAttribute("attr");
System.out.println("删除后,再次检查数据==="+attr);
}
MyInterceptor拦截器的preHandle====
执行了MyController的doSome方法
MyInterceptor拦截器的postHandle====
MyInterceptor拦截器的afterCompletion====
attr=在controller中增加的临时数据
删除后,再次检查数据===null
3.2 多个拦截器
使用两个拦截器,主要看拦截器的执行顺序,以及按个方法控制请求的执行。
1)两个拦截器,第一个preHandler=true,第二个拦截器preHandler=true
MyInterceptor111拦截器的preHandle====
MyInterceptor222拦截器的preHandle====
执行了MyController的doSome方法
MyInterceptor222拦截器的postHandle====
MyInterceptor111拦截器的postHandle====
MyInterceptor222拦截器的afterCompletion====
MyInterceptor111拦截器的afterCompletion====
请求的执行顺序:
用户some.do----拦截器1的preHandle-----拦截器2preHandle-----控制器doSome-------拦截器2postHandle—拦截器1的postHandle------拦截器2的afterCompletion-----拦截器1的afterCompletion。
2)两个拦截器,第一个preHandler=true,第二个拦截器preHandler=flase
MyInterceptor111拦截器的preHandle====
MyInterceptor222拦截器的preHandle====
MyInterceptor111拦截器的afterCompletion====
3)两个拦截器,第一个preHandler = false,第二个拦截器preHandler = true | flase
MyInterceptor111拦截器的preHandle====
为什么要使用多个拦截器?
- 把验证功能分散到独立的拦截器、每个拦截器做单一的验证处理。
- 组合多个拦截器。
总结:
多个拦截器,串在一个链条上的。多个拦截器和一个控制器对象在一个链条上,框架中使用HandlerExecutionChain(处理器执行链),表示一个执行链条
public class HandlerExecutionChain {
// 存放控制器对象的,MyController
private final Object handler;
@Nullable
private HandlerInterceptor[] interceptors;// 存放多个拦截器对象的。MyInterceptor 1, 2
@Nullable
private List<HandlerInterceptor> interceptorList;
private int interceptorIndex;
public HandlerExecutionChain(Object handler) {
this(handler, (HandlerInterceptor[])null);
}
}
拦截器怎么实现1,2,2,1的执行顺序,遍历 HandlerInterceptor[] interceptors 数组
HandlerInterceptor[] interceptors = {MyInterceptor1,MyInterceptor2};
//循环调用方法 1-2
for(int i =0;i<interceptors.length;i++){
HandlerInterceptor obj =interceptors[i];
obj.preHandle();
}
MyController.doSome();
// 2-1
for(int i =interceptors.length-1;i>=0;i--){
HandlerInterceptor obj =interceptors[i];
obj.postHandle();
}
简单实现验证功能
这一部分在开发过程中逻辑大部分是相同的,可能发生改变的部分是判断账号的部分。
/**
* 验证功能
*
* */
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
System.out.println("MyInterceptor111拦截器的preHandle====");
// 获取登录的账号信息
String username= "";
Object attr = request.getSession().getAttribute("username");
if(attr!=null){
username = (String) attr;
}
// 判断账号
if("zhangsan".equals(username)){
return true;
}else{
request.getRequestDispatcher("/tips.jsp").forward(request,response);
return false;
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录</title>
</head>
<body>
登录操作!!!
<%
session.setAttribute("username","zhangsan");
%>
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>退出操作</title>
</head>
<body>
退出系统!!!
<%
session.removeAttribute("username");
%>
</body>
</html>
3.3 拦截器和过滤器的对比
-
拦截器是springmvc框架中的对象。过滤器是servlet中的对象。
-
拦截器对象是框架容器创建的,过滤器对象是tomcat创建的对象。
-
拦截器是侧重对请求做判断,处理的,可以截断请求。过滤器是侧重对request,response对象设置值的。例如request.setCharacterEncoding(“utf-8”)
-
拦截器的执行时间有三个,控制器方法之前,之后,请求完成后。过滤器是在请求之前。
-
拦截器是拦截对controller,动态资源请求的。过滤器可以过滤所有请求动态的和静态的。
-
拦截器和过滤器一起执行的,先执行的过滤器,后面是*调度器,后面才是拦截器,再后面是控制器方法。