一、拦截器
我们在网上发贴子的时候如果没有登录,点击发送按钮会提示未进行登录,跳转到登录页面。这样的功能是如何实现的。
1、 拦截器的作用
Spring MVC 的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。 用户可以自己定义一些拦截器来实现特定的功能。
拦截器链(Interceptor Chain)。拦截器链就是将拦截器按一定的顺序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。
拦截器和过滤器的区别:
过滤器是servlet规范中的一部分,任何java web工程都可以使用。
拦截器是SpringMVC框架自己的,只有使用了SpringMVC框架的工程才能用。
过滤器在url-pattern中配置了"/*"之后,可以对所有要访问的资源拦截。
拦截器它是只会拦截访问的控制器方法,如果访问的是jsp,html,css,image或者js是不会进行拦截的。 它也是AOP思想的具体应用。
2、拦截器使用步骤
-
自定义类 implements HandlerInterceptor
-
重写拦截器接口三个方法
/** * 实现拦截器的步骤: * 1. 自定义类实现拦截器接口 * 2. 重写接口所有方法 * 弄清楚重写三个方法执行顺序【拦截器执行流程】 * 3. springmvc配置类:配置拦截器,指定拦截策略 */ @Component public class MyInterceptor implements HandlerInterceptor { /** * 请求到达控制器之间,就会进入preHandle,这个方法如果返回值true,请求就进入控制器执行, * 返回值false,请求就不会进入控制器执行,直接返回,页面就没有控制器查询结果 * 几乎大部分功能,我们都是借助preHandle处理。 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandle执行了...."); return true;//不放行请求 } /** * postHandle在控制器执行完毕,进入jsp之前 */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle执行了...."); } /** * afterCompletion:jsp渲染完毕,在浏览器看到数据之前 */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion执行了...."); } }
-
sprinmvc的配置类:配置拦截器拦截策略,
/** * 配置自定义拦截器,使自己拦截器的代码可以工作 * 基于上面方法已经配置不拦截所有的静态资源,springmvc只拦截去控制器的请求,不拦截静态资源 * @param registry 注册中心 */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(myInterceptor) .addPathPatterns("/**") //拦截的url地址配置 拦截多级目录,比如:/building/list /edit }
注意:拦截器拦截的是controller请求,所以只有提交请求到controller中时才会进行拦截
HelloController.java编写控制器代码
@Controller
public class HelloController {
@RequestMapping("/hello") //任意类型的请求list都可以
public String hello(){
System.out.println("Controller接收到客户端发送的请求并处理");
return "hello";
}
}
-hello.jsp编写jsp代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>ooooooooooooooooooooooooooooooooooooook</h1>
<%
System.out.println("hello.jsp代码执行了");
%>
</body>
</html>
-
启动服务器,在浏览器输入以下地址
http://localhost:8989/hello,控制台输出以下结果
preHandle执行了...
Controller接收到客户端发送的请求并处理
postHandle执行了...
hello.jsp代码执行了
afterCompletion执行了...
根据输出结果我们可以得出以下结论:所有的发给控制器的请求都会先进入preHandle方法进行处理,preHandle返回true,请求才会被放行到Controller执行,控制器Controller代码执行完毕后再次进入拦截器执行postHandle,执行完毕后才能进入JSP执行代码,而JSP代码执行完毕后,请求会再一次进入afterCompletion执行,最终响应处理完毕,浏览器看到响应结果。图解如下:
3、案例:利用拦截器完成用户登录认证
案例:使用拦截器处理登录认证,登录成功,可以进入主页;没有登录过,直接导向到login.html进行登录
创建自定义拦截器,验证用户登录情况
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle执行了....");
if (request.getSession().getAttribute("loginUser") != null) {//登录过
return true;//true放行 false:不放行
}
//没有登录,则响应错误消息码,便于判断后进入登录页面
response.getWriter().write(
new ObjectMapper().writeValueAsString(
new ResponseResult<>(401, "尚未登录,请先登录")
)
);
return false;
}
在springmvc配置类中注册自定义拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor)
.addPathPatterns("/**") //拦截的url地址配置 拦截多级目录,比如:/building/list /edit
.excludePathPatterns("/user/dologin");//排除不需要拦截的url地址,不能拦截处理登录的控制器
}
building-list.html发起请求测试拦截器
//查询所有的楼栋分类信息
loadAllTypes(){
axios.get("/type/list",{params:{}})
.then(resp=>{
if(resp.data.status===200){//请求处理成功的码
this.types=resp.data.data;
}else if(resp.data.status===401){ //尚未登录
this.$message({
message: resp.data.msg,
type: 'warning'
});
setTimeout(()=>{
window.parent.location.href="/login.html";
},1000);
}else{ //其他错误情况,直接弹窗提示消息
this.$message({
message: resp.data.msg,
type: 'error'
});
}
})
},
登录页面代码略
登录控制器代码:使用session保存登录结果
@PostMapping("/dologin")
@ResponseBody
public ResponseResult<User> doLogin(@RequestBody User user, HttpSession session){
System.out.println(user);
try {
User loginUser = userService.doLogin(user.getPhone(), user.getPassword());
//session保存
session.setAttribute("loginUser",loginUser);
return new ResponseResult<User>(200,"登录成功",loginUser);
} catch (Exception e) {
e.printStackTrace();
return new ResponseResult<>(505,e.getMessage(),null);
}
}
因为前面配置拦截器是拦截了所有的请求,如果将登录请求也拦截会造成永远登录不成功,所 以要将登录请求设置为不拦截
启动服务器,直接访问http://localhost:8989/main.html页面的"楼栋列表",页面会弹出提示"尚未登录,请先登录"
细节:框架集页面如何实现浏览器地址栏显示登录页面
window.parent.location.href="/login.html";
二、全局异常处理
异常处理的作用就是当程序在运行过程中出现异常的时候给用户显示一个友好提示。
细节:全局异常只监视控制器发生的异常。所以,一般来说,dao和service发生的异常,我们一般就会抛出到controller,由全局异常处理
springmvc全局异常处理使用步骤
1 添加创建全局异常处理类
/**
* 全局异常处理类,其实本质:aop切面
*/
@ControllerAdvice //aop切面
public class MyGlobalException {
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public ResponseResult<Void> handleArithmeticException(ArithmeticException e){
//控制台:异常消息还是要输出的!!给自己看
e.printStackTrace();
return new ResponseResult<>(501,"算术异常,异常原因:"+e.getMessage());
}
@ExceptionHandler(NullPointerException.class)
@ResponseBody
public ResponseResult<Void> handleArithmeticException(NullPointerException e){//e作用用来接收控制器实际抛出异常
//控制台:异常消息还是要输出的!!给自己看
e.printStackTrace();
return new ResponseResult<>(501,"空指针异常,异常原因:"+e.getMessage());
}
}
2 springmvc配置类中开启全局异常处理类所在包扫描
/**
* springmvc配置类,作用:取代springmvc.xml
*/
@Configuration
@ComponentScan({"com.woniu.controller","com.woniu.interceptor","com.woniu.exception"})
@EnableWebMvc //启用springmvc的内置配置,对WebMvcConfigurer接口实现
public class SpringWebConfig implements WebMvcConfigurer {
//其他代码略
}
3 如果要根据不同的异常出现不同的提示,直接在全局异常类中补充对应异常的处理方法即可,参考代码如下:
@ControllerAdvice
public class GlobalException {
/**
* 400 - Bad Request
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
@ResponseBody
public ResponseResult<Void> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
return new ResponseResult<>(400,"参数解析失败");
}
/**
* 405 - Method Not Allowed
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
@ResponseBody
public ResponseResult<Void> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
return new ResponseResult<>(405,"不支持当前请求的方法");
}
/**
* 415 - Unsupported Media Type
*/
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
@ResponseBody
public ResponseResult<Void> handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e) {
return new ResponseResult<>(415,"不支持当前媒体类型");
}
/**
* 500 - Internal Server Error
*/
@ExceptionHandler(HttpServerErrorException.class)
@ResponseBody
public ResponseResult<Void> handleServerErrorException(HttpServerErrorException e) {
return new ResponseResult<>(500,"服务器异常");
}
/**
* 5001
* @param e
* @return
*/
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public ResponseResult<Void> handleArithmeticException(ArithmeticException e){
return new ResponseResult<>(5001,"除数不能为0");
}
}
启动服务器,浏览器输入URL地址,和以前一样发送请求,控制器处理请求的过程中,只要遇到异常,就会去全局异常中找对应的方法执行。
案例:利用全局异常处理登录失败的情况
-
自定义异常LoginException
public class LoginException extends RuntimeException{
public LoginException() {
}
public LoginException(String message) { super(message); } public LoginException(String message, Throwable cause) { super(message, cause); } public LoginException(Throwable cause) { super(cause); } public LoginException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } }
-
UserServiceImpl中遇到登录问题,就抛出LoginException对象
//登录业务
public User doLogin(String phone, String password) {
try {
User user = userDao.selectUserByPhone(phone);
if(!user.getPassword().equals(password)){
//抛出业务异常
throw new LoginException("密码错误!");
}
return user;
} catch (Exception e) {
if(e instanceof EmptyResultDataAccessException){
throw new LoginException("账号不存在!");
}
throw new RuntimeException(e);
}
}
-
在GlobalException中添加LoginException的处理方法