Spring MVC学习之DispatcherServlet请求处理详析

前言

要深入理解spring mvc的工作流程,就需要先了解spring mvc的架构:

从上图可以看到 前端控制器DispatcherServlet在其中起着主导作用,理解了DispatcherServlet 就完全可以说弄清楚了spring mvc。

DispatcherServlet作为Spring用于处理web请求注册的唯一一个Servlet,所有的请求都是经由DispatcherServlet进行分发处理的。本文主要讲解DispatcherServlet是如何对请求进行分发,处理,并且生成相应的视图的。

1. 整体结构

在HttpServlet中,其对不同方式的请求进行了分发,比如对于GET请求,其提供了doGet()方法,对于POST请求,其提供了doPost()方法等等。通过这种方式,子类可以针对于当前请求的方式实现不同的方法即可。但是在DispatcherServlet中,由于需要使用同一的方式对不同的请求进行处理,因而其对各个请求方式进行了整合,如下就是DispatcherServlet针对GET和POST请求所编写的同一处理逻辑:

@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
 throws ServletException, IOException {
 processRequest(request, response);
}

@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
 throws ServletException, IOException {
 processRequest(request, response);
}

可以看到,无论是GET请求还是POST请求,DispatcherServlet都是委托给了processRequest()方法处理,对于其他的请求方式,其处理方式也是类似的。通过这种方式,DispatcherServlet将各个请求整合在了一起,虽然整合在了一起,但是request中也还是保存有当前请求的请求方式的,因而保存了后续对请求进行分发的能力。这里我们直接看processRequest()方法是如何处理各个请求的:

protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 long startTime = System.currentTimeMillis();
 Throwable failureCause = null;
 // 获取先前请求的LocaleContext
 LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
 // 获取当前请求的LocaleContext,其中保存了当前请求的Locale信息
 LocaleContext localeContext = buildLocaleContext(request);

 // 获取先前请求的Attributes信息
 RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
 // 获取当前请求的Attributes信息,其中保存了当前请求的各个属性数据
 ServletRequestAttributes requestAttributes =
 buildRequestAttributes(request, response, previousAttributes);

 // 获取当前请求的WebAsyncManager,这只有在当前请求是请求的异步任务时才会真正用到
 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
 // 注册异步任务的拦截器,如果请求的是异步任务,这个拦截器可以拦截异步任务的前置,后置和异常等情况
 asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(),
 new RequestBindingInterceptor());

 // 将当前请求的Locale,Attributes信息初始化到对应的ThreadLocal对象中,用于后续使用
 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 {
 // 在请求完成之后,判断当前请求的Locale和Attributes信息是否需要继承,如果需要继承,
 // 则会将Locale信息设置到inheritableLocaleContextHolder中,而将Attributes
 // 信息设置到inheritableRequestAttributesHolder中;否则就会移除对应的信息,
 // 而只为当前请求的ContextHolder设置相应的属性
 resetContextHolders(request, previousLocaleContext, previousAttributes);
 if (requestAttributes != null) {
 // 调用已注册的在当前请求被销毁时的回调函数,并且更新Session中当前请求所更新的属性
 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);
 }
}

可以看到,processRequest()方法主要是对Locale和Attributes信息进行了处理,然后就通过doService()方法对请求再次进行了分发。我们这里继续阅读doService()方法的源码:

@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) + "]");
 }

 // 这里主要是判断当前请求是否为include请求,如果是include请求,那么就会将当前请求中的
 // 数据都放入一个快照中,在当前请求完成之后,会从该块中中取出数据,然后将其重新加载到
 // 当前request中,以便request进行后续的处理。这里默认情况下是会对所有的属性进行处理的,
 // 因为cleanupAfterInclude默认值为true,如果将其设置为false,那么就只会对Spring框架
 // 相关的属性进行处理
 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));
 }
 }
 }

 // 这里分别将ApplicationContext,LoacleResolver,ThemeResolver和ThemeSource等
 // bean添加到当前request中
 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());

 // 这里FlashMapManager主要的作用在于当请求如果是重定向的请求,那么可以将一些属性保存在FlashMap
 // 中,然后通过FlashMapManager进行管理,从而在重定向之后能够获取到重定向之前所保存的请求
 if (this.flashMapManager != null) {
 // 在当前请求中获取FlashMap数据,如果不是重定向之后的请求,那么这里获取到的就是空值
 FlashMap inputFlashMap =
 this.flashMapManager.retrieveAndUpdate(request, response);
 if (inputFlashMap != null) {
 // 将获取到的FlashMap数据保存在request中
 request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE,
 Collections.unmodifiableMap(inputFlashMap));
 }
 // 设置默认的FlashMap和FlashMapManager
 request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
 request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
 }

 try {
 // 这里才是真正的对请求进行分发处理的位置
 doDispatch(request, response);
 } finally {
 // 判断当前请求不是一个异步任务的请求,但是是一个include请求,那么就会重新加载
 // 请求之前保存的快照数据
 if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
 if (attributesSnapshot != null) {
 restoreAttributesAfterInclude(request, attributesSnapshot);
 }
 }
 }
}

这里的doService()方法也还没有对请求进行真正的处理,其首先判断了当前请求是不是一个include请求,如果是include请求,那么就将请求的属性都保存在一个快照中,以便请求完成之后能够重新进行加载;然后会判断当前是否是一个重定向之后的请求,如果是重定向之后的请求,那么其FlashMapManager就不是空的,此时会将重定向之前保存的属性重新加载到当前请求中;最后doService()方法才会调用doDispatch()方法进行请求的分发和处理。如下是doDispatch()方法的源码:

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 {
 // 这里判断当前请求是否为一个文件请求,这里的判断方式就是要求当前请求满足两点:①请求
 // 方式是POST;②判断contentType是否以multipart/开头。如果满足这两点,那么就认为当前
 // 请求是一个文件请求,此时会将当前请求的request对象封装为一个
 // MultipartHttpServletRequest对象,这也是我们在定义文件请求的Controller时
 // 能够将request参数写为MultipartHttpServletRequest的原因。这里如果不是文件请求,
 // 那么会将request直接返回。
 processedRequest = checkMultipart(request);
 // 这里判断原始request与转换后的request是否为同一个request,如果不是同一个,则说明
 // 其是一个文件请求
 multipartRequestParsed = (processedRequest != request);
 // 这里getHandler()方法就是通过遍历当前Spring容器中所有定义的HandlerMapping对象,
 // 通过调用它们的getHandler()方法,看当前的HandlerMapping能否将当前request映射
 // 到某个handler,也就是某个Controller方法上,如果能够映射到,则说明该handler能够
 // 处理当前请求
 mappedHandler = getHandler(processedRequest);
 if (mappedHandler == null) {
 // 如果每个HandlerMapping都无法找到与当前request匹配的handler,那么就认为
 // 无法处理当前请求,此时一般会返回给页面404状态码
 noHandlerFound(processedRequest, response);
 return;
 }

 // 通过找到的handler,然后在当前Spring容器中找到能够支持将当前request请求适配到
 // 找到的handler上的HandlerAdapter。这里需要找到这样的适配器的原因是,我们的handler
 // 一般都是Controller的某个方法,其是一个Java方法,而当前request则是一种符合http
 // 协议的请求,这里是无法直接将request直接应用到handler上的,因而需要使用一个适配器,
 // 也就是这里的HandlerAdapter。由于前面获取handler的时候,不同的HandlerMapping
 // 所产生的handler是不一样的,比如ReqeustMappingHandlerMapping产生的handler是一个
 // HandlerMethod对象,因而这里在判断某个HandlerAdapter是否能够用于适配当前handler的
 // 时候是通过其supports()方法进行的,比如RequestMappingHandlerAdapter就是判断
 // 当前的handler是否为HandlerMethod类型,从而判断其是否能够用于适配当前handler。
 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
 String method = request.getMethod();
 boolean isGet = "GET".equals(method);
 // 这里判断请求方式是否为GET或HEAD请求,如果是这两种请求的一种,那么就会判断
 // 当前请求的资源是否超过了其lastModified时间,如果没超过,则直接返回,
 // 并且告知浏览器可以直接使用缓存来处理当前请求
 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;
 }
 }

 // 这里在真正处理请求之前会获取容器中所有的拦截器,也就是HandlerInterceptor对象,
 // 然后依次调用其preHandle()方法,如果某个preHandle()方法返回了false,那么就说明
 // 当前请求无法通过拦截器的过滤,因而就会直接出发其afterCompletion()方法,只有在
 // 所有的preHandle()方法都返回true时才会认为当前请求是能够使用目标handler进行处理的
 if (!mappedHandler.applyPreHandle(processedRequest, response)) {
 return;
 }

 // 在当前请求通过了所有拦截器的预处理之后,这里就直接调用HandlerAdapter.handle()
 // 方法来处理当前请求,并且将处理结果封装为一个ModelAndView对象。该对象中主要有两个
 // 属性:view和model,这里的view存储了后续需要展示的逻辑视图名或视图对象,而model
 // 中则保存了用于渲染视图所需要的属性
 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

 // 如果当前是一个异步任务,那么就会释放当前线程,等待异步任务处理完成之后才将
 // 任务的处理结果返回到页面
 if (asyncManager.isConcurrentHandlingStarted()) {
 return;
 }

 // 如果返回的ModelAndView对象中没有指定视图名或视图对象,那么就会根据当前请求的url
 // 来生成一个视图名
 applyDefaultViewName(processedRequest, mv);
 // 在请求处理完成之后,依次调用拦截器的postHandle()方法,对请求进行后置处理
 mappedHandler.applyPostHandle(processedRequest, response, mv);
 } catch (Exception ex) {
 dispatchException = ex;
 } catch (Throwable err) {
 // 将处理请求过程中产生的异常封装到dispatchException中
 dispatchException = new NestedServletException("Handler dispatch failed",
 err);
 }

 // 这里主要是请求处理之后生成的视图进行渲染,也包括出现异常之后对异常的处理。
 // 渲染完之后会依次调用拦截器的afterCompletion()方法来对请求进行最终处理
 processDispatchResult(processedRequest, response, mappedHandler, mv,
 dispatchException);
 } catch (Exception ex) {
 // 如果在上述过程中任意位置抛出异常,包括渲染视图时抛出异常,那么都会触发拦截器的
 // afterCompletion()方法的调用
 triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
 } catch (Throwable err) {
 triggerAfterCompletion(processedRequest, response, mappedHandler,
 new NestedServletException("Handler processing failed", err));
 } finally {
 // 如果当前异步任务已经开始,则触发异步任务拦截器的afterConcurrentHandlingStarted()方法
 if (asyncManager.isConcurrentHandlingStarted()) {
 if (mappedHandler != null) {
 mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest,
 response);
 }
 } else {
 // 如果当前是一个文件请求,则清理当前request中的文件数据
 if (multipartRequestParsed) {
 cleanupMultipart(processedRequest);
 }
 }
 }
}

这里doDispatch()方法是进行请求分发和处理的主干部分,其主要分为如下几个步骤:

  • 判断当前是否为文件请求,如果是,则将request对象类型转换为MultipartHttpServletRequest;
  • 在HandlerMapping中查找能够处理当前request的HandlerMapping,并且获取能够处理当前请求的handler;
  • 根据获取到的handler,查找当前容器中支持将当前request适配到该handler的HandlerAdapter;
  • 应用容器中所有拦截器的preHandle()方法,只有在所有的preHandle()方法都通过之后才会将当前请求交由具体的handler进行处理;
  • 调用HandlerAdapter.handle()方法将request适配给获取到的handler进行处理;
  • 应用容器中所有拦截器的postHandle()方法,以对当前请求进行后置处理;
  • 根据处理后得到的ModelAndView对象对视图进行渲染;
  • 应用容器中所有拦截器的afterCompletion()方法,以对当前请求进行完成处理。

2. handler获取

从前面的步骤可以看出,请求的具体处理过程主要是通过HandlerMapping根据当前request获取到对应的handler,然后交由HandlerAdapter将request适配给该handler进行处理,并将处理结果封装为一个ModelAndView对象,最后将该ModelAndView对象渲染出来。这里我们首先看HandlerMapping根据request查找具体的handler的过程:

@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
 if (this.handlerMappings != null) {
 // 遍历当前容器中所有的HandlerMapping对象,调用其getHandler()方法,如果其能够根据
 // 当前request获取一个handler,那么就直接返回。
 for (HandlerMapping hm : this.handlerMappings) {
 if (logger.isTraceEnabled()) {
 logger.trace(
  "Testing handler map [" + hm + "] in DispatcherServlet with name '"
  + getServletName() + "'");
 }
 HandlerExecutionChain handler = hm.getHandler(request);
 if (handler != null) {
 return handler;
 }
 }
 }
 return null;
}

这里的逻辑比较简单,就是遍历当前容器中所有的HandlerMapping对象,然后依次判断其是否能够根据当前request获取一个handler,如果能够获取就直接使用该handler。这里关于HandlerMapping将request映射为handler的过程可以阅读本人之前的文章:Spring MVC之RequestMappingHandlerMapping匹配。

3. HandlerAdapter获取与请求处理

在获取到具体的handler之后,Dispatcher就会根据获取到的handler查找能够将当前request适配到该handler的Adapter,这里获取HandlerAdapter的代码如下:

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
 if (this.handlerAdapters != null) {
 // 遍历当前容器中所有的HandlerAdapter,通过调用其supports()方法,判断当前HandlerAdapter
 // 能否用于适配当前的handler,如果可以,则直接使用该HandlerAdapter
 for (HandlerAdapter ha : this.handlerAdapters) {
 if (logger.isTraceEnabled()) {
 logger.trace("Testing handler adapter [" + ha + "]");
 }
 if (ha.supports(handler)) {
 return ha;
 }
 }
 }

 // 如果找不到任何一个HandlerAdapter用于适配当前请求,则抛出异常
 throw new ServletException("No adapter for handler [" + handler
 + "]: The DispatcherServlet configuration needs to include a HandlerAdapter"
 + " that supports this handler");
}

这里获取HandlerAdapter的过程与HandlerMapping非常的相似,也是遍历当前容器中所有的HandlerAdapter对象,然后调用其supports()方法,判断该适配器能否应用于当前handler的适配,如果可以则直接使用该HandlerAdapter。关于HandlerAdapter进行request与handler适配的过程,读者可阅读本人之前的文章:Spring MVC之RequestMappingHandlerAdapter详解。

4. 视图渲染

在HandlerAdapter进行了请求的适配,并且调用了目标handler之后,其会返回一个ModelAndView对象,该对象中保存有用于渲染视图的模型数据和需要渲染的视图名。具体的视图渲染工作是在processDispatchResult()方法中进行的,这里我们直接阅读器源码:

private void processDispatchResult(HttpServletRequest request,
 HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler,
 @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {

 // 用于标记当前生成view是否是异常处理之后生成的view
 boolean errorView = false;
 if (exception != null) {
 // 如果当前的异常是ModelAndViewDefiningException类型,则说明是ModelAndView的定义
 // 异常,那么就会调用其getModelAndView()方法生成一个新的view
 if (exception instanceof ModelAndViewDefiningException) {
 logger.debug("ModelAndViewDefiningException encountered", exception);
 mv = ((ModelAndViewDefiningException) exception).getModelAndView();
 } else {
 // 如果生成的异常是其他类型的异常,就会在当前容器中查找能够处理当前异常的“拦截器”,
 // 找到之后调用这些拦截器,然后生成一个新的ModelAndView
 Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
 mv = processHandlerException(request, response, handler, exception);
 errorView = (mv != null);
 }
 }

 // 如果得到的ModelAndView对象(无论是否为异常处理之后生成的ModelAndView)不为空,并且没有被清理,
 // 那么就会对其进行渲染,渲染的主要逻辑在render()方法中
 if (mv != null && !mv.wasCleared()) {
 render(mv, request, response);
 if (errorView) {
 // 如果当前是异常处理之后生成的视图,那么就请求当前request中与异常相关的属性
 WebUtils.clearErrorRequestAttributes(request);
 }
 } else {
 if (logger.isDebugEnabled()) {
 logger.debug("Null ModelAndView returned to DispatcherServlet with name '"
 + getServletName() + "': assuming HandlerAdapter completed request "
 + "handling");
 }
 }

 // 如果当前正在进行异步请求任务的调用,则直接释放当前线程,等异步任务处理完之后再进行处理
 if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
 return;
 }

 // 在视图渲染完成之后,依次调用当前容器中所有拦截器的afterCompletion()方法
 if (mappedHandler != null) {
 mappedHandler.triggerAfterCompletion(request, response, null);
 }
}

从上面的逻辑可以看出,在进行视图渲染时,首先会判断请求处理过程中是否抛出了异常,如果抛出了异常,则会调用相应的异常处理器,获取异常处理之后的ModelAndView对象,然后通过ModelAndView对象渲染具体的视图,最后会依次触发当前容器中所有拦截器的afterCompletion()方法。这里对视图的具体渲染工作在render()方法中,我们继续阅读其源码:

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
 // 获取当前请求的Locale信息,该信息在进行视图的国际化展示时将会非常有用
 Locale locale = (this.localeResolver != null
 ? this.localeResolver.resolveLocale(request) : request.getLocale());
 response.setLocale(locale);

 View view;
 String viewName = mv.getViewName();
 if (viewName != null) {
 // 如果视图名不为空,那么就会使用当前容器中配置的ViewResolver根据视图名获取一个View对象
 view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
 if (view == null) {
 throw new ServletException("Could not resolve view with name '"
 + mv.getViewName() + "' in servlet with name '" + getServletName() + "'");
 }
 } else {
 // 如果ModelAndView中没有视图名,而提供的View对象,则直接使用该View对象
 view = mv.getView();
 if (view == null) {
 throw new ServletException("ModelAndView [" + mv + "] neither contains a "
 + "view name nor a View object in servlet with name '"
 + getServletName() + "'");
 }
 }

 if (logger.isDebugEnabled()) {
 logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '"
 + getServletName() + "'");
 }
 try {
 // 设置响应的status属性
 if (mv.getStatus() != null) {
 response.setStatus(mv.getStatus().value());
 }

 // 调用View对象的render()方法来渲染具体的视图
 view.render(mv.getModelInternal(), request, response);
 } catch (Exception ex) {
 if (logger.isDebugEnabled()) {
 logger.debug("Error rendering view [" + view + "] in DispatcherServlet"
 + " with name '" + getServletName() + "'", ex);
 }
 throw ex;
 }
}

这里的render()方法才是进行视图渲染的真正方法,首先该方法首先通过ModelAndView对象获取所要渲染的视图名,通过ViewResolver生成一个用于视图渲染的View对象;如果ModelAndView中不是保存的视图名,而是保存的View对象,则直接使用该对象。在生成View对象之后,通过调用该对象的render()方法渲染得到具体的视图。这里关于ViewResolver如何获取到View对象,并且如何进行视图渲染的过程,读者可以阅读本人的文章:Spring MVC之视图解析。

5. 小结

本文首先从整体上讲解了DispatcherServlet是如何对请求进行聚合并且处理的,然后分别从handler获取,HandlerAdapter进行请求适配,以及视图的渲染三个方面对请求处理的整体流程进行了讲解。这里主要是对DispatcherServlet处理请求的整体流程进行讲解,其各个部分的细节读者可以阅读本人前面的文章以进行详细的了解。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • 配置DispatcherServlet的方法介绍

    DispatcherServlet是Spring MVC的前端控制器,要想在程序中使用DispatcherServlet,我们需要在web.xml中配置一个DispatcherServlet.配置的方法是,在web.xml文件中,配置一个<servlet>节点,它的子节点<servlet-name>可以自定义一个名字,例如sample,<servlet-class>指定为org.spring.springframework.web.servlet.DispatcherS

  • 浅谈springmvc的DispatcherServlet分析

    本文介绍了springmvc的DispatcherServlet,分享给大家,具体如下: 一.程序 (一)web.xml文件中的内容 <!-- springMVC核心配置 --> <servlet> <servlet-name>springmvcServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-cl

  • 详解Spring MVC/Boot 统一异常处理最佳实践

    前言 在 Web 开发中, 我们经常会需要处理各种异常, 这是一件棘手的事情, 对于很多人来说, 可能对异常处理有以下几个问题: 什么时候需要捕获(try-catch)异常, 什么时候需要抛出(throws)异常到上层. 在 dao 层捕获还是在 service 捕获, 还是在 controller 层捕获. 抛出异常后要怎么处理. 怎么返回给页面错误信息. 异常处理反例 既然谈到异常, 我们先来说一下异常处理的反例, 也是很多人容易犯的错误, 这里我们同时讲到前端处理和后端处理 : 捕获异常后

  • Spring MVC之DispatcherServlet详解_动力节点Java学院整理

    DispatcherServlet作用 DispatcherServlet是前端控制器设计模式的实现,提供Spring Web MVC的集中访问点,而且负责职责的分派,而且与Spring IoC容器无缝集成,从而可以获得Spring的所有好处. 具体请参考第二章的图2-1. DispatcherServlet主要用作职责调度工作,本身主要用于控制流程,主要职责如下: 1.文件上传解析,如果请求类型是multipart将通过MultipartResolver进行文件上传解析: 2.通过Handle

  • spring mvc DispatcherServlet之前端控制器架构详解

    前端控制器是整个MVC框架中最为核心的一块,它主要用来拦截符合要求的外部请求,并把请求分发到不同的控制器去处理,根据控制器处理后的结果,生成相应的响应发送到客户端.前端控制器既可以使用Filter实现(Struts2采用这种方式),也可以使用Servlet来实现(spring MVC框架). DispatcherServlet 作为前置控制器是web服务器的入口,是spring mvc最重要的一个类,通过它的生命周期可以加深对web服务器的理解. servlet的生命周期 首先我们回忆一下ser

  • 详解在spring boot中配置多个DispatcherServlet

    spring boot为我们自动配置了一个开箱即用的DispatcherServlet,映射路径为'/',但是如果项目中有多个服务,为了对不同服务进行不同的配置管理,需要对不同服务设置不同的上下文,比如开启一个DispatcherServlet专门用于rest服务. 传统springMVC项目 在传统的springMVC项目中,配置多个DispatcherServlet很轻松,在web.xml中直接配置多个就行: <servlet> <servlet-name>restServle

  • Spring MVC之DispatcherServlet_动力节点Java学院整理

    Spring MVC之DispatcherServlet 使用Spring MVC,配置DispatcherServlet是第一步. DispatcherServlet是一个Servlet,所以可以配置多个DispatcherServlet. DispatcherServlet是前置控制器,配置在web.xml文件中的.拦截匹配的请求,Servlet拦截匹配规则要自已定义,把拦截下来的请求,依据某某规则分发到目标Controller(我们写的Action)来处理. "某某规则":是根据

  • Spring Boot集成netty实现客户端服务端交互示例详解

    前言 Netty 是一个高性能的 NIO 网络框架,本文主要给大家介绍了关于SpringBoot集成netty实现客户端服务端交互的相关内容,下面来一起看看详细的介绍吧 看了好几天的netty实战,慢慢摸索,虽然还没有摸着很多门道,但今天还是把之前想加入到项目里的 一些想法实现了,算是有点信心了吧(讲真netty对初学者还真的不是很友好......) 首先,当然是在SpringBoot项目里添加netty的依赖了,注意不要用netty5的依赖,因为已经废弃了 <!--netty--> <

  • 在Spring boot的项目中使用Junit进行单体测试

    使用Junit或者TestNG可以进行单体测试,这篇文章简单说明一下如何在Spring boot的项目中使用Junit进行单体测试. pom设定 pom中需要添加spring-boot-starter-test <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>

  • SpringBoot项目修改访问端口和访问路径的方法

    创建SpringBoot项目,启动后,默认的访问路径即主机IP+默认端口号8080:http://localhost:8080/ 此时,我们就可以访问Controller层的接口了,如:http://localhost:8080/hello package com.springboot.test; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.a

随机推荐