Spring5源码解析

在Java的Web应用程序中不认为奇选择过滤器来捕获HTTP央求。但它们仅为webapps保留。Spring引进了一种新的点子来促成,更通用,称为管理程序拦截器。

本文将分3部分。第一有个别来讲Spring管理程序拦截器的辩驳概念。第二有个别,说一说私下认可的Spring拦截器。最终一有的常规,应用实战,大家将写咱俩团结的管理程序拦截器。

要通晓Spring拦截器的功力,大家须求先解释一下HTTP必要的实行链。DispatcherServlet捕获每一种央求。调解员做的率先件事便是将抽取到的U奇骏L和对应的controller举行映射(controller必需适度地管理当下的央浼)。可是,在达到对应的controller在此以前,哀告能够被拦截器管理。那个拦截器就如过滤器。唯有当USportageL找到相应于它们的照射时才调用它们。在通过拦截器(拦截器预管理,其实也得以说前置管理)实行停放管理后,诉求最后达到controller。之后,发送央浼生成视图。可是在那后面,拦截器仍旧有不小可能率来再一次拍卖它。独有在最终叁回操作之后,视图解析器技能捕获数据并出口视图。

管理程序映射拦截器基于org.springframework.web.servlet.HandlerInterceptor接口。和在此之前大约描述的那么,它们能够在将其发送到调整器(方法前应用preHandle)此前或之后(方法后选取postHandle)拦截央浼。preHandle方法重回三个布尔值,假如回去false,则足以在实施链中实践中断诉求管理。此接口中还会有二个主意afterCompletion,唯有在preHandler方法发送为true时才会在渲染视图后调用它(达成要求管理后的回调,即渲染视图后)。

拦截器也能够在新线程中运转。在这种场合下,拦截器必需兑现org.springframework.web.servlet.AsyncHandlerInterceptor接口。它继续HandlerInterceptor并提供多个主意afterConcurrentHandlingStarted。每一次管理程序获得不错施行时,都会调用此格局并非调用postHandler()和afterCompletion()。它也得以对发送需要举行异步管理。通过Spring源码此办法注释能够领会,这些办法的卓绝的采取是足以用来清理地面线程变量。

/** * Extends {@code HandlerInterceptor} with a callback method invoked after the * start of asynchronous request handling. * * <p>When a handler starts an asynchronous request, the {@link DispatcherServlet} * exits without invoking {@code postHandle} and {@code afterCompletion} as it * normally does for a synchronous request, since the result of request handling * (e.g. ModelAndView) is likely not yet ready and will be produced concurrently * from another thread. In such scenarios, {@link #afterConcurrentHandlingStarted} * is invoked instead, allowing implementations to perform tasks such as cleaning * up thread-bound attributes before releasing the thread to the Servlet container. * * <p>When asynchronous handling completes, the request is dispatched to the * container for further processing. At this stage the {@code DispatcherServlet} * invokes {@code preHandle}, {@code postHandle}, and {@code afterCompletion}. * To distinguish between the initial request and the subsequent dispatch * after asynchronous handling completes, interceptors can check whether the * {@code javax.servlet.DispatcherType} of {@link javax.servlet.ServletRequest} * is {@code "REQUEST"} or {@code "ASYNC"}. * * <p>Note that {@code HandlerInterceptor} implementations may need to do work * when an async request times out or completes with a network error. For such * cases the Servlet container does not dispatch and therefore the * {@code postHandle} and {@code afterCompletion} methods will not be invoked. * Instead, interceptors can register to track an asynchronous request through * the {@code registerCallbackInterceptor} and {@code registerDeferredResultInterceptor} * methods on {@link org.springframework.web.context.request.async.WebAsyncManager * WebAsyncManager}. This can be done proactively on every request from * {@code preHandle} regardless of whether async request processing will start. * * @author Rossen Stoyanchev * @since 3.2 * @see org.springframework.web.context.request.async.WebAsyncManager * @see org.springframework.web.context.request.async.CallableProcessingInterceptor * @see org.springframework.web.context.request.async.DeferredResultProcessingInterceptor */public interface AsyncHandlerInterceptor extends HandlerInterceptor { /** * Called instead of {@code postHandle} and {@code afterCompletion}, when * the a handler is being executed concurrently. * <p>Implementations may use the provided request and response but should * avoid modifying them in ways that would conflict with the concurrent * execution of the handler. A typical use of this method would be to * clean up thread-local variables. * @param request the current request * @param response the current response * @param handler the handler (or {@link HandlerMethod}) that started async * execution, for type and/or instance examination * @throws Exception in case of errors */ void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;}

拦截器看起来很像servlet过滤器,为啥Spring不选拔暗许的Java设计方案?那些中第一差别正是相互的功效域的难题。过滤器只好在servlet容器下利用。而笔者辈的Spring容器不必然运营在web情形中,在这种景观下过滤器就倒霉使了,而拦截器依旧得以在Spring容器中调用。

Spring通过拦截器为呼吁提供了三个越来越细粒度的决定。如同咱们后面看来的那么,它们能够在controller对诉求管理在此以前或以往被调用,也足以在将渲染视图呈现给客户之后被调用。假若是过滤器的话,只好在将响应再次回到给最后客户以前运用它们。

下二个不相同之处在于中断链实行的难易程度。拦截器能够透过在preHandler()方法内重临false来轻松实现。而在过滤器的情状下,它就变得复杂了,因为它必需处理诉求和响应对象来诱惑中断,需求有的额外的动作,举个例子如将客户重定向到不当页面。

Spring主要将拦截器用于切换操作。比方咱们最常用的法力之一是区域设置更动。请查看org.springframework.web.servlet.i18n.LocaleChangeInterceptor类中源码,可以通过大家所定义的语言意况分析器来对HTTP伏乞进行分析来落实。全部区域安装深入分析器都会深入分析央浼元素(headers,Cookie),以分明向客户提供哪一类本地化语言设置。

另三个本土拦截器是org.springframework.web.servlet.theme.ThemeChangeInterceptor,它同意更换视图的宗旨。它还选用核心分析器更加准确地来领悟要运用的核心(参照下边preHandle方法)。它的剖析器也依照供给剖析(cookie,会话或参数)。

/** * Interceptor that allows for changing the current theme on every request, * via a configurable request parameter (default parameter name: "theme"). * * @author Juergen Hoeller * @since 20.06.2003 * @see org.springframework.web.servlet.ThemeResolver */public class ThemeChangeInterceptor extends HandlerInterceptorAdapter { /** * Default name of the theme specification parameter: "theme". */ public static final String DEFAULT_PARAM_NAME = "theme"; private String paramName = DEFAULT_PARAM_NAME; /** * Set the name of the parameter that contains a theme specification * in a theme change request. Default is "theme". */ public void setParamName(String paramName) { this.paramName = paramName; } /** * Return the name of the parameter that contains a theme specification * in a theme change request. */ public String getParamName() { return this.paramName; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException { String newTheme = request.getParameter(this.paramName); if (newTheme != null) { ThemeResolver themeResolver = RequestContextUtils.getThemeResolver; if (themeResolver == null) { throw new IllegalStateException("No ThemeResolver found: not in a DispatcherServlet request?"); } themeResolver.setThemeName(request, response, newTheme); } // Proceed in any case. return true; }}

小编们写贰个例证来差十分少完毕HandlerInterceptor。二个乐透彩票的光景,这么些自定义的拦截器将深入分析种种须求,并调节是还是不是是彩票的“lottery
winner”。为了简化代码逻辑,独有用于转移多少个随机数并透过取模决断是不是再次来到0的要求。

public class LotteryInterceptor implements HandlerInterceptor { public static final String ATTR_NAME = "lottery_winner"; private static final Logger LOGGER = LoggerFactory.getLogger(LotteryInterceptor.class); @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) throws Exception { LOGGER.debug("[LotteryInterceptor] afterCompletion"); } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView view) throws Exception { LOGGER.debug("[LotteryInterceptor] postHandle"); } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { LOGGER.debug("[LotteryInterceptor] preHandle"); if (request.getSession().getAttribute(ATTR_NAME) == null) { Random random = new Random(); int i = random.nextInt; request.getSession().setAttribute(ATTR_NAME, i%2 == 0); } return true; }}

关于相应controller中要出示的音讯:

@Controllerpublic class TestController { private static final Logger LOGGER = LoggerFactory.getLogger(TestController.class); @RequestMapping(value = "/test", method = RequestMethod.GET) public String test(HttpServletRequest request) { LOGGER.debug("Controller asks, are you a lottery winner ? "+request.getSession().getAttribute(LotteryInterceptor.ATTR_NAME)); return "test"; }}

举例大家尝试访谈/test,我们将看不到拦截器的日志,因为它未有在布局中定义。如若大家是利用注脚来配置的webapp。我们须要将上面那么些布局加上到应用程序的上下文文件中(Springboot配置个照拂的bean就可):

<mvc:interceptors> <bean /></mvc:interceptors>

现行反革命大家得以访问/ test页面并检讨日志:

[LotteryInterceptor] preHandleController asks, are you a lottery winner ? false[LotteryInterceptor] postHandle[LotteryInterceptor] afterCompletion

小结一下,拦截器是一种可以运用到全体Spring生态系统中的servlet过滤器。它们得以在呼吁以前或以往运维,也足以在视图展现之后运营。它们也能够由此AsyncHandlerInterceptor接口的落到实处达到规定的标准异步管理的效率。

原稿:Spring5源码剖判-Spring中的管理拦截器极乐科学和技术:新浪专栏

相关文章