Spring-boot的拦截器源码探寻

自己玩Springboot的时候,使用拦截器来实现用户的登陆验证。之前我在公司的项目中有自己做一个参数的校验的拦截器有兴趣的可以看看能给点一件更好,当然也希望能帮到你.

使用拦截器实现请求参数的简单校验

创建一个拦截器,实现接口HandlerInterceptor 这个接口的里边就方法。分别是preHandle、postHandle、afterCompletion.

为什么在接口中能够存在方法呢?因为

JDK1.8中为了加强接口的能力,使得接口可以存在具体的方法,前提是方法需要被default或static关键字所修饰。
所以,如果我们实现了接口缺什么接口也没实现也是可以的。先上源码

 

/**
	 * Intercept the execution of a handler. Called after HandlerMapping determined
	 * an appropriate handler object, but before HandlerAdapter invokes the handler.
	 * <p>DispatcherServlet processes a handler in an execution chain, consisting
	 * of any number of interceptors, with the handler itself at the end.
	 * With this method, each interceptor can decide to abort the execution chain,
	 * typically sending a HTTP error or writing a custom response.
	 * <p><strong>Note:</strong> special considerations apply for asynchronous
	 * request processing. For more details see
	 * {@link org.springframework.web.servlet.AsyncHandlerInterceptor}.
	 * <p>The default implementation returns {@code true}.
	 * @param request current HTTP request
	 * @param response current HTTP response
	 * @param handler chosen handler to execute, for type and/or instance evaluation
	 * @return {@code true} if the execution chain should proceed with the
	 * next interceptor or the handler itself. Else, DispatcherServlet assumes
	 * that this interceptor has already dealt with the response itself.
	 * @throws Exception in case of errors
	 */
	default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		return true;
	}

	/**
	 * Intercept the execution of a handler. Called after HandlerAdapter actually
	 * invoked the handler, but before the DispatcherServlet renders the view.
	 * Can expose additional model objects to the view via the given ModelAndView.
	 * <p>DispatcherServlet processes a handler in an execution chain, consisting
	 * of any number of interceptors, with the handler itself at the end.
	 * With this method, each interceptor can post-process an execution,
	 * getting applied in inverse order of the execution chain.
	 * <p><strong>Note:</strong> special considerations apply for asynchronous
	 * request processing. For more details see
	 * {@link org.springframework.web.servlet.AsyncHandlerInterceptor}.
	 * <p>The default implementation is empty.
	 * @param request current HTTP request
	 * @param response current HTTP response
	 * @param handler handler (or {@link HandlerMethod}) that started asynchronous
	 * execution, for type and/or instance examination
	 * @param modelAndView the {@code ModelAndView} that the handler returned
	 * (can also be {@code null})
	 * @throws Exception in case of errors
	 */
	default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable ModelAndView modelAndView) throws Exception {
	}

	/**
	 * Callback after completion of request processing, that is, after rendering
	 * the view. Will be called on any outcome of handler execution, thus allows
	 * for proper resource cleanup.
	 * <p>Note: Will only be called if this interceptor's {@code preHandle}
	 * method has successfully completed and returned {@code true}!
	 * <p>As with the {@code postHandle} method, the method will be invoked on each
	 * interceptor in the chain in reverse order, so the first interceptor will be
	 * the last to be invoked.
	 * <p><strong>Note:</strong> special considerations apply for asynchronous
	 * request processing. For more details see
	 * {@link org.springframework.web.servlet.AsyncHandlerInterceptor}.
	 * <p>The default implementation is empty.
	 * @param request current HTTP request
	 * @param response current HTTP response
	 * @param handler handler (or {@link HandlerMethod}) that started asynchronous
	 * execution, for type and/or instance examination
	 * @param ex exception thrown on handler execution, if any
	 * @throws Exception in case of errors
	 */
	default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable Exception ex) throws Exception {
	}

观看方法的注视可以知道,三个方法的作用范围,

其中起拦截作用的方法就是preHandle方法,该方法是在http请求了接口之后但在HandlerAdapter invokes 具体的handler之前。

而postHandle放法的作用是在handler处理之后,但在DispatcherServlet返回视图之前。

最后是afterCompletion方法作用在返回view之后。

知道了实现什么接口,在实现什么方法就好办了。

直接上代码

package com.shuguolili.product.interceptors;

import com.shuguolili.product.baseMsg.Contants;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

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

/**
 * @program Agriculture_Products
 * @description: 用户登陆校验的拦截器
 * @author: yang
 * @create: 2019/11/20 22:17
 */
@Component
public class LoginInterceptor implements HandlerInterceptor {

    private final Logger logger = LoggerFactory.getLogger(getClass());


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        logger.info("进入拦截器。。。");
       
        
        //在这进行想要做的判断,做你想做的操作。例如校验用户是否能够登陆

        return true;
    }
}

当然这才第一步了,我们还要配置拦截器的拦截路径以及白名单。

package com.shuguolili.product.config;

import com.shuguolili.product.interceptors.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

/**
 * @program Agriculture_Products
 * @description: 登陆拦截器的配置类
 * @author: yang
 * @create: 2019/11/20 22:35
 */
@Configuration
public class LoginWebConfig extends WebMvcConfigurationSupport {


    @Autowired
    private LoginInterceptor loginInterceptor;

    // 这个方法是用来配置静态资源的,比如html,js,css,等等
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
    }

    // 这个方法用来注册拦截器,我们自己写好的拦截器需要通过这里添加注册才能生效
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // addPathPatterns("/**") 表示拦截所有的请求,
        // excludePathPatterns("/login", "/register") 表示除了登陆与注册之外,因为登陆注册不需要登陆也可以访问,在配置白名单路径时不要带项目根路径
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/sso/login")
                .excludePathPatterns("/register")
                .excludePathPatterns("/demo/hello") ;
//        super.addInterceptors(registry);
    }


}

我添加了一个类,继承了WebMvcConfigurationSupport类并重写了addInterceptors方法并在类上加了@Configuration注解告诉

spring加载此类,上边那个小伙伴们也可以自己玩玩,本篇文章我就不玩了。我代码上的注释应该还是挺清楚的哈。

我的问题就是白名单的注释,路径有项目跟路径。但今天的重点不是这儿

记住这个WebMvcConfigurationSupport类。

万事俱备我们跑起来,跑起来之前我先把LoginInterceptor中的preHandle的返回值修改为false。

我只是简单的实现了这个接口,你如果在最后返回了true就会进入对应的controller,如果返回 false页面将不会得到任何的结果

如图就是返回false的网页页面只截了部分也没什么意义,就是白板。当然你也可以通过response来返回你想指定的值给页面。

response返回指定值

        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        PrintWriter out = null ;
        try{
            out = response.getWriter();
            out.append("用户未登陆!");
        }
        catch (Exception e){
            e.printStackTrace();
            logger.error("创建返回信息抛错:",e);
            try {
                response.sendError(500);
            } catch (IOException ioE) {
                ioE.printStackTrace();
                logger.error("发送异常信息抛错:",ioE);
            }
        }

再次访问,得到结果。

这么简单的拦截器就可以使用了。

但是我当时由于犯了低级错误,造成配置的白名单一直不起效。导致我去看了整个拦截过程。

想要搞清楚,我们首先要知道拦截器是怎么设置的,之前的代码配置拦截器起效的是继承了WebMvcConfigurationSupport的LoginWebConfig所以我们就从它开始。它继承了WebMvcConfigurationSupport所以我们看继承了什么。

我们进入WebMvcConfigurationSupport发现有一个获得拦截器的方法 如下


	/**
	 * Provide access to the shared handler interceptors used to configure
	 * {@link HandlerMapping} instances with. This method cannot be overridden,
	 * use {@link #addInterceptors(InterceptorRegistry)} instead.
	 */
	protected final Object[] getInterceptors() {
		if (this.interceptors == null) {
			InterceptorRegistry registry = new InterceptorRegistry();
			addInterceptors(registry);
			registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService()));
			registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider()));
			this.interceptors = registry.getInterceptors();
		}
		return this.interceptors.toArray();
	}

我们可以看到方法内部在第一次获取的时候调用了自身的addInterceors(registry)方法,还记得我们通过之前配置的时候知道

有一个registry.addInterceptor(HandlerInterceptor interceptor);方法么?

// 这个方法用来注册拦截器,我们自己写好的拦截器需要通过这里添加注册才能生效
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // addPathPatterns("/**") 表示拦截所有的请求,
        // excludePathPatterns("/login", "/register") 表示除了登陆与注册之外,因为登陆注册不需要登陆也可以访问,在配置白名单路径时不要代项目根路径
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/sso/login")
                .excludePathPatterns("/register")
                .excludePathPatterns("/demo/hello") ;
//        super.addInterceptors(registry);
    }

我们进入HandlerInterceptor的方法看

发现这个方法内部是将拦截器作为参数创建了一个InterceptorRegistration对象并添加该类的registrations中InterceptorRegistration

对象返回

private final List<InterceptorRegistration> registrations = new ArrayList<>();

然后添加拦截路径和白名单。到该类的属性:

private final List<String> includePatterns = new ArrayList<>();

private final List<String> excludePatterns = new ArrayList<>();

然后再加上创建InterceptorRegistration是作为参数的拦截器。目前我们的拦截器以及拦截路径和白名单都在InterceptorRegistration

了。这是第一步将拦截器设置。

设置完了,之后通过

this.interceptors = registry.getInterceptors();将WebMvcConfigurationSupport的拦截器的集合赋值。

我们进入getInterceptors()方法。发现返回的是自己的registrations。

还记得之前的LoginWebConfig中的方法么addInterceptors?也就是从这一操作起,我们将拦截器和拦截路径已经白名单全部配置好了。

 

第二步:怎么起作用?

我们可以想一想,如果起作用的话会在什么地方,什么时候起作用呢?假如我们是设计者会怎么做?

如果是我的话,就放在进入我们的controller之前,也就是在我们接收一次请求的时候,不要直接进入controller而是查看项目的拦截器。

所以带着好奇来看看。

首先我们先去一个叫做WsFilter的类中看

从图中我们可以从我指出的方框中得到一下信息:

1.根据该类名字得到,该类不属于spring了。而是tomcat的filter。

2.观看左边的结构得到,该类只有三个方法 init初始 , dofilter在访问使用最多的过滤的方法,destory摧毁的方法。

(原谅我对者几个的概念还不是特别熟)

3.通过被框到的注释得知,该类的作用是在进行网络连接的时候对http连接进行初始化。

好了我们在dofilter中加上断点进行学习。这里我把dofilter的方法拿出来

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {

        // This filter only needs to handle WebSocket upgrade requests
        if (!sc.areEndpointsRegistered() ||
                !UpgradeUtil.isWebSocketUpgradeRequest(request, response)) {
            chain.doFilter(request, response);
            return;
        }

        // HTTP request with an upgrade header for WebSocket present
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        // Check to see if this WebSocket implementation has a matching mapping
        String path;
        String pathInfo = req.getPathInfo();
        if (pathInfo == null) {
            path = req.getServletPath();
        } else {
            path = req.getServletPath() + pathInfo;
        }
        WsMappingResult mappingResult = sc.findMapping(path);

        if (mappingResult == null) {
            // No endpoint registered for the requested path. Let the
            // application handle it (it might redirect or forward for example)
            chain.doFilter(request, response);
            return;
        }

        UpgradeUtil.doUpgrade(sc, req, resp, mappingResult.getConfig(),
                mappingResult.getPathParams());
    }

我们可以看到该方法有3个参数resquest,response,chain。然后我们现在请求下接口断点起作用,我将断点打在方法的刚开始。

        // This filter only needs to handle WebSocket upgrade requests
        if (!sc.areEndpointsRegistered() ||
                !UpgradeUtil.isWebSocketUpgradeRequest(request, response)) {
            chain.doFilter(request, response);
            return;
        }

大家可以看到我把一行注释也贴出来,注释的意思是:只需处理WebSocket升级请求。所以我们的请求应该会走这里。

然后就是顺藤摸瓜了。可以看出来此处使用的是FilterChain的实现类 ApplicationFilterChain。

然后进入ApplicationFilterChain 这个类的代码不算少的。所以就不全部贴出来了。只把doFilter方法和后边调用的方法贴出来。这里将方法名称我们一起看下标志为m的都是方法。

    /**
     * Invoke the next filter in this chain, passing the specified request
     * and response.  If there are no more filters in this chain, invoke
     * the <code>service()</code> method of the servlet itself.
     *
     * @param request The servlet request we are processing
     * @param response The servlet response we are creating
     *
     * @exception IOException if an input/output error occurs
     * @exception ServletException if a servlet exception occurs
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {

        if( Globals.IS_SECURITY_ENABLED ) {
            final ServletRequest req = request;
            final ServletResponse res = response;
            try {
                java.security.AccessController.doPrivileged(
                    new java.security.PrivilegedExceptionAction<Void>() {
                        @Override
                        public Void run()
                            throws ServletException, IOException {
                            internalDoFilter(req,res);
                            return null;
                        }
                    }
                );
            } catch( PrivilegedActionException pe) {
                Exception e = pe.getException();
                if (e instanceof ServletException)
                    throw (ServletException) e;
                else if (e instanceof IOException)
                    throw (IOException) e;
                else if (e instanceof RuntimeException)
                    throw (RuntimeException) e;
                else
                    throw new ServletException(e.getMessage(), e);
            }
        } else {
            internalDoFilter(request,response);
        }
    }

我们会直接进入if下面的分支。会调用自身的internalDofilter方法。这个方法的前半部分先不用看。因为我也没看明白,不过从代码看是对全局权限的判断,本次的目的是拦截器的工作原理,所以就不胡扯了。我们从

// We fell off the end of the chain -- call the servlet instance

的注释开始。首先这个注释的意思就是会在所有的过滤器链之后调用一个servlet的实例。看到servlet是不是眼前一亮,

是不是隐隐觉得要遇到spring了?恭喜你的直觉是对的。

紧接着是一些判断的赋值。当我们在读到

servlet.service(request,response);

快存档然后站起来做套颈椎操犒劳犒劳自己,准备后边的探索吧 哈哈哈

    private void internalDoFilter(ServletRequest request,
                                  ServletResponse response)
        throws IOException, ServletException {

        // Call the next filter if there is one
        if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];
            try {
                Filter filter = filterConfig.getFilter();

                if (request.isAsyncSupported() && "false".equalsIgnoreCase(
                        filterConfig.getFilterDef().getAsyncSupported())) {
                    request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
                }
                if( Globals.IS_SECURITY_ENABLED ) {
                    final ServletRequest req = request;
                    final ServletResponse res = response;
                    Principal principal =
                        ((HttpServletRequest) req).getUserPrincipal();

                    Object[] args = new Object[]{req, res, this};
                    SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
                } else {
                    filter.doFilter(request, response, this);
                }
            } catch (IOException | ServletException | RuntimeException e) {
                throw e;
            } catch (Throwable e) {
                e = ExceptionUtils.unwrapInvocationTargetException(e);
                ExceptionUtils.handleThrowable(e);
                throw new ServletException(sm.getString("filterChain.filter"), e);
            }
            return;
        }

        // We fell off the end of the chain -- call the servlet instance
        try {
            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                lastServicedRequest.set(request);
                lastServicedResponse.set(response);
            }

            if (request.isAsyncSupported() && !servletSupportsAsync) {
                request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
                        Boolean.FALSE);
            }
            // Use potentially wrapped request from this point
            if ((request instanceof HttpServletRequest) &&
                    (response instanceof HttpServletResponse) &&
                    Globals.IS_SECURITY_ENABLED ) {
                final ServletRequest req = request;
                final ServletResponse res = response;
                Principal principal =
                    ((HttpServletRequest) req).getUserPrincipal();
                Object[] args = new Object[]{req, res};
                SecurityUtil.doAsPrivilege("service",
                                           servlet,
                                           classTypeUsedInService,
                                           args,
                                           principal);
            } else {
                servlet.service(request, response);
            }
        } catch (IOException | ServletException | RuntimeException e) {
            throw e;
        } catch (Throwable e) {
            e = ExceptionUtils.unwrapInvocationTargetException(e);
            ExceptionUtils.handleThrowable(e);
            throw new ServletException(sm.getString("filterChain.servlet"), e);
        } finally {
            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                lastServicedRequest.set(null);
                lastServicedResponse.set(null);
            }
        }
    }

这里的servlet是FrameworkServlet从名字就能看出来它应该个Spring有关了,果然包名就暴露了。

package org.springframework.web.servlet;

这个类的篇幅也很大,所以就只贴出来用到的部分。然后我们进入它的service的方法


	/**
	 * Override the parent class implementation in order to intercept PATCH requests.
	 */
	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
		if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
			processRequest(request, response);
		}
		else {
			super.service(request, response);
		}
	}
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());这行代码是获得请求的方法为get还是post;

如果请求方式不为空且不是PATCH的方式,就会调用父类的service方法。

而这个super就需要说道说道了。

如图,可以看出来FrameworkServlet继承了HttpServletBean 实现了ApplicationContextAware。

ApplicationContextAware先放放。我们看下这个HttpServletBean。

如图,HttpServletBean继承HttpServlet类。那我们发扬打破沙锅问到底的精神继续看HttpServlet。有经验的大哥应该已经知道了。

所以我们最终会调用Httpservlet的service 方法。


    /**
     * Receives standard HTTP requests from the public
     * <code>service</code> method and dispatches
     * them to the <code>do</code><i>Method</i> methods defined in
     * this class. This method is an HTTP-specific version of the
     * {@link javax.servlet.Servlet#service} method. There's no
     * need to override this method.
     *
     * @param req   the {@link HttpServletRequest} object that
     *                  contains the request the client made of
     *                  the servlet
     *
     * @param resp  the {@link HttpServletResponse} object that
     *                  contains the response the servlet returns
     *                  to the client
     *
     * @exception IOException   if an input or output error occurs
     *                              while the servlet is handling the
     *                              HTTP request
     *
     * @exception ServletException  if the HTTP request
     *                                  cannot be handled
     *
     * @see javax.servlet.Servlet#service
     */
    protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {

        String method = req.getMethod();

        if (method.equals(METHOD_GET)) {
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
                // servlet doesn't support if-modified-since, no reason
                // to go through further expensive logic
                doGet(req, resp);
            } else {
                long ifModifiedSince;
                try {
                    ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                } catch (IllegalArgumentException iae) {
                    // Invalid date header - proceed as if none was set
                    ifModifiedSince = -1;
                }
                if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                    // If the servlet mod time is later, call doGet()
                    // Round down to the nearest second for a proper compare
                    // A ifModifiedSince of -1 will always be less
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else {
                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
            }

        } else if (method.equals(METHOD_HEAD)) {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);

        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);

        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);

        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);

        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);

        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp);

        } else {
            //
            // Note that this means NO servlet supports whatever
            // method was requested, anywhere on this server.
            //

            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);

            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }

现在基本上大多数人都能知道了,通过请求方式相应的调用doGet和doPost方法。虽然看到调用的自身的doGet和doPost方法。

但是在实际运行时调用的是实际类的自身的doGet和doPost方法 。那这个实际的类是谁呢?相信大家应该能猜的到了。大家不妨

找到spring开始的地方,没错就是DispatcherServlet

嘿嘿它继承了FrameworkServlet类。所以从ApplicationFilterChain中我们就开始使用DispatcherServlet了。哈哈

但是DispatcherServlet并没有重写doGet方法,所以此处的代码在FrameworkServlet中


	/**
	 * Delegate GET requests to processRequest/doService.
	 * <p>Will also be invoked by HttpServlet's default implementation of {@code doHead},
	 * with a {@code NoBodyResponse} that just captures the content length.
	 * @see #doService
	 * @see #doHead
	 */
	@Override
	protected final void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		processRequest(request, response);
	}

接着调用了processRequest方法。仍然在FrameworkSservlet中

	/**
	 * Process this request, publishing an event regardless of the outcome.
	 * <p>The actual event handling is performed by the abstract
	 * {@link #doService} template method.
	 */
	protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		long startTime = System.currentTimeMillis();
		Throwable failureCause = null;

		LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
		LocaleContext localeContext = buildLocaleContext(request);

		RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
		ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

		initContextHolders(request, localeContext, requestAttributes);

		try {
			doService(request, response);
		}
		catch (ServletException | IOException ex) {
			failureCause = ex;
			throw ex;
		}
		catch (Throwable ex) {
			failureCause = ex;
			throw new NestedServletException("Request processing failed", ex);
		}

		finally {
			resetContextHolders(request, previousLocaleContext, previousAttributes);
			if (requestAttributes != null) {
				requestAttributes.requestCompleted();
			}

			if (logger.isDebugEnabled()) {
				if (failureCause != null) {
					this.logger.debug("Could not complete request", failureCause);
				}
				else {
					if (asyncManager.isConcurrentHandlingStarted()) {
						logger.debug("Leaving response open for concurrent processing");
					}
					else {
						this.logger.debug("Successfully completed request");
					}
				}
			}

			publishRequestHandledEvent(request, response, startTime, failureCause);
		}
	}

我们只要关注doService(request, response);那行代码就行了。

这个方法在FrameworkServlet中是抽象方法。所以我们需要在DispatcherServlet中去看了。是不是感觉距离真理又近了一步?

哈哈哈看来需要颈椎操来犒劳自己了 哈哈哈

贴代码!


	/**
	 * Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
	 * for the actual dispatching.
	 */
	@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		if (logger.isDebugEnabled()) {
			String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
			logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
					" processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
		}

		// Keep a snapshot of the request attributes in case of an include,
		// to be able to restore the original attributes after the include.
		Map<String, Object> attributesSnapshot = null;
		if (WebUtils.isIncludeRequest(request)) {
			attributesSnapshot = new HashMap<>();
			Enumeration<?> attrNames = request.getAttributeNames();
			while (attrNames.hasMoreElements()) {
				String attrName = (String) attrNames.nextElement();
				if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
					attributesSnapshot.put(attrName, request.getAttribute(attrName));
				}
			}
		}

		// Make framework objects available to handlers and view objects.
		request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
		request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
		request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
		request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

		if (this.flashMapManager != null) {
			FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
			if (inputFlashMap != null) {
				request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
			}
			request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
			request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
		}

		try {
			doDispatch(request, response);
		}
		finally {
			if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
				// Restore the original attribute snapshot, in case of an include.
				if (attributesSnapshot != null) {
					restoreAttributesAfterInclude(request, attributesSnapshot);
				}
			}
		}
	}

这个方法我们关注doDispatch(request,response);这一行。

之前的都是数据的处理,然后我们进入doDispatch。(敲黑板 这里是重点哦)

	/**
	 * Process the actual dispatching to the handler.
	 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
	 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
	 * to find the first that supports the handler class.
	 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
	 * themselves to decide which methods are acceptable.
	 * @param request current HTTP request
	 * @param response current HTTP response
	 * @throws Exception in case of any kind of processing failure
	 */
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (logger.isDebugEnabled()) {
						logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
					}
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				// making them available for @ExceptionHandler methods and other scenarios.
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

这里我认为第一个重要的地方是

// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);

这一步值找到对应的Handler

第二个是:

// Process last-modified header, if supported by the handler.
String method = request.getMethod();

这行开始。上边的就是匹配handler。下边的跟标题拦截器是相关的。所以就把第一个跳过去了。相信大家是能看得懂的。实在不行

我再补。

第二个我单独拿出来说

				// Process last-modified header, if supported by the handler.
				//获得请求方式,这里的是get
                String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (logger.isDebugEnabled()) {
						logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
					}
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}
                //重中之重,不要因为是一个if判断就忽略掉
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);
				mappedHandler.applyPostHandle(processedRequest, response, mv);

大家注意中间我加的注释重中之重的那个下边的if判断看到判断条件是applyPreHandle的返回值。看到方法名是否是又似曾相识

的感觉,有这种感觉是对的。还记得我们在创将拦截器的时候重写的方法么?(preHandle)

还有注意这里的mappedHandler,就是我之前说的第一个重点。我们回过头注意下这个mappedHandler的类型。

是不是也很熟悉?

他就是我们在项目启动的时候初始拦截器的设置的时候将拦截器封装到这个对象的 interceptorList的属性中

然后我们继续debug进入applyPreHandle方法中去

方法:


	/**
	 * Apply preHandle methods of registered interceptors.
	 * @return {@code true} if the execution chain should proceed with the
	 * next interceptor or the handler itself. Else, DispatcherServlet assumes
	 * that this interceptor has already dealt with the response itself.
	 */
	boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HandlerInterceptor[] interceptors = getInterceptors();
		if (!ObjectUtils.isEmpty(interceptors)) {
			for (int i = 0; i < interceptors.length; i++) {
				HandlerInterceptor interceptor = interceptors[i];
				if (!interceptor.preHandle(request, response, this.handler)) {
					triggerAfterCompletion(request, response, null);
					return false;
				}
				this.interceptorIndex = i;
			}
		}
		return true;
	}

我debug的效果

虽然图可能不美观,但是信息是有的。

我们可以看到第一步就是获取拦截起的数组interceptors 通过下边的debug控制台可以看到第一个就是我们自己编写的拦截器。

然后便利数组调用拦截器的preHandle方法。这里在调用自己编写的LoginInterceptor的preHandle方法时我让他返回了false;

所以请求的最终结果就是请求被拦截,由于我在拦截器中使用response返回了信息。所以最终结果是

 

总结:

spring在启动的时候会将我们的配置类中配置拦截器及设置的拦截url和白名名单封装在InterceptorRegistration中

在我们发起请求的时候Dispatcher中的doDispatch方法会在真正访问对应的controller时匹配url对应的拦截器,

如果拦截器通过才会进行后续.

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值