How SpringMVC works and custom interceptors

SpringMVC working principle diagram:

Insert image description here
Insert image description here

SpringMVC process

1. The user sends a request to the front-end controller DispatcherServlet.

2. DispatcherServlet receives the request and calls the HandlerMapping processor mapper.

3. The processor mapper finds the specific processor (can be searched based on xml configuration and annotations), generates the processor object and processor interceptor (generated if any) and returns them to DispatcherServlet.

4. DispatcherServlet calls HandlerAdapter processor adapter.

5. HandlerAdapter calls the specific processor (Controller, also called back-end controller) through adaptation.

6. Controller returns to ModelAndView after execution is completed.

7. HandlerAdapter returns the controller execution result ModelAndView to DispatcherServlet.

8. DispatcherServlet passes ModelAndView to ViewReslover view resolver.

9. ViewReslover returns the specific View after parsing.

10. DispatcherServlet renders the view based on the View (that is, fills the model data into the view).

11. DispatcherServlet responds to the user.

Component Description:

The following components are typically implemented using frameworks:

DispatcherServlet: As a front-end controller, the center of the entire process control, it controls the execution of other components, unifies scheduling, reduces the coupling between components, and improves the scalability of each component.

HandlerMapping: Implement different mapping methods by extending the processor mapper, such as configuration file method, interface implementation method, annotation method, etc.

HandlAdapter: Supports more types of processors by extending the processor adapter.

ViewResolver: By extending the view resolver, it supports more types of view resolution, such as: jsp, freemarker, pdf, excel, etc.

Components:

1. Front-end controller DispatcherServlet (no need for engineers to develop), provided by the framework.
Function: receive requests, respond to results, equivalent to forwarder, central processor. With dispatcherServlet, the coupling between other components is reduced.
When the user request reaches the front-end controller, it is equivalent to c in the MVC mode. The dispatcherServlet is the center of the entire process control. It calls other components to handle the user's request. The existence of the dispatcherServlet reduces the coupling between components.

2. Handler Mapping (no need for engineers to develop), provided by the framework.
Function: Find the Handler according to the requested URL.
HandlerMapping is responsible for finding the Handler, that is, the processor according to the user's request. Springmvc provides different mappers to implement different mapping methods, such as : Configuration file method, interface implementation method, annotation method, etc.

3.
The function of the processor adapter HandlerAdapter: to execute the Handler according to specific rules (the rules required by the HandlerAdapter) to
execute the processor through the HandlerAdapter. This is the application of the adapter mode. By extending the adapter, more types of processors can be executed.

4. Processor Handler (requires engineer development)
Note: When writing Handler, follow the requirements of HandlerAdapter, so that the adapter can correctly execute the Handler.
Handler is the back-end controller following the DispatcherServlet front-end controller. Under the control of DispatcherServlet, the Handler Specific user requests are processed.
Since Handler involves specific user business requests, engineers generally need to develop Handler according to business needs.

5. View resolver (no need for engineers to develop), provided by the framework.
Function: perform view resolution, and resolve it into a real view (view) based on the logical view name.
View Resolver is responsible for generating the View view from the processing results. View Resolver first based on the logic The view name is parsed into the physical view name, which is the specific page address, and then the View object is generated. Finally, the View is rendered and the processing results are displayed to the user through the page. The springmvc framework provides many View types, including: jstlView, freemarkerView, pdfView, etc.
Generally, model data needs to be displayed to users through page tags or page template technology, and engineers need to develop specific pages based on business needs.

6. View View (engineers are required to develop jsp...)
View is an interface, and the implementation class supports different View types (jsp, freemarker, pdf...)

The specific process steps of the core architecture are as follows:

1. First, the user sends a request——>DispatcherServlet. After receiving the request, the front-end controller does not process it, but delegates it to other parsers for processing. It serves as a unified access point for global process control; 2.
DispatcherServlet—— >HandlerMapping, HandlerMapping will map the request to a HandlerExecutionChain object (including a Handler processor (page controller) object, multiple HandlerInterceptor interceptors) object. Through this strategy mode, it is easy to add new mapping strategies; 3.
DispatcherServlet ——>HandlerAdapter, HandlerAdapter will package the processor as an adapter to support multiple types of processors, that is, the application of the adapter design pattern, making it easy to support many types of processors; 4. HandlerAdapter——>Processor
functions When calling the processing method, the HandlerAdapter will call the function processing method of the real processor according to the adaptation result to complete the function processing; and return a ModelAndView object (including model data and logical view name); 5. The logical view name of ModelAndView
- —> ViewResolver, ViewResolver will resolve the logical view name into a specific View. Through this strategy mode, it is easy to replace other view technologies;
6. View —> Rendering, View will render according to the incoming Model model data. This The Model at is actually a Map data structure, so it can easily support other view technologies;
7. Return control to DispatcherServlet, and DispatcherServlet returns a response to the user. This is the end of the process.
The following two components usually need to be developed:
Handler: Processor, that is, the back-end controller is represented by controller.

View: View is the interface displayed to the user. The view usually requires a label language to display model data.

As shown below:
Insert image description here

SpringMVC custom interceptor

1.SpringMVC interceptor

1.1 Introduction to interceptors

Spring web MVC's processor interceptor is similar to the filter in Servlet development, which is used to pre-process and post-process the processor.

1.2 Common application scenarios

1. Logging: Logs that record request information
2. Permission checks, such as login checks
3. Performance testing: Execution time of detection methods

1.2.1 Logging

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();
        }
    }

Method 1. Configuration in springmvc.xml

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

Method 2. Interceptor configuration

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 Permission check

/**
 * 权限检查
 * 
 * @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 Performance testing

/**
 * 实现统计应用性能 拦截器 的实现是单例的,因此不管用户请求多少次 都 只访问一个拦截器实例,即线程不安全<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();

    }

}

Guess you like

Origin blog.csdn.net/m0_49353216/article/details/109077616