zoukankan      html  css  js  c++  java
  • 对Spring MVC拦截器的理解

    在平常练手的项目中,对于用户认证以及用户权限管理往往都是通过SpringMVC 拦截器以及其他手段进行处理,然而当项目规模变大,系统安全性要求增加的时候,基于SpringMVC 拦截器等实现的用户认证功能已经不能满足系统需求。常见的手段为利用Spring Security、Apache Shiro 等常见安全框架进行替换。本文首先介绍一下SpringMVC 拦截器的相关知识。

    1.Spring MVC

    Spring MVC是SSM中的一件套,它是由Spring提供的一个Web框架,借助于注解,使得控制器(Controller)的开发与测试更加简单。Spring MVC通常由以下几个部分构成:DispatcherServlet(核心)、HandlerMapping、controller、ViewResolver等。

    1.1 运行原理

    1. 客户端(浏览器)将请求(包括URL、HTTP协议方法、请求头、请求参数、Cookie等)直接发送至DispatcherServlet
    2. DispatcherServlet 根据请求信息调⽤ HandlerMapping ,解析请求对应的Handler(Controller)。
    3. DispatcherServlet将请求提交至对应的Handler(其实就是Controller),开始由HandlerAdapter 适配器处理
    4. Controller调用业务逻辑对用户的请求进行处理
    5. 处理完成后返回一个ModelAndView对象(包含了数据模型以及相应的视图的信息)给DispatcherServlet。
    6. ModelAndView中的视图是逻辑视图,DispatcherServlet借助ViewResolver(视图解析器)完成真实视图对象的解析
    7. 当得到真实的视图对象后,DispatcherServlet利用真实视图对ModelAndView中的Mode数据对象进行渲染。
    8. 把View返回给浏览器。

    2.Spring MVC拦截器

    2.1 概述

    Spring MVC中的Interceptor拦截器机制主要用于拦截用户的请求并做出相应的处理,如下图所示,可以发现Interceptor位于DispatcherServlet以及Controller之间,所以能够用于拦截对Controller层的相关请求。Interceptor拦截器常用于用户权限认证以及判断用户是否登录等场景。

    2.2 拦截器的两种实现方式

    2.2.1 通过实现HandlerInterceptor接口

    (1)拦截器的实现

    在SpringMVC中,拦截器的实现一般都是通过实现HandlerInterceptor接口,并重写该接口中的3个方法:preHandle()postHandle()afterCompletion()

    public interface HandlerInterceptor {
    
        boolean preHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;
    
        void postHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3, ModelAndView var4) throws Exception;
    
        void afterCompletion(HttpServletRequest var1, HttpServletResponse var2, Object var3, Exception var4) throws Exception;
    }
    
    preHandle

    preHandle拦截器作用于用户请求到达Controller之前,如果需要对用户的请求做预处理,可以选择在该方法中完成。3种方法中唯一带有返回值的方法:

    • 如果返回值为true,则继续执行之后的拦截器或者Controller
    • 如果返回值为false,则不再执行后面的拦截器和Controller
    postHandle

    执行完Controller之后,利用model渲染真实视图之前,作用场景为需要对响应的相关数据进处理。

    afterCompletion

    调用完Controller接口,渲染View页面后调用,同时如果prehandle方法的返回值为true,则也会执行该方法。

    (2)拦截器的配置

    实现拦截器之后,需要对拦截器进行配置,默认情况下拦截器将会拦截所有请求,包括静态资源等等,而静态资源(图片、css、js等)的请求一般是不需要拦截的。同时也可以自定义需要拦截的请求。

    2.2.2 使用自定义注解实现拦截器

    (1)自定义注解

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    // 自定义注解需要用元注解标记
    // 四种常见的元注解@Target(自定义注解的作用类型,例如类、方法、属性等) 
    // @Retention(自定义注解有效时间,例如编译时,运行时)
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface LoginRequired {
    }
    

    (2)在Controller中对相应方法进行自定义注解的标注

    @LoginRequired
    @RequestMapping(path = "/setting",method = RequestMethod.GET)
    public String getSettingPage(){
        return "/site/setting";
    }
    

    (3)利用反射获取注解

    拦截器会拦截所有请求,但是我们通过判断可以实现只对带有该注解的方法进行处理。

    import com.nowcoder.community.annotation.LoginRequired;
    import com.nowcoder.community.util.HostHolder;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.web.method.HandlerMethod;
    import org.springframework.web.servlet.HandlerInterceptor;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.lang.reflect.Method;
    
    @Component
    public class LoginRequiredInterceptor implements HandlerInterceptor {
    
        @Autowired
        private HostHolder hostHolder;
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            // 因为我们的目标是只拦截方法,而拦截器有可能会拦截其他资源,所以必须先判断拦截器拦截的目标handler是否为方法
            if(handler instanceof HandlerMethod){
                HandlerMethod handlerMethod = (HandlerMethod) handler;
                Method method = handlerMethod.getMethod();
                // 利用反射获取注解
                LoginRequired loginRequired = method.getAnnotation(LoginRequired.class);
                if(loginRequired != null && hostHolder.getUser() == null){
                    // 如果用户未登录,则重定向至登录界面
                    response.sendRedirect(request.getContextPath() + "/login");
                    // 拒绝本次请求
                    return false;
                }
            }
            return true;
        }
    }
    

    自定义注解实现拦截器的好处在于可以避免对拦截器进行配置。

    2.3 源码底层

    上文说到,所有请求都会直接先传递至DispatcherServlet,所以先分析一下DispatcherServlet,在该类中,最重要的一个方法就是doDispatch,查看部分核心源码之后,你会发现DispatcherServlet就是依赖该方法进行任务分配。

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
                try {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
    
                    //返回所有拦截器
                    mappedHandler = this.getHandler(processedRequest);
                    
                    if(mappedHandler == null || mappedHandler.getHandler() == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }
                    //获取能够处理当前请求所对应的适配器,并用于调用Controller中逻辑代码。
                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
    
                    //调用所有拦截器的preHandle方法
                    if(!mappedHandler.applyPreHandle(processedRequest, response)) {
                        //如果拦截器的preHandle方法返回值为false,则结束该方法的执行
                        return;
                    }
                    //执行Controller中的逻辑代码,获取到ModelAndView对象
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
                    //调用所有拦截器的postHandle方法
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var19) {
                    dispatchException = var19;
                }
                //处理视图渲染
                this.processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
            } catch (Exception var20) {
                //如果在执行过程中有异常,执行后续的收尾工作,执行对应拦截器中的afterCompletion方法
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var20);
            } 
        }
    
  • 相关阅读:
    基于jquery自己写滑动门(通用版)
    这一年,做为asp.net程序员我合格吗?
    基于jquery的滚动条滚动固定div(附Demo)
    asp.net获取数据随机显示(原创)
    为昨天一篇博文【asp.net,对于一个有点经验的猴子,我的要求高么?】做点解释
    2012年总结,2013年更精彩。
    放大镜
    be strong
    模拟凡客导航
    Ajax中Get请求与Post请求的区别(转载)
  • 原文地址:https://www.cnblogs.com/XDU-Lakers/p/14349788.html
Copyright © 2011-2022 走看看