SpringMVC拦截器简介(摘抄)
SpringMVC中的Interceptor拦截器的作用:
1)日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等
2)权限检查:譬如登录检测、不同权限的用户访问不同的页面
3)性能监控:监控系统性能状态,有时候系统运行缓慢,其可以记录某请求进入处理器前的时间,最后再记录个请求处理完毕的最终时间,得到处理该请求所耗用的时间,以便分析问题原因(如果有反向代理,如apache可以自动记录);
4)通用行为:读取cookies获取用户信息并将用户对象放进请求中,例可以实现免登陆(cookies未过期的情况下)等
拦截器主要的作用是拦截用户请求并进行相应处理
自定义拦截器:
SpringMVC中的Interceptor拦截请求是通过HandlerInterceptor来实现的:
要定义的Interceptor类要实现了Spring 的HandlerInterceptor 接口
要定义的Interceptor类继承实现了HandlerInterceptor 接口的类(比如 HandlerInterceptorAdapter 类)
HandlerInterceptor 有三个方法:
preHandle (HttpServletRequest request, HttpServletResponse response, Object handle),在请求处理之前进行调用。
postHandle (HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView),请求进行处理之后调用。
afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex) ,整个请求结束之后(渲染了对应的视图之后)调用。
理论代码实现
逻辑:
--用户访问home页面
--拦截器拦截检查用户是否已经登录
--已登录:return true,执行postHandle和afterCompletion
--未登录:preHandle将用户重定向到登录页面,并return false,不会往下执行postHandle和afterCompletion
Global.java,可以将各种全局变量放置到此
package main.java.com.smart.constant;
import javax.servlet.http.HttpServletRequest;
public class Global {
public static final String USER_SESSION_KEY = "user-session-key";
}
UserController .java,一下为SpringMVC的C部分的用户控制器,完整的登录功能
package main.java.com.smart.web;
import java.util.List;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import main.java.com.smart.constant.Global;
import main.java.com.smart.domain.User;
import main.java.com.smart.exception.MyException;
import main.java.com.smart.service.UserService;
@RequestMapping("user")//类级路径,class里面的都为子级路径
@Controller
public class UserController {
@Resource
UserService service;
@Resource
HttpServletRequest request;
/**
* @return 项目在当前服务器上的基础路径,这个路径很重要,重定向时因为路径会改变需要
* 把basePath带上,不然会找不到资源,可以观察浏览器路径栏看其路径变化
*/
public String basePath(){
String path = request.getContextPath();
+request.getServerName()+":"
+request.getServerPort()+path+"/";
}
/**
* SpringMVC异常抛出测试页面
* @throws MyException
*/
@RequestMapping("/exception")
public void exception() throws MyException {
throw new MyException("测试springmvc中的异常捕获");
}
/**
* 用户主页--------------------注意这里的页面比其他的多了个/s路径,是为了拦截器过滤home页面设置的
* @return
*/
@RequestMapping("/s/home")
public String toUserHome(){
return "/WEB-INF/jsp/success.jsp";//直接请求转发
}
/**
* 用户登录页面
* @return
*/
@RequestMapping("/ulogin")
public String toLoginPage(){
return "/WEB-INF/jsp/ulogin.jsp";
}
/**
* 退出登录
*
* @param userName
* @param status
* @return
* tip:SessionStatus用于清楚session中的属性一般退出登录时候使用
* String userName,SessionStatus status
*/
@RequestMapping("/doLogout")
public String doLogout(){
System.out.println("触发退出登录");
//清除session
request.getSession().removeAttribute(Global.USER_SESSION_KEY);//这里设置了一个全局静态变量,实际项目不允许这样做,后续会配合Redis来完善整个流程
//拼接跳转页面路径
return "redirect:"+basePath();//重定向需要引入basePath()
}
/**
* 处理用户请求
*
* @param uid
* @param password
* @return
*
*
* tip:参数consumes={"application/json","text/plain"}指定提交内容类型
* produces="text/plain"指定请求中必须包含某些参数值才会触发这个处理方法
* params="myParam=myValue"指定请求中必须包含某些参数值才会触发这个处理方法
* hearders="content-type=text/*"请求头Header中必须包含某些指定的参数值,才能让该方法处理请求
*/
@RequestMapping(value = "/doLogin", method = RequestMethod.POST)
public String doLogin(@RequestParam Integer uid, @RequestParam String password){
try {
System.out.println("触发登录模块");
User user = service.doLoginById(uid,password);
//这里只是个单用户测试
request.getSession().setAttribute(Global.USER_SESSION_KEY,user);
return "redirect:"+basePath()+"/user/s/home.html";//重定向(间接请求转发)
// return "/WEB-INF/jsp/success.jsp";
} catch (Exception e) {
request.setAttribute("error", e.getMessage());
return "/WEB-INF/jsp/ulogin.jsp";
}
}
index.jsp(默认页面,该页面不在WEB-INF安全目录下,浏览器可以直接访问)
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>首页</title>
</head>
<body>
<div style="margin:0 auto; padding-top: 100px; font-size: 18px;" align="center">
<p><a href="user/ulogin.html">登录系统 </a></p>
<p><a href="user/s/home.html">用户中心</a></p>
<p><a href="user/exception.html">触发异常</a></p>
</div>
</body>
</html>
ulogin.jsp(用户登录页面,安全目录下浏览器无法直接访问服务器,要注意basePath的引入,插入一段java代码获取当前路径)
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>登录用户桌面</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/public.css" type="text/css">
<link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/allprivate.css" type="text/css">
</head>
<body>
<div id="container">
<div class="ulogin">
<p class="ulogin_head"><span>用户登录</span></p>
<form class="ulogin_form" onsubmit="return checkUserName()" method="POST" action="user/doLogin.html">
<table>
<tr>
<td>账号</td>
<td><input type="text" name="uid" id="uid" class="ulogin_form_1"
required v-model="uid_text" :placeholder="tip_uid" :maxlength="userlength"></td>
</tr>
<tr>
<td>密码</td>
<td><input type="password" name="password" id="pwd" class="ulogin_form_1"
required v-model="pwd_text" :placeholder="tip_pwd" :maxlength="pwdlength"></td>
</tr>
</table>
<p class="ulogin_form_line"></p>
<input type="submit" name="登录" class="ulogin_form_2">
<input type="reset" name="重置" class="ulogin_form_2">
</form>
</div>
<div><a href="user/register.html">用户注册</a></div>
<font color="red">${error}</font>
</div>
<script src="${pageContext.request.contextPath}/static/js/vue.min.js"></script>
<script src="${pageContext.request.contextPath}/static/js/login.js"></script>
</body>
</html>
success.jsp()
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<base href="<%=basePath%>">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>登陆成功</title>
</head>
<body>
<h1>登陆成功</h1>
<P><a href="user/doLogout.html">退出登录</a></P>
</body>
</html>
拦截器部分(拦截器本质上也是属于AOP,面向切面的编程,因此可以和原本需要处理的程序完全解耦)
LoginInterceptor.java,定义一个用户登录拦截器
package main.java.com.smart.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.testng.internal.Nullable;
import main.java.com.smart.constant.Global;
public class LoginInterceptor implements HandlerInterceptor {
/**
* 请求进入处理器前运行
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
Object user = request.getSession().getAttribute(Global.USER_SESSION_KEY);
if(user == null){
System.out.println("尚未登录,调到登录页面");
response.sendRedirect(basePath+"/user/ulogin.html");//注意注意,再强调,得引入basePath
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
System.out.println("postHandle");
}
/**
* 请求全部执行完后运行
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
System.out.println("afterCompletion");
}
}
SpringMVC-servlet.xml中配置拦截器,在这里根据业务可以配置整条拦截器链进行各种过滤
<mvc:interceptors>
<mvc:interceptor>
<!-- 拦截所有URL中包含/user/的请求 -->
<mvc:mapping path="/user/s/**"/><!-- 将后缀为/user/s/的所有请求拦截下来-->
<!--将请求指定给自定义的拦截器进行处理-->
<bean class="main.java.com.smart.interceptor.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
SpringMVC的异常统一处理
MyException.java(定义一个超级super简单的自定义异常类)
package main.java.com.smart.exception;
public class MyException extends Exception {
/**
*
*/
private static final long serialVersionUID = 1L;
public MyException(String message) {
super(message);
}
}
BaseController.java(将自定义的异常类交给SpringMVC处理)
package main.java.com.smart.web;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.ExceptionHandler;
import main.java.com.smart.exception.MyException;
public class BaseController {
@ExceptionHandler(MyException.class)
public String handleException(Exception ex, HttpServletRequest request){
//request.setAttribute和ModelMap的使用异曲同工,后者是springMVC的惯用手法,可以查看其源码进行对比
request.setAttribute("errors", ex.getMessage());
return "/WEB-INF/jsp/exception.jsp";//可以更换为重定向,注意事项已经B过了
}
}
exception.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>异常</title>
</head>
<body>
<div style="margin:0 auto; padding-top: 100px;" align="center">
<h1>服务器发生异常,异常信息:${errors}</h1>
</div>
</body>
</html>
总结大会
校长发言:
--转发和重定向的区别--
重定向response.sendRedirect("success.jsp");
转发:request.getRequestDispatcher("success.jsp").forward(request,response);
1.转发在服务器端完成的;重定向是在客户端完成的
2.转发的速度快;重定向速度慢
3.转发的是同一次请求;重定向是两次不同请求
4.转发不会执行转发后的代码;重定向会执行重定向之后的代码
5.转发地址栏没有变化;重定向地址栏有变化
6.转发必须是在同一台服务器下完成;重定向可以在不同的服务器下完成
7.转发时数据可以共享,重定向时请求数据会丢失
8.重定向把负担转交给浏览器,在客户端处理;转发是由服务器端处理
9.如果要转向的资源属于同一个系统,就可以用转发;如果不在同一个系统,或者不在同一个服务器上,就用重新定向
10.简单说,只是返回页面,无关地址问题就使用转发,若需要进行下一步业务操作,从而需要改变地址,那就重定向
11.重定向后地址会变化,需要手动通过代码获取跳转的服务器的basePath,不然找不到资源,即使重定向到原本服务器也要手动获取basePath
同学发言:
Forward和Redirect代表了两种请求转发方式:直接转发和间接转发。
直接转发方式(Forward),客户端和浏览器只发出一次请求,Servlet、HTML、JSP或其它信息资源,由第二个信息资源响应该请求,在请求对象request中,保存的对象对于每个信息资源是共享的。
间接转发方式(Redirect)实际是两次HTTP请求,服务器端在响应第一次请求的时候,让浏览器再向另外一个URL发出请求,从而达到转发的目的。
SpringMVC的拦截器是和需要处理的程序完全解耦的
ModelMap对象主要用于传递控制方法处理数据到结果页面,也就是说我们把结果页面上需要的数据一个请求过程中传递处理的数据。通过以下方法向页面传递参数: addAttribute(String key,Object value);
在页面上可以通过el变量方式$key或者bboss的一系列数据展示标签获取并展示modelmap中的数据
原理图: