SpringMVC工作原理和自定义拦截器

SpringMVC的工作原理图:

在这里插入图片描述
在这里插入图片描述

SpringMVC流程

1、 用户发送请求至前端控制器DispatcherServlet。

2、 DispatcherServlet收到请求调用HandlerMapping处理器映射器。

3、 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。

4、 DispatcherServlet调用HandlerAdapter处理器适配器。

5、 HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。

6、 Controller执行完成返回ModelAndView。

7、 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。

8、 DispatcherServlet将ModelAndView传给ViewReslover视图解析器。

9、 ViewReslover解析后返回具体View。

10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。

11、 DispatcherServlet响应用户。

组件说明:

以下组件通常使用框架提供实现:

DispatcherServlet:作为前端控制器,整个流程控制的中心,控制其它组件执行,统一调度,降低组件之间的耦合性,提高每个组件的扩展性。

HandlerMapping:通过扩展处理器映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。

HandlAdapter:通过扩展处理器适配器,支持更多类型的处理器。

ViewResolver:通过扩展视图解析器,支持更多类型的视图解析,例如:jsp、freemarker、pdf、excel等。

组件:

1、前端控制器DispatcherServlet(不需要工程师开发),由框架提供
作用:接收请求,响应结果,相当于转发器,中央处理器。有了dispatcherServlet减少了其它组件之间的耦合度。
用户请求到达前端控制器,它就相当于mvc模式中的c,dispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet的存在降低了组件之间的耦合性。

2、处理器映射器HandlerMapping(不需要工程师开发),由框架提供
作用:根据请求的url查找Handler
HandlerMapping负责根据用户请求找到Handler即处理器,springmvc提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。

3、处理器适配器HandlerAdapter
作用:按照特定规则(HandlerAdapter要求的规则)去执行Handler
通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。

4、处理器Handler(需要工程师开发)
注意:编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler
Handler 是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。
由于Handler涉及到具体的用户业务请求,所以一般情况需要工程师根据业务需求开发Handler。

5、视图解析器View resolver(不需要工程师开发),由框架提供
作用:进行视图解析,根据逻辑视图名解析成真正的视图(view)
View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。 springmvc框架提供了很多的View视图类型,包括:jstlView、freemarkerView、pdfView等。
一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由工程师根据业务需求开发具体的页面。

6、视图View(需要工程师开发jsp…)
View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf…)

核心架构的具体流程步骤如下:

1、首先用户发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;
2、DispatcherServlet——>HandlerMapping, HandlerMapping 将会把请求映射为HandlerExecutionChain 对象(包含一个Handler 处理器(页面控制器)对象、多个HandlerInterceptor 拦截器)对象,通过这种策略模式,很容易添加新的映射策略;
3、DispatcherServlet——>HandlerAdapter,HandlerAdapter 将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;
4、HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter 将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个ModelAndView 对象(包含模型数据、逻辑视图名);
5、ModelAndView的逻辑视图名——> ViewResolver, ViewResolver 将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;
6、View——>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支持其他视图技术;
7、返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。
下边两个组件通常情况下需要开发:
Handler:处理器,即后端控制器用controller表示。

View:视图,即展示给用户的界面,视图中通常需要标签语言展示模型数据。

如下图:
在这里插入图片描述

SpringMVC自定义拦截器

1.SpringMVC拦截器

1.1 拦截器简介

Spring web MVC的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器 进行预处理和后处理。

1.2 常见应用场景

1、日志记录 :记录请求信息的日志
2、权限检查,如登录检查
3、性能检测:检测方法的执行时间

1.2.1 日志记录

package com.yaspeed.web.interceptor;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.NamedThreadLocal;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

/**
 * 日志拦截器 <br>
 * 记录信息:访问时间-Controller路径-对应方法名-请求参数信息-请求相对路径-请求处理时长
 * 
 * @author Administrator
 *
 */
public class LogInterceptor implements HandlerInterceptor {
    
    
    public static final Logger LOGGER = LoggerFactory.getLogger(LogInterceptor.class);
    private static final ThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<Long>("ThreadLocal StartTime");

    private String getParamString(Map<String, String[]> map) {
    
    
        StringBuilder sb = new StringBuilder();
        for (Entry<String, String[]> e : map.entrySet()) {
    
    
            sb.append(e.getKey()).append("=");
            String[] value = e.getValue();
            if (value != null && value.length == 1) {
    
    
                sb.append(value[0]).append("\t");
            } else {
    
    
                sb.append(Arrays.toString(value)).append("\t");
            }
        }
        return sb.toString();
    }

    /**
     * 将ErrorStack转化为String.
     */
    public static String getStackTraceAsString(Throwable e) {
    
    
        if (e == null) {
    
    
            return "";
        }
        StringWriter stringWriter = new StringWriter();
        e.printStackTrace(new PrintWriter(stringWriter));
        return stringWriter.toString();
    }

    @Override
    /**
     * 该方法将在请求处理之前进行调用<br>
     * 多个Interceptor,然后在SpringMVC会根据声明的前后顺序一个接一个的执行,而且所有的Interceptor中的preHandle方法都会在<br>
     * COntroller方法之前调用。SpringMVC的这种Interceptor链式结构也是可以中断的,这种中断方式时令preHandler的返回值为false<br>
     * 当prehandler的返回值为false的时候整个请求就结束了。
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
    
    
        long startTime = System.currentTimeMillis();
        request.setAttribute("startTime", startTime);
        startTimeThreadLocal.set(startTime); // 线程绑定变量(该数据只有当前请求的线程可见)
        if (HandlerMethod.class.equals(handler.getClass())) {
    
    
            StringBuilder sb = new StringBuilder(1000);
            sb.append("-----------------------开始计时:").append(new SimpleDateFormat("hh:mm:ss.SSS").format(startTime))
                    .append("-------------------------------------\n");
            HandlerMethod h = (HandlerMethod) handler;
            sb.append("Controller: ").append(h.getBean().getClass().getName()).append("\n");
            sb.append("Method    : ").append(h.getMethod().getName()).append("\n");
            sb.append("Params    : ").append(getParamString(request.getParameterMap())).append("\n");
            sb.append("URI       : ").append(request.getRequestURI()).append("\n");
            LOGGER.debug(sb.toString());
        }
        return true;
    }

    /**
     * 在当前请求进行处理之后,也就是Controller 方法调用之后执行,但是它会在DispatcherServlet
     * 进行视图返回渲染之前被调用,所以我们可以在这个方法中对Controller 处理之后的ModelAndView 对象进行操作。
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
    
    
        long startTime = (Long) request.getAttribute("startTime");
        long endTime = System.currentTimeMillis();
        long executeTime = endTime - startTime;
        if (HandlerMethod.class.equals(handler.getClass())) {
    
    
            StringBuilder sb = new StringBuilder(1000);
            sb.append("CostTime  : ").append(executeTime).append("ms").append("\n");
            sb.append("-------------------------------------------------------------------------------");
            LOGGER.debug(sb.toString());
        }
    }

    /**
     * 该方法将在整个请求结束之后,也就是在DispatcherServlet 渲染了对应的视图之后执行。这个方法的主要作用是用于进行资源清理工作的。
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
    
    
        // 打印JVM信息。
        if (LOGGER.isDebugEnabled()) {
    
    
            long beginTime = startTimeThreadLocal.get();// 得到线程绑定的局部变量(开始时间)
            long endTime = System.currentTimeMillis(); // 2、结束时间

            // 如果controller报错,则记录异常错误
            if (ex != null) {
    
    
                LOGGER.debug("Controller异常: " + getStackTraceAsString(ex));
            }

            LOGGER.debug("计时结束:" + new SimpleDateFormat("hh:mm:ss.SSS").format(endTime) + " 耗时:" + (endTime - beginTime)
                    + " URI:" + request.getRequestURI());
            startTimeThreadLocal.remove();
        }
    }

方法1.springmvc.xml 中的配置

        <!-- 配置拦截器 -->  
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="com.yaspeed.web.interceptor.LogInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

方法2.拦截器配置

package com.cy.pj.common.config;

import com.cy.pj.common.web.LogInterceptor ;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

//拦截器配置
@Configuration
public class SpringWebConfig implements WebMvcConfigurer {
    
    
    //配置springMVC拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    

        registry.addInterceptor(new LogInterceptor ())
                .addPathPatterns("/user/doLogin");
    }
}

1.2.2 权限检查

/**
 * 权限检查
 * 
 * @author Administrator
 *
 */
public class PermissionInterceptor implements HandlerInterceptor {
    
    

    // 在执行handler之前来执行的
    // 用于用户认证校验、用户权限校验
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
    
    

        // 得到请求的url
        String url = request.getRequestURI();

        // 判断是否是公开 地址
        // 实际开发中需要公开 地址配置在配置文件中
        // 从配置中取逆名访问url
        List<String> open_urls = ResourcesUtil.gekeyList("anonymousURL");
        // 遍历公开 地址,如果是公开 地址则放行
        for (String open_url : open_urls) {
    
    
            if (url.indexOf(open_url) >= 0) {
    
    
                // 如果是公开 地址则放行
                return true;
            }
        }

        // 从配置文件中获取公共访问地址
        List<String> common_urls = ResourcesUtil.gekeyList("commonURL");
        // 遍历公用 地址,如果是公用 地址则放行
        for (String common_url : common_urls) {
    
    
            if (url.indexOf(common_url) >= 0) {
    
    
                // 如果是公开 地址则放行
                return true;
            }
        }

        // 获取session
        HttpSession session = request.getSession();
        ActiveUser activeUser = (ActiveUser) session.getAttribute("activeUser");
        // 从session中取权限范围的url
        List<SysPermission> permissions = activeUser.getPermissions();
        for (SysPermission sysPermission : permissions) {
    
    
            // 权限的url
            String permission_url = sysPermission.getUrl();
            if (url.indexOf(permission_url) >= 0) {
    
    
                // 如果是权限的url 地址则放行
                return true;
            }
        }
        // 执行到这里拦截,跳转到无权访问的提示页面
        request.getRequestDispatcher("/WEB-INF/jsp/refuse.jsp").forward(request, response);
        // 如果返回false表示拦截不继续执行handler,如果返回true表示放行
        return false;
    }

    // 在执行handler返回modelAndView之前来执行
    // 如果需要向页面提供一些公用 的数据或配置一些视图信息,使用此方法实现 从modelAndView入手
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
    
    
        System.out.println("HandlerInterceptor1...postHandle");

    }

    // 执行handler之后执行此方法
    // 作系统 统一异常处理,进行方法执行性能监控,在preHandle中设置一个时间点,在afterCompletion设置一个时间,两个时间点的差就是执行时长
    // 实现 系统 统一日志记录
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
    
    
        System.out.println("HandlerInterceptor1...afterCompletion");
    }

}

1.2.3 性能检测

/**
 * 实现统计应用性能 拦截器 的实现是单例的,因此不管用户请求多少次 都 只访问一个拦截器实例,即线程不安全<br>
 * 解决方案:使用ThreadLocal,它是线程绑定的变量,提供线程局部变量 (一个线程一个ThreadLocal)
 * 
 * @author Administrator
 *
 */
public class TimeInterceptor implements HandlerInterceptor {
    
    

    public static final Logger logger = LoggerFactory.getLogger(TimeInterceptor.class);

    // 统计应用性能
    private NamedThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<>("StopWatch-StartTime");

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
    
    
        // 1,开始时间
        long startTime = System.currentTimeMillis();
        // 线程绑定变量(该数据只有当前请求的线程可见)
        startTimeThreadLocal.set(startTime);

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
    
    

    }

    @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;

        logger.debug("监控==========================: "
                + String.format("%s consume %d millis", request.getRequestURI(), consumeTime));
        startTimeThreadLocal.remove();

    }

}

猜你喜欢

转载自blog.csdn.net/m0_49353216/article/details/109077616