您好,欢迎来到测品娱乐。
搜索
您的当前位置:首页【Spring MVC】Spring MVC(Interceptor)

【Spring MVC】Spring MVC(Interceptor)

来源:测品娱乐


一、介绍

Spring MVC中提供了处理器组件(Interceptor),在 Spring MVC 中的地位等同于 Servlet 规范中的过滤器 过滤器(Filter),用于对处理器进行预处理和后处理。

拦截的是处理器的执行,由于是全局行为,因此常用于做一些通用的功能,例如:

本质也是AOP(面向切面编程),也就是说符合横切关注点的所有功能都可以放入实现。

我们把 Spring MVC DispatcherServlet 请求处理流程这张图拿出来。

当浏览器发起的请求到达 Servlet 容器,DispatcherServlet 先根据处理器映射器 HandlerMapping 获取处理器,这时候获取到的是一个包含处理器和的处理器执行链,处理器执行之前将会先执行。

不包含的情况下,DispatcherServlet 处理请求的流程可以简化如下:

添加了做登录检查后,DispatcherServlet 请求处理的流程可以简化如下:

二、 Interceptor 定义

2.1 HandlerInterceptor接口

事实上的执行流程远比上述 DispatcherServelt 简化后的流程图复杂,它不仅可以在处理器之前执行,还可以在处理器之后执行。先看 Interceptor 在 Spring MVC 中的定义,Spring MVC的顶级接口为HandleInterceptor,定义了三个方法:1、preHandle(请求前) 2、postHandle(请求提交) 3、afterCompletion(请求完成后拦截)

public interface HandlerInterceptor {

    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        return true;
    }

    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            @Nullable ModelAndView modelAndView) throws Exception {
    }

    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
            @Nullable Exception ex) throws Exception {
    }   
}

我们可能注意到一共有3个回调方法,而一般的过滤器Filter才两个,这是怎么回事呢?马上分析。

  • preHandle:预处理回调方法,实现处理器的预处理(如登录检查),第三个参数为响应的处理器(如Controller实现);
    • 返回值:true表示继续流程(如调用下一个或处理器);false表示流程中断(如登录检查失败),不会继续调用其他的或处理器,此时我们需要通过response来产生响应;
  • postHandle:后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为null。
  • afterCompletion:整个请求处理完毕回调方法,即在视图渲染完毕时回调,如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finally,但仅调用处理器执行链中preHandle返回true的的afterCompletion。

通过源码我们还可以发现,这三个方法都有的 handler 参数表示处理器,通常情况下可以表示我们使用注解 @Controller 定义的控制器。

对上面的流程图继续细化:

三个方法具体的执行流程如下:

  1. preHandle:处理器执行之前执行,如果返回 false 将跳过处理器、 postHandle 方法、视图渲染等,直接执行 afterCompletion 方法。
  2. postHandle:处理器执行后,视图渲染前执行,如果处理器抛出异常,将跳过该方法直接执行 afterCompletion 方法。
  3. afterCompletion:视图渲染后执行,不管处理器是否抛出异常,该方法都将执行。

注意:自从前后端分离之后,Spring MVC 中的处理器方法执行后通常不会再返回视图,而是返回表示 json 或 xml 的对象,@Controller 方法返回值类型如果为 ResponseEntity 或标注了 @ResponseBody 注解,此时处理器方法一旦执行结束,Spring 将使用 HandlerMethodReturnValueHandler 对返回值进行处理,具体来说会将返回值转换为 json 或 xml,然后写入响应,后续也不会进行视图渲染,这时postHandle 将没有机会修改响应体内容

如果需要更改响应内容,可以定义一个实现 ResponseBodyAdvice 接口的类,然后将这个类直接定义到 RequestMappingHandlerAdapter 中的 requestResponseBodyAdvice 或通过 @ControllerAdvice 注解添加到 RequestMappingHandlerAdapter。

2.2 Spring MVC中提供的一些HandlerInterceptor接口实现类

1AsyncHandlerInterceptor

继承HandlerInterceptor的接口,额外提供了afterConcurrentHandlingStarted方法,该方法是用来处理异步请求。当Controller中有异步请求方法的时候会触发该方法。 经过测试,异步请求先支持preHandle、然后执行afterConcurrentHandlingStarted。异步线程完成之后执行postHandle、afterCompletion。 有兴趣的读者可自行研究。

2WebRequestInterceptor

与HandlerInterceptor接口类似,区别是WebRequestInterceptor的preHandle没有返回值。还有WebRequestInterceptor是针对请求的,接口方法参数中没有response。

public interface WebRequestInterceptor {
    
    void preHandle(WebRequest request) throws Exception;
    void postHandle(WebRequest request, @Nullable ModelMap model) throws Exception;
    void afterCompletion(WebRequest request, @Nullable Exception ex) throws Exception;
}

AbstractHandlerMapping内部的interceptors是个Object类型集合。处理的时候判断为MappedInterceptor[加入到mappedInterceptors集合中];HandlerInterceptor、WebRequestInterceptor(适配成WebRequestHandlerInterceptorAdapter)[加入到adaptedInterceptors中]

3MappedInterceptor

4ConversionServiceExposingInterceptor

默认的<annotation-driven/>标签初始化的时候会初始化ConversionServiceExposingInterceptor这个,并被当做构造方法的参数来构造MappedInterceptor。之后会被加入到AbstractHandlerMapping的mappedInterceptors集合中。该会在每个请求之前往request中丢入ConversionService。主要用于spring:eval标签的使用。

三、 Interceptor 使用及配置

3.1 实现

使用需要实现 HandlerInterceptor 接口,为了避免实现该接口的所有方法,Spring 5 之前提供了一个抽象的实现 HandlerInterceptorAdapter,Java 8 接口默认方法新特性出现后,我们直接实现 HandlerInterceptor 接口即可。

我们实现一个,示例如下:

public class LogInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("请求来了");
		 // ture表示放行
        return true;
    }
}

Spring 配置通常有三种方式,分别是传统的 xml 、最新的注解配置以及通过 API 配置,也不例外。

3.2 配置

3.2.1 xml 文件配置

springmvc.xml 配置方式如下:

<mvc:interceptors >
    <!-- 将类添加到Spring容器 -->
    <bean class="com.zzuhkp.mvc.interceptor.LogInterceptor"/>
    <!-- 设置 -->
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <mvc:exclude-mapping path="/login"/>
        <bean class="com.zzuhkp.mvc.interceptor.LoginInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>
  • bean:mvc:interceptors 标签下的 bean 将应用到所有的处理器。
  • mvc:interceptor:这个标签下的子标签可以指定应用到哪些请求路径。
    • mvc:mapping:指定要处理的请求路径。
    • mvc:exclude-mapping:指定要排除的请求路径。
    • bean:指定要应用到给定路径的 bean。

<mvc:interceptors>这个标签是被InterceptorsBeanDefinitionParser类解析。

这里配置的每个<mvc:interceptor>都会被解析成MappedInterceptor类型的Bean。

其中

  • 子标签<mvc:mapping path="/**"/>会被解析成MappedInterceptor的includePatterns属性;
  • <mvc:exclude-mapping path="/**"/>会被解析成MappedInterceptor的excludePatterns属性;
  • <bean/>会被解析成MappedInterceptor的interceptor属性。

3.2.2 注解配置

对于注解配置来说,需要将 MappedInterceptor 配置为 Spring 的 bean,和上述 xml 配置等价的注解配置如下:

// Configuration配置类
@Configuration
public class MvcConfig {
    // 将logInterceptor添加到Spring容器
    @Bean
    public MappedInterceptor logInterceptor() {
        return new MappedInterceptor(null, new LoginInterceptor());
    }

    // 将loginInterceptor添加到Spring容器
    @Bean
    public MappedInterceptor loginInterceptor() {
        // 在MappedInterceptor构造方法中可以传入的配置信息
        return new MappedInterceptor(new String[]{"/**"}, new String[]{"/login"}, new LoginInterceptor());
    }
}

MappedInterceptor实现了HandlerInterceptor接口。可用来设置的配置信息。

3.2.3 API 配置

与 Spring MVC 环境紧密结合,并且是作用范围通常是全局性的,因此大多数情况建议使用这种方式配置。

与 xml 配置对应的 API 配置如下:

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
    
    // 重写添加的方法,来注册
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册
        registry.addInterceptor(new LogInterceptor());
        // 可传入配置信息
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").excludePathPatterns("/login");
    }
}

这里在配置类上添加了@EnableWebMvc注解开启了 Spring MVC 中的某些特性,然后就可以实现 WebMvcConfigurer 接口中的 addInterceptors 方法向 Spring MVC 中添加。如果你使用了 spring-boot-starter-web,不再需要手工添加 @EnableWebMvc 注解。

四、 Interceptor 的执行顺序

通常情况下,我们并不需要关心多个的执行顺序,然而,如果一个依赖于另一个的执行结果,那么就需要注意了。使用多个后的 DispatcherServlet 请求处理流程可以简化为如下的流程图。

多个方法执行顺序如下:

  1. preHandle 按照的顺序先后执行。如果任意一次调用返回 false 则直接跳到的 afterCompletion 执行。
  2. postHandle 按照的逆序先后执行,也就说后面的先执行 postHandle。
  3. afterCompletion 也按照的逆序先后执行,后面的先执行 afterCompletion。

中断情况实例:

1 正常流程 

2 中断流程

中断流程中,比如是HandlerInterceptor2中断的流程(preHandle返回false),此处仅调用它之前的preHandle返回true的afterCompletion方法。 这个底层原理看后面的源码分析就明白了,主要是由this.interceptorIndex这个变量控制的。

那么的顺序是如何指定的呢?

  • 对于 xml 配置来说,Spring 将记录 bean 声明的顺序,先声明的将排在前面。
  • 对于注解配置来说,由于通过反射读取方法无法保证顺序,因此需要在方法上添加@Order注解指定 bean 的声明顺序。
  • 对应API配置来说,的顺序并非和添加顺序完全保持一致,为了控制先后顺序,需要自定义的实现Ordered接口。

注解配置指定顺序示例如下:

@Configuration
public class MvcConfig {
    @Order(2)
    @Bean
    public MappedInterceptor loginInterceptor() {
        return new MappedInterceptor(new String[]{"/**"}, new String[]{"/login"}, new LoginInterceptor());
    }
    
    @Order(1)
    @Bean
    public MappedInterceptor logInterceptor() {
        return new MappedInterceptor(null, new LoginInterceptor());
    }
    
}

此时虽然登录写在前面,但因为 @Order 注解指定的值较大,因此将排在日志的后面。

API配置指定顺序示例如下:

public class LoginInterceptor implements HandlerInterceptor, Ordered {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("已登录");
        return true;
    }

    @Override
    public int getOrder() {
        return 2;
    }
}

public class LogInterceptor implements HandlerInterceptor, Ordered {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("请求来了");
        return true;
    }

    @Override
    public int getOrder() {
        return 1;
    }
}

LogInterceptor 指定的排序号较 LoginInterceptor 来说比较小,因此 LogInterceptor 将排在前面。

五、 Interceptor 原理分析

DispatcherServlet 处理请求的代码位于 DispatcherServlet#doDispatch 方法,关于处理器和简化后的代码如下:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    //...
       HandlerExecutionChain mappedHandler = null;
 
    try {
        try {
            ModelAndView mv = null;
            Object dispatchException = null;
            try {
                processedRequest = this.checkMultipart(request);
                multipartRequestParsed = processedRequest != request;
                // 1.获取处理器执行链(从 HandlerMapping 获取处理器链)
                mappedHandler = this.getHandler(processedRequest);
                if (mappedHandler == null) {
                    this.noHandlerFound(processedRequest, response);
                    return;
                }
                // 2.获取处理器适配器
                HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                //...
                //【3】.执行前置( preHandle 执行(正序))
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
                // 4.执行业务handler
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                this.applyDefaultViewName(processedRequest, mv);
                //【5】.执行后置( postHandle 执行(逆序))
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            } catch (Exception var20) {
                dispatchException = var20;
            } catch (Throwable var21) {
                dispatchException = new NestedServletException("Handler dispatch failed", var21);
            }
            //【6】.渲染视图,处理页面响应,同时也会去执行最终( afterCompletion 执行(逆序))
            this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
        } catch (Exception var22) {
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
        } catch (Throwable var23) {
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
        }
    }finally {
        //...
    }
}

可以看到,整体流程和我们前面描述是保持一致的。【3】、【5】、【6】步骤是对的执行处理,现在分别来查看第【3】、【5】、【6】步骤执行的具体方法的源码

5.1 applyPreHandle()执行 preHandle 方法

以预执行 preHandle 为例,看一下处理器执行链是怎么调用方法的。

HandlerExecutionChain.java

// 3.执行前置中的详细代码
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 获得本次请求对应的所有
    // getInterceptors()是HandlerExecutionChain中的方法,获取到的是当前处理器执行链中所有的,也就是和当前请求的处理器映射器绑定在一起的所有
    // 说明获得的都是用来拦截本次请求的,不会有别的请求的
    HandlerInterceptor[] interceptors = this.getInterceptors();

    if (!ObjectUtils.isEmpty(interceptors)) {
        // 按照顺序依次执行每个的preHandle方法。
        // 并且,interceptorIndex值会一次 + 1 (该值是给后面的最终使用的)
        for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
            HandlerInterceptor interceptor = interceptors[i];
            // 只要每个不返回false,则继续执行,否则执行最终
            if (!interceptor.preHandle(request, response, this.handler)) {
                // 返回 false 则直接执行 afterCompletion
                this.triggerAfterCompletion(request, response, (Exception)null);
                return false;
            }
        }
    }
    // 最终返回true
    return true;
}

处理器链拿到列表后按照顺序(1、2)调用了的 preHandle 方法,如果返回 false 则跳到 afterCompletion 执行。

那处理器链中的的列表从哪来的呢?继续跟踪获取处理器链的方法DispatcherServlet#getHandler,可以发现获取处理器链的核心代码如下:

public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
        implements HandlerMapping, Ordered, BeanNameAware {

    protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {

        HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
                (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
        String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
        // 将与当前处理器映射器绑定在一起的添加到处理器执行链中。this.adaptedInterceptors是AbstractHandlerMapping中的列表
        for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
            // 将通过注解创建的添加到处理器执行链中(MappedInterceptor类型的)
            if (interceptor instanceof MappedInterceptor) {
                MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
                if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                    // 将与该处理器映射器绑定在一起的都加入到当前处理器执行链中
                    chain.addInterceptor(mappedInterceptor.getInterceptor());
                }
            // 将通过其他方式创建的添加到处理器执行链中
            } else {
                // 将与该处理器映射器绑定在一起的都加入到当前处理器执行链中
                chain.addInterceptor(interceptor);
            }
        }
        return chain;
    }
}

上面的源码显示Spring 创建处理器执行链 HandlerExecutionChain 后将 AbstractHandlerMapping 中列表 adaptedInterceptors 中的添加到了处理器执行链,那 AbstractHandlerMapping 中的列表中的又从哪来呢?

public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
        implements HandlerMapping, Ordered, BeanNameAware {
    private final List<Object> interceptors = new ArrayList<>();
    private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<>();
    
    // 该方法在创建HandlerMapping时,Spring会自动回调
    @Override
    protected void initApplicationContext() throws BeansException {
        extendInterceptors(this.interceptors);
        // 从Spring容器中获取全部
        detectMappedInterceptors(this.adaptedInterceptors);
        // 对进行适配,并且将其添加到AbstractHandlerMapping的adaptedInterceptors列表中
        initInterceptors();
    }

    // 从容器中获取
    protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
        mappedInterceptors.addAll(
                BeanFactoryUtils.beansOfTypeIncludingAncestors(
                        obtainApplicationContext(), MappedInterceptor.class, true, false).values());
    }

    // 适配
    protected void initInterceptors() {
        if (!this.interceptors.isEmpty()) {
            for (int i = 0; i < this.interceptors.size(); i++) {
                Object interceptor = this.interceptors.get(i);
                if (interceptor == null) {
                    throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
                }
                // 将添加到AbstractHandlerMapping的adaptedInterceptors列表中
                this.adaptedInterceptors.add(adaptInterceptor(interceptor));
            }
        }
    }       
}

各种 HandlerMapping 的实现都继承了 AbstractHandlerMapping,HandlerMapping 被容器创建时将回调#initApplicationContext方法,这个方法回调时会从容器中查找类型为 MappedInterceptor 的,然后对进行适配,这个流程是针对使用注解来实现的(MappedInterceptor类型)。Spring MVC 中如果使用了 @EnableWebMvc ,HandlerMapping bean 被创建时会回调WebMvcConfigurer#addInterceptors方法直接将设置到 AbstractHandlerMapping 中的 interceptors成员属性中。

MappedInterceptor类型的会被加到mappedInterceptors集合中,HandlerInterceptor类型的会被加到adaptedInterceptors集合中,WebRequestInterceptor类型的会被适配成WebRequestHandlerInterceptorAdapter加到adaptedInterceptors集合中。

5.2 applyPostHandle()执行 postHandle 方法

HandlerExecutionChain.java

// 5.执行后置
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
    // 获得本次请求对应的所有
    HandlerInterceptor[] interceptors = this.getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        // 按逆序执行每个的postHandle方法,所以我们看到先执行的2的postHandle,再执行1的postHandle
        for(int i = interceptors.length - 1; i >= 0; --i) {
            HandlerInterceptor interceptor = interceptors[i];
            // 执行的postHandle方法
            interceptor.postHandle(request, response, this.handler, mv);
        }
    }
}

后置处理是按照顺序逆序处理的。

5.3 processDispatchResult()执行 afterCompletion 方法

DispatcherServlet.java

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
    //...
    if (mv != null && !mv.wasCleared()) {
        // 处理响应,进行视图渲染
        this.render(mv, request, response);
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    } else if (this.logger.isDebugEnabled()) {
        this.logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + this.getServletName() + "': assuming HandlerAdapter completed request handling");
    }

    if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        if (mappedHandler != null) {
            // 6、执行最终
            mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
        }
    }
}

其中,有一个render()方法,该方法会直接处理完response。然后则是触发triggerAfterCompletion方法去执行本次请求对应的所有的afterCompletion 方法:

HandlerExecutionChain.java

// 6、执行的最终方法
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) throws Exception {
    // 获得本次请求对应的所有
    HandlerInterceptor[] interceptors = this.getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        // 逆序执行每个(interceptorIndex为前置动态计算)的afterCompletion方法
        // 由于this.interceptorIndex当前标记着最后一个执行到的前置下表,然后这里又是逆序遍历,所以只有成功执行了preHandle方法的,才回去执行其对应的afterCompletion方法
        for(int i = this.interceptorIndex; i >= 0; --i) {
            HandlerInterceptor interceptor = interceptors[i];
            try {
                // 执行的afterCompletion方法
                interceptor.afterCompletion(request, response, this.handler, ex);
            } catch (Throwable var8) {
                logger.error("HandlerInterceptor.afterCompletion threw exception", var8);
            }
        }
    }
}

由此可以看到,的最终方法的执行也是按照倒叙来执行的,而且是在视图渲染之后。

六、的应用

这里我们讲几个最常见的应用。

6.1 性能监控

如记录一下请求的处理时间,得到一些慢请求(如处理时间超过500毫秒),从而进行性能改进,一般的反向代理服务器如apache都具有这个功能,但此处我们演示一下使用怎么实现。 

实现分析:

  1. 在进入处理器之前记录开始时间,即在的preHandle记录开始时间;
  2. 在结束请求处理之后记录结束时间,即在的afterCompletion记录结束实现,并用结束时间-开始时间得到这次请求的处理时间。

问题:

我们的是单例的,因此不管用户请求多少次都只有一个实现,即线程不安全,那我们应该怎么记录时间呢?

解决方案是使用ThreadLocal,它是线程绑定的变量,提供线程局部变量(一个线程一个ThreadLocal,A线程的ThreadLocal只能看到A线程的ThreadLocal,不能看到B线程的ThreadLocal)。 

代码实现:

public class StopWatchHandlerInterceptor extends HandlerInterceptorAdapter {  
    
    private NamedThreadLocal<Long>  startTimeThreadLocal =  new NamedThreadLocal<Long>("StopWatch-StartTime");  
    @Override  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1、开始时间    
        long beginTime = System.currentTimeMillis();
        // 线程绑定变量(该数据只有当前请求的线程可见)  
        startTimeThreadLocal.set(beginTime);
        // 继续流程  
        return true;
    }  
      
    @Override  
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 2、结束时间    
        long endTime = System.currentTimeMillis();
        // 得到线程绑定的局部变量(开始时间)  
        long beginTime = startTimeThreadLocal.get();
        // 3、消耗的时间  
        long consumeTime = endTime - beginTime;
        // 此处认为处理时间超过500毫秒的请求为慢请求  
        if(consumeTime > 500) {
            // TODO 记录到日志文件  
            System.out.println(String.format("%s consume %d millis", request.getRequestURI(), consumeTime));  
        }          
    }  
}

NamedThreadLocal:Spring提供的一个命名的ThreadLocal实现。 

在测试时需要把stopWatchHandlerInterceptor的排序设置成1,也就是放在链的第一个,这样得到的时间才是比较准确的。 

6.2 登陆检测

在访问某些资源时(如订单页面),需要用户登录后才能查看,因此需要进行登录检测。 

流程:

  1. 访问需要登录的资源时,由重定向到登录页面;
  2. 如果访问的是登录页面,不应该拦截;
  3. 用户登录成功后,往cookie/session添加登录成功的标识(如用户编号);
  4. 下次请求时,通过判断cookie/session中是否有该标识来决定继续流程还是到登录页面;
  5. 在此还应该允许游客访问的资源。 

代码如下所示:

@Override  
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  
    // 1、请求到登录页面则放行  
    if(request.getServletPath().startsWith(loginUrl)) {  
        return true;  
    }  
          
    // 2、TODO 比如退出、首页等页面无需登录,即此处要放行 允许游客的请求  
          
    // 3、如果用户已经登录则放行    
    if(request.getSession().getAttribute("username") != null) {  
        // 更好的实现方式是使用cookie  
        return true;  
    }  
          
    // 4、非法请求,即这些请求需要登录后才能访问  
    // 重定向到登录页面  
    response.sendRedirect(request.getContextPath() + loginUrl);  
    return false;  
}

提示:推荐能使用servlet规范中的过滤器Filter实现相关功能的话,最好用Filter实现,因为HandlerInteceptor只有在Spring Web MVC环境下才能使用,因此Filter是最通用的、最先应该使用的。如登录这种最好使用Filter来实现。

七、总结

常用于初始化资源,权限监控,会话设置,资源清理等的功能设置。我们通过源码可以看到,类似于对我们业务方法的环绕通知效果,并且是通过循环收集好的集合来控制每个方法的执行顺序。要熟练运用,就需要我们对它的执行顺序完全掌握,做到深入掌握的执行机制!

总结 Spring MVC 整个相关的流程如下:

  1. HandlerMapping 被容器实例化并初始化。
    1. 初始化时默认从容器中查找类型为 MappedInterceptor 的添加到 HandlerMapping 中的列表,这种默认行为支持了 xml 和注解配置。
    2. 使用 @EnableWebMvc 注解后,Spring 通过 @Bean 创建 HandlerMapping bean,实例化后回调 WebMvcConfigurer#addInterceptors 将提前设置到 HandlerMapping 中的列表,这种行为支持了 API 配置。
  2. 客户端发起请求,DispatcherServlet 使用 HandlerMapping 查找处理器执行链,将 HandlerMapping 中的添加到处理器执行链 HandlerExecutionChain 中的列表。
  3. DispatcherServlet 按照的顺序依次调用中的回调方法。

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- cepb.cn 版权所有 湘ICP备2022005869号-7

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务