SpringMVC基于注解使用拦截器

546 阅读9分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第9天,点击查看活动详情

一、SpringMVC拦截器

拦截器采用AOP的设计思想, 它跟过滤器类似, 用来拦截处理方法在之前和之后执行一些跟主业务没有关系的一些公共功能:
比如:可以实现:权限控制、日志、异常记录、记录方法执行时间.....

SpringMVC提供了拦截器机制,允许运行目标方法之前进行一些拦截工作或者目标方法运行之后进行一下其他相关的处理。自定义的拦截器必须实现HandlerInterceptor接口。

image.png

拦截器一个有3个回调方法,而一般的过滤器Filter才两个: preHandle:预处理回调方法,实现处理器的预处理(如登录检查),第三个参数为响应的处理器返回值:true表示继续流程(如调用下一个拦截器或处理器);false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应;

postHandle:后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为null。

afterCompletion:整个请求处理完毕回调方法,即在视图渲染完毕时回调,如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finally,但仅调用处理器执行链中preHandle返回true的拦截器才会执行

二、自定义拦截器

2-1、首先创建一个springmvc项目

之前文章已经提到过,如有问题,可以看一下前面的文章

2-2、创建一个类并集成HandlerInterceptor

image.png 可以看到类中有报错信息,这个是由于未导入tomcat包引起的。

1、打开project Structure image.png

2、选择导入lib包 image.png

3、导入tomcat包 image.png

4、这样类就不会报错了 image.png

2-3、添加拦截器的相关内容

我们先简单输出一些信息,方便了解拦截器

image.png

2-4、在spring-mvc.xml添加拦截器的配置

我们创建完拦截器之后必须在配置文件中进行配置,否则就无法生效了

<mvc:interceptors>
    <bean class="com.jony.interceptor.MyInterceptor"/>
</mvc:interceptors>

2-5、测试一下

浏览器访问控制器方法之后,控制台输入层如下: image.png

然后我们认为生成一个异常,然后访问方法,控制台输出如下:

image.png

2-6、小结

通过测试我们可以看到:
1、不论方法是否异常,拦截器中的preHandle一定先执行
2、方法正常执行的时候,拦截器执行顺序为:preHandle->postHandle->afterCompletion
3、方法异常的时候,拦截器执行顺序为:preHandle->afterCompletion

在配置拦截器的时候有两个需要注意的点:

1、如果prehandle方法返回值为false,那么意味着不放行,那么就会造成后续的所有操作都中断

2、如果执行到方法中出现异常,那么后续流程不会处理但是afterCompletion方法会执行

三、拦截器中方法的解析

3-1、preHandle方法参数解析

执行方法之前执行

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    System.out.println("----------方法前执行--------------preHandle");
    return HandlerInterceptor.super.preHandle(request, response, handler);
}

可以看到preHandle方法中有三个参数,各参数作用如下: request:请求中request的信息(我们可以在修改在进入具体方法前,修改request里面的值) response:获取response中的信息 handler:封装了当前处理方法的信息(比如拦截当前是哪个类、哪个方法以及方法里面的参数等)

image.png

这样的情况,比如我们做权限控制的时候,就可以在preHandle中进行控制,可以在方法中判断当前用户是否有权限访问当前数据接口,如果有则

return HandlerInterceptor.super.preHandle(request, response, handler);

如果没有则返回相关错误信息。

在上面的方法中,使用了

HandlerMethod handlerMethod= (HandlerMethod) handler;

这种情况适用于直接访问到了控制器的方法中,如果直接通过视图解析器到了视图层就户报错

现在spring-mvc.xml配置一个test的解析器如下:

image.png 然后通过浏览器访问

image.png 这是因为直接通过视图解析器直接访问到了jsp页面,这样在用HandlerMethod接收的时候就会类型转换错误。

需要先判断一下当前的handle是否是Handle是否是HandlerMethod类型就可以了。 image.png

3-2、postHandle方法解析

在请求方法后执行,在视图渲染之前执行,当处理方法出现异常,这个方法就不会执行了

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    System.out.println("----------方法后执行,在渲染之前--------------postHandle");
    HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}

request:请求中request的信息(我们可以在修改在进入具体方法前,修改request里面的值)
response:获取response中的信息,可以修改response里面的信息,然后再返回给视图层
handler:封装了当前处理方法的信息(比如拦截当前是哪个类、哪个方法以及方法里面的参数等) modelAndView:封装了model和view,在这个地方可以对model里面的数据进行修改,也可以对view返回的视图进行修改

3-3、afterCompletion 方法解析

在视图渲染之后执行

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    System.out.println("----------方法后执行,在渲染后执行--------------afterCompletion");
    HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}

request:请求中request的信息(我们可以在修改在进入具体方法前,修改request里面的值)
response:获取response中的信息,可以修改response里面的信息,然后再返回给视图层
handler:封装了当前处理方法的信息(比如拦截当前是哪个类、哪个方法以及方法里面的参数等) modelAndView:封装了model和view,在这个地方可以对model里面的数据进行修改,也可以对view返回的视图进行修改 ex:Exception对象,在该方法中可以去记录错误信息日志功能。

此方法相当于try-catch-finally中的finally。

四、多个拦截器执行顺序是怎样的

多个过滤器,哪个优先执行取决于在spring-mvc.xml中配置的先后顺序,配置在前面的则会优先执行。

五、拦截器和过滤器的区别

5-1、拦截器过滤器的区别

1、过滤器是基于函数回调的,而拦截器是基于java反射的

2、过滤器依赖于servlet容器,而拦截器不依赖与Servlet容器,拦截器依赖SpringMVC(拦截器需要在spring-mvc配置文件中配置,过滤器需要在web.xml中进行配置)

3、过滤器几乎对所有的请求都可以起作用,而拦截器只能对SpringMVC请求起作用

4、拦截器可以访问处理方法的上下文(如处理方法的类、方法名、参数等),而过滤器不可以。

5、如果过滤器和拦截器同时存在,会先经过过滤器

5-2、执行顺序

具体执行过程如下:

image.png

上面执行过程,最终就是按照下图的方式进行的
请求先到tomcat->Filter->servlet->Inteceptor->Controller->Inteceptor->Servlet->Filter->tomcat image.png

5-3、测试

5-3-1、新建一个过滤器

5-3-1-1、创建MyFilter

package com.jony.filter;

import javax.servlet.*;
import java.io.IOException;

public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("------------过滤器前------------------");
        filterChain.doFilter(servletRequest,servletResponse);
        System.out.println("------------过滤器后------------------");
    }

    @Override
    public void destroy() {
    }
}

5-3-1-2、配置过滤器

再提一下,过滤器配置url各配置的含义如下: / 除了jsp请求都会匹配到
/* 所有请求都会匹配到
*.do *.action *.htm url结尾匹配(可以自定义)

5-3-1-2-1、在web.xml中进行配置
<filter>
    <filter-name>MyFilter</filter-name>
    <filter-class>com.jony.filter.MyFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>MyFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
5-3-1-2-2、使用@WebFilter注解配置
@WebFilter("/*")

5-3-2、测试

通过下图可以看到,首先先执行过滤器中的doFilter方法并且 先执行filterChain.doFilter(servletRequest,servletResponse);之前的代码,然后接着执行拦截器,等拦截器中afterCompletion执行完之后再执行filterChain.doFilter(servletRequest,servletResponse);之后的代码。

image.png

六、使用拦截器过滤用户登录状态

我们在实际项目中,一些功能操作或者视图访问都需要对用户登录状态进行处理,如果未登录则不允许用户访问,下面我们做一个简单的实现方式,通过登录页,然后到后台管理页面。

6-1、首先创建两个jsp,login和admin

login.jsp


<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
this is loginPage

<form action="${pageContext.request.contextPath}/login" method="post">
    用户名:<input name="userName" value=""><p></p>
    密  码: <input name="password" value=""><p></p>
    <input type="submit" value="登录">
</form>
</body>
</html>

admin.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
this is adminPage
hello : ${pageContext.session.getAttribute("userSession")}
</body>
</html>

6-2、创建控制器相关方法

控制器中创建了两个方法,一个是通过get请求访问login页面的,另外一个是通过post请求进行登录,并将用户信息放到session中。

//使用get请求跳转登录页
@GetMapping("/login")
public String login(){
    return "login";
}

//使用post登录,并跳转管理页面
@PostMapping("/login")
public String login(User user){
    System.out.println(user);
    //保存用户session
    httpSession.setAttribute("userSession",user);
    return "admin";
}

6-3、spring-mvc.xml配置admin.jsp视图解析

<mvc:view-controller path="/admin" view-name="admin"/>

这样admin.jsp 就有两种方式可以访问呢了
1、通过login方法登录,跳转到admin。
通过登录页输入账号密码,然后在控制器中放入到session,admin输出session信息如下:

image.png 2、通过视图解析器直接访问admin.jsp(因为上步操作,已经有了session,清除session之后再次访问admin)
这种情况下session中是没有数据的

image.png

我们需要的场景实际上,需要用户登录并保存session才可以跳转admin,如果没有session的情况下,直接访问admin是需要被禁止的。

6-4、在拦截器拦截userSession

创建SessionInterception.java
在拦截器中我们获取用户的session,如果session为空则直接重定向到login页面

image.png

6-5、在spring-mvc.xml中配置拦截器

image.png

思考:拦截器会拦截所有请求,假如我们访问admin,遇到拦截器session为空,然后跳转到login页面,然后再次遇到拦截器同样session还是为空,再次重定向到login页面,这样就形成了死循环,如下图:

image.png 这样我们就需要将login排除在拦截器之外才行。

6-6、配置拦截器排除拦截的请求

<mvc:interceptors>
    <!--拦截所有请求-->
    <bean class="com.jony.interceptor.MyInterceptor"/>

    <!--如果不是所有请求都需要拦截,则配置一个<mvc:interceptor>-->
    <mvc:interceptor >
        <!--需要拦截所有请求-->
        <mvc:mapping path="/**"/>
        <!--排除不需要拦截的请求(只会拦截get请求)-->
        <mvc:exclude-mapping path="/login"/>
        <bean class="com.jony.interceptor.SessionInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

测试访问admin,通过查看开发者工具中的Network可以看到首先访问到了admin然后重定向到了login。

image.png

登录之后,再次访问admin,因为有session拦截器就可以不进行过滤了,这时候如果清除cookie,就再次无法访问admin页面了

image.png