1.从DispatcherServlet开始

与很多使用广泛的MVC框架一样,SpringMVC使用的是FrontController模式,所有的设计都围绕DispatcherServlet
为中心来展开的。见下图,所有请求从DispatcherServlet进入,DispatcherServlet根据配置好的映射策略确定处理的
Controller,Controller处理完成返回ModelAndView,DispatcherServlet根据配置好的视图策略确定处理的
View,由View生成具体的视图返回给请求客户端。

2.初始化

SpringMVC几个核心的配置策略包括:

*HandlerMapping:请求-处理器映射策略处理,根据请求生成具体的处理链对象

*HandlerAdapter:处理器适配器,由于最终的Handler对象不一定是一个标准接口的实现对象,参数也可能非常的灵活复杂,因此所有的对象需要一个合适的适配器适配成标准的处理接口来最终执行请求

*ViewResolver:视图映射策略,根据视图名称和请求情况,最终映射到具体的处理View,由View对象来生成具体的视图。

其他的配置策略包括MultipartResolver、LocaleResolver、ThemeResolver等等,但并不影响我们对整个SpringMVC的工作原理的理解,此处并不具体说明。

1)初始化Context

见下图DispatcherServlet的继承结构,其中,HttpServletBean主要功能是在初始化(init)时将servlet的配置参
数(init-param)转换成Servlet的属性,FrameworkServlet主要功能是与ApplicationContext的集成,因
此Context的初始化工作主要在FrameworkServlet中进行。

Context初始化的过程可以通过如下过程来描述:HttServletBean.init -->
FrameworkServlet.initServletBean -->
FrameworkServlet.initWebApplicationContext。具体的初始化过程可见如下代码:

FrameworkServlet.initWebApplicationContext

  1. protected WebApplicationContext initWebApplicationContext() throws BeansException {
  2. WebApplicationContext parent = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
  3. WebApplicationContext wac = createWebApplicationContext(parent);
  4. if (!this.refreshEventReceived) {
  5. // Apparently not a ConfigurableApplicationContext with refresh support:
  6. // triggering initial onRefresh manually here.
  7. onRefresh(wac);
  8. }
  9. if (this.publishContext) {
  10. // Publish the context as a servlet context attribute.
  11. String attrName = getServletContextAttributeName();
  12. getServletContext().setAttribute(attrName, wac);
  13. if (logger.isDebugEnabled()) {
  14. logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
  15. "' as ServletContext attribute with name [" + attrName + "]");
  16. }
  17. }
  18. return wac;
  19. }

FrameworkServlet.createWebApplicationContext

  1. protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent)
  2. throws BeansException {
  3. if (logger.isDebugEnabled()) {
  4. logger.debug("Servlet with name '" + getServletName() +
  5. "' will try to create custom WebApplicationContext context of class '" +
  6. getContextClass().getName() + "'" + ", using parent context [" + parent + "]");
  7. }
  8. if (!ConfigurableWebApplicationContext.class.isAssignableFrom(getContextClass())) {
  9. throw new ApplicationContextException(
  10. "Fatal initialization error in servlet with name '" + getServletName() +
  11. "': custom WebApplicationContext class [" + getContextClass().getName() +
  12. "] is not of type ConfigurableWebApplicationContext");
  13. }
  14. ConfigurableWebApplicationContext wac =
  15. (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(getContextClass());
  16. wac.setParent(parent);
  17. wac.setServletContext(getServletContext());
  18. wac.setServletConfig(getServletConfig());
  19. wac.setNamespace(getNamespace());
  20. wac.setConfigLocation(getContextConfigLocation());
  21. wac.addApplicationListener(new SourceFilteringListener(wac, this));
  22. postProcessWebApplicationContext(wac);
  23. wac.refresh();
  24. return wac;
  25. }

2)初始化策略

具体与SpringMVC相关的策略在DispatcherServlet中初始化,DispatcherServlet的类定义被加载时,如下初始化代码段被执行:

  1. static {
  2. // Load default strategy implementations from properties file.
  3. // This is currently strictly internal and not meant to be customized
  4. // by application developers.
  5. try {
  6. ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
  7. defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
  8. }
  9. catch (IOException ex) {
  10. throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
  11. }

我们可以看到,SpringMVC的策略在与DispatcherServlet同目录的Dispatcher.properties文件中配置,如下是Spring2.5的默认配置策略

Dispatcher.properties 写道
# Default implementation classes for DispatcherServlet's strategy interfaces.

# Used as fallback when no matching beans are found in the DispatcherServlet context.

# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\

org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\

org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\

org.springframework.web.servlet.mvc.throwaway.ThrowawayControllerHandlerAdapter,\

org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

当然,我们可以变更其处理策略,通过上面部分,我们知道,FrameworkServlet实现了ApplicationListener,并在构建
WebApplicationContext后,将自身(this)向WebApplicationContext注册,因此
WebApplicationContext初始化完毕之后,将发送ContextRefreshedEvent事件,该事件实际上被
DispatcherServlet处理,处理过程如下:

FrameworkServlet.onApplicationEvent --> DispatcherServlet.onRefresh --> DispatcherServlet.initStrategies

DispatcherServlet.initStrategies代码如下,具体处理过程可参见Spring源代码

  1. protected void initStrategies(ApplicationContext context) {
  2. initMultipartResolver(context);
  3. initLocaleResolver(context);
  4. initThemeResolver(context);
  5. initHandlerMappings(context);
  6. initHandlerAdapters(context);
  7. initHandlerExceptionResolvers(context);
  8. initRequestToViewNameTranslator(context);
  9. initViewResolvers(context);

3.请求处理流程

SpringMVC的请求处理从doService-->doDispatch为入口,实际上,我们只要紧紧抓住HandlerMapping、
HandlerAdapter、ViewResolver这三个核心对象,SpringMVC的一整个运行机制看起来将非常简单,其主要处理流程包括:

1)将请求映射到具体的执行处理链,见如下代码

  1. // Determine handler for the current request.
  2. mappedHandler = getHandler(processedRequest, false);
  3. if (mappedHandler == null || mappedHandler.getHandler() == null) {
  4. noHandlerFound(processedRequest, response);
  5. return;
  6. }

具体看一下getHandler是如何处理的

  1. protected HandlerExecutionChain getHandler(HttpServletRequest request, boolean cache) throws Exception {
  2. HandlerExecutionChain handler =
  3. (HandlerExecutionChain) request.getAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
  4. if (handler != null) {
  5. if (!cache) {
  6. request.removeAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
  7. }
  8. return handler;
  9. }
  10. Iterator it = this.handlerMappings.iterator();
  11. while (it.hasNext()) {
  12. HandlerMapping hm = (HandlerMapping) it.next();
  13. if (logger.isDebugEnabled()) {
  14. logger.debug("Testing handler map [" + hm  + "] in DispatcherServlet with name '" +
  15. getServletName() + "'");
  16. }
  17. handler = hm.getHandler(request);
  18. if (handler != null) {
  19. if (cache) {
  20. request.setAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE, handler);
  21. }
  22. return handler;
  23. }
  24. }
  25. return null;

可以看到,仅仅是遍历每一个HandlerMapping,如果能够其能够处理,则返回执行处理链(HandleExecuteChain)

2)执行处理链的拦截器列表的preHandle方法,如果执行时返回false,表示该拦截器已处理完请求要求停止执行后续的工作,则倒序执行所有已执行过的拦截器的afterCompletion方法,并返回

  1. // Apply preHandle methods of registered interceptors.
  2. HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
  3. if (interceptors != null) {
  4. for (int i = 0; i < interceptors.length; i++) {
  5. HandlerInterceptor interceptor = interceptors[i];
  6. if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {
  7. triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
  8. return;
  9. }
  10. interceptorIndex = i;
  11. }
  12. }

3)根据处理对象获得处理器适配器(HandlerAdapter),并由处理适配器负责最终的请求处理,并返回ModelAndView(mv),关于处理器适配器的作用,见第2部分的说明

  1. // Actually invoke the handler.
  2. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
  3. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

具体看getHandlerAdapter如何工作

  1. protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
  2. Iterator it = this.handlerAdapters.iterator();
  3. while (it.hasNext()) {
  4. HandlerAdapter ha = (HandlerAdapter) it.next();
  5. if (logger.isDebugEnabled()) {
  6. logger.debug("Testing handler adapter [" + ha + "]");
  7. }
  8. if (ha.supports(handler)) {
  9. return ha;
  10. }
  11. }
  12. throw new ServletException("No adapter for handler [" + handler +
  13. "]: Does your handler implement a supported interface like Controller?");
  14. }

非常简单,仅仅是依次询问HandlerAdapter列表是否支持处理当前的处理器对象

4)倒序执行处理链拦截器列表的postHandle方法

  1. // Apply postHandle methods of registered interceptors.
  2. if (interceptors != null) {
  3. for (int i = interceptors.length - 1; i >= 0; i--) {
  4. HandlerInterceptor interceptor = interceptors[i];
  5. interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);
  6. }
  7. }

5)根据ViewResolver获取相应的View实例,并生成视图响应给客户端

  1. // Did the handler return a view to render?
  2. if (mv != null && !mv.wasCleared()) {
  3. render(mv, processedRequest, response);
  4. }
  5. else {
  6. if (logger.isDebugEnabled()) {
  7. logger.debug("Null ModelAndView returned to DispatcherServlet with name '" +
  8. getServletName() + "': assuming HandlerAdapter completed request handling");
  9. }
  10. }

再看看render方法

  1. protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response)
  2. throws Exception {
  3. // Determine locale for request and apply it to the response.
  4. Locale locale = this.localeResolver.resolveLocale(request);
  5. response.setLocale(locale);
  6. View view = null;
  7. if (mv.isReference()) {
  8. // We need to resolve the view name.
  9. view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
  10. if (view == null) {
  11. throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
  12. "' in servlet with name '" + getServletName() + "'");
  13. }
  14. }
  15. else {
  16. // No need to lookup: the ModelAndView object contains the actual View object.
  17. view = mv.getView();
  18. if (view == null) {
  19. throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
  20. "View object in servlet with name '" + getServletName() + "'");
  21. }
  22. }
  23. // Delegate to the View object for rendering.
  24. if (logger.isDebugEnabled()) {
  25. logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
  26. }
  27. view.render(mv.getModelInternal(), request, response);
  28. }

6)倒序执行处理链拦截器列表的afterCompletion方法

    1. private void triggerAfterCompletion(
    2. HandlerExecutionChain mappedHandler, int interceptorIndex,
    3. HttpServletRequest request, HttpServletResponse response, Exception ex)
    4. throws Exception {
    5. // Apply afterCompletion methods of registered interceptors.
    6. if (mappedHandler != null) {
    7. HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
    8. if (interceptors != null) {
    9. for (int i = interceptorIndex; i >= 0; i--) {
    10. HandlerInterceptor interceptor = interceptors[i];
    11. try {
    12. interceptor.afterCompletion(request, response, mappedHandler.getHandler(), ex);
    13. }
    14. catch (Throwable ex2) {
    15. logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
    16. }
    17. }
    18. }
    19. }
    20. }
    21. 4.请求-处理链映射(HandlerMapping)
         HandlerMapping定义了请求与处理链之间的映射的策略,见如下接口。

      1. public interface HandlerMapping {
      2. String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
      3. HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

      主要的继承类和继承结构如下

         其中
      *AbstractHandlerMapping:定义了HandlerMapping实现的最基础的部分内容,包括拦截器列表和默认处理对象
      *AbstractUrlHandlerMapping:在AbstractHandlerMapping的基础上,定义了从URL到处理对象的映射关系管理,其主要处理过程如下
         getHandlerInternal:

      1. protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
      2. String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
      3. if (logger.isDebugEnabled()) {
      4. logger.debug("Looking up handler for [" + lookupPath + "]");
      5. }
      6. Object handler = lookupHandler(lookupPath, request);
      7. if (handler == null) {
      8. // We need to care for the default handler directly, since we need to
      9. // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
      10. Object rawHandler = null;
      11. if ("/".equals(lookupPath)) {
      12. rawHandler = getRootHandler();
      13. }
      14. if (rawHandler == null) {
      15. rawHandler = getDefaultHandler();
      16. }
      17. if (rawHandler != null) {
      18. validateHandler(rawHandler, request);
      19. handler = buildPathExposingHandler(rawHandler, lookupPath);
      20. }
      21. }
      22. return handler;
      23. }

      lookupHandler:

      1. protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
      2. // Direct match?
      3. Object handler = this.handlerMap.get(urlPath);
      4. if (handler != null) {
      5. validateHandler(handler, request);
      6. return buildPathExposingHandler(handler, urlPath);
      7. }
      8. // Pattern match?
      9. String bestPathMatch = null;
      10. for (Iterator it = this.handlerMap.keySet().iterator(); it.hasNext();) {
      11. String registeredPath = (String) it.next();
      12. if (getPathMatcher().match(registeredPath, urlPath) &&
      13. (bestPathMatch == null || bestPathMatch.length() < registeredPath.length())) {
      14. bestPathMatch = registeredPath;
      15. }
      16. }
      17. if (bestPathMatch != null) {
      18. handler = this.handlerMap.get(bestPathMatch);
      19. validateHandler(handler, request);
      20. String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPathMatch, urlPath);
      21. return buildPathExposingHandler(handler, pathWithinMapping);
      22. }
      23. // No handler found...
      24. return null;
      25. }

      另外,其定义了Handler的注册方法registerHandler

      *AbstractDetectingUrlHandlerMapping:AbstractDetectingUrlHandlerMapping
      通过继承ApplicationObjectSupport实现了ApplicationContextAware接口,在初始化完成之后自动通过
      ApplicationObjectSupport.setApplicationContext-->AbstractDetectingUrlHandlerMapping.initApplicationContext-->AbstractDetectingUrlHandlerMapping.detectHandlers
      调用detectHandlers函数,该函数将注册到ApplicationContext的所有Bean对象逐一检查,由其子类实现的
      determineUrlsForHandler判断每个Bean对象对应的URL,并将URL与Bean的关系通过
      AbstractUrlHandlerMapping.registerHandler注册

      1. protected void detectHandlers() throws BeansException {
      2. if (logger.isDebugEnabled()) {
      3. logger.debug("Looking for URL mappings in application context: " + getApplicationContext());
      4. }
      5. String[] beanNames = (this.detectHandlersInAncestorContexts ?
      6. BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
      7. getApplicationContext().getBeanNamesForType(Object.class));
      8. // Take any bean name or alias that begins with a slash.
      9. for (int i = 0; i < beanNames.length; i++) {
      10. String beanName = beanNames[i];
      11. String[] urls = determineUrlsForHandler(beanName);
      12. if (!ObjectUtils.isEmpty(urls)) {
      13. // URL paths found: Let's consider it a handler.
      14. registerHandler(urls, beanName);
      15. }
      16. else {
      17. if (logger.isDebugEnabled()) {
      18. logger.debug("Rejected bean name '" + beanNames[i] + "': no URL paths identified");
      19. }
      20. }
      21. }
      22. }

      *BeanNameUrlHandlerMapping:非常简单,其实现determineUrlsForHandler函数,如果一个Bean以“/”开头,则认为是一个处理器类,并且以bean的名字作为映射的url,处理过程如下

      1. protected String[] determineUrlsForHandler(String beanName) {
      2. List urls = new ArrayList();
      3. if (beanName.startsWith("/")) {
      4. urls.add(beanName);
      5. }
      6. String[] aliases = getApplicationContext().getAliases(beanName);
      7. for (int j = 0; j < aliases.length; j++) {
      8. if (aliases[j].startsWith("/")) {
      9. urls.add(aliases[j]);
      10. }
      11. }
      12. return StringUtils.toStringArray(urls);
      13. }

      *DefaultAnnotationHandlerMapping:实现determineUrlsForHandler函数,检查每个Bean
      对象的类或者方法有没有RequestMapping这个Annotation,如果有,则将相应配置的URL作为该Bean对应处理的URL,处理过程
      如下

      1. protected String[] determineUrlsForHandler(String beanName) {
      2. ApplicationContext context = getApplicationContext();
      3. Class<?> handlerType = context.getType(beanName);
      4. RequestMapping mapping = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class);
      5. if (mapping == null && context instanceof ConfigurableApplicationContext &&
      6. context.containsBeanDefinition(beanName)) {
      7. ConfigurableApplicationContext cac = (ConfigurableApplicationContext) context;
      8. BeanDefinition bd = cac.getBeanFactory().getMergedBeanDefinition(beanName);
      9. if (bd instanceof AbstractBeanDefinition) {
      10. AbstractBeanDefinition abd = (AbstractBeanDefinition) bd;
      11. if (abd.hasBeanClass()) {
      12. Class<?> beanClass = abd.getBeanClass();
      13. mapping = AnnotationUtils.findAnnotation(beanClass, RequestMapping.class);
      14. }
      15. }
      16. }
      17. if (mapping != null) {
      18. // @RequestMapping found at type level
      19. this.cachedMappings.put(handlerType, mapping);
      20. Set<String> urls = new LinkedHashSet<String>();
      21. String[] paths = mapping.value();
      22. if (paths.length > 0) {
      23. // @RequestMapping specifies paths at type level
      24. for (String path : paths) {
      25. addUrlsForPath(urls, path);
      26. }
      27. return StringUtils.toStringArray(urls);
      28. }
      29. else {
      30. // actual paths specified by @RequestMapping at method level
      31. return determineUrlsForHandlerMethods(handlerType);
      32. }
      33. }
      34. else if (AnnotationUtils.findAnnotation(handlerType, Controller.class) != null) {
      35. // @RequestMapping to be introspected at method level
      36. return determineUrlsForHandlerMethods(handlerType);
      37. }
      38. else {
      39. return null;
      40. }
      41. }

      5.处理器适配器(HandlerAdapter)
         HandlerAdapter定义了处理类如何处理请求的策略,见如下接口

      1. public interface HandlerAdapter {
      2. boolean supports(Object handler);
      3. ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
      4. long getLastModified(HttpServletRequest request, Object handler);
      5. }

      通过实现特定的策略,可以灵活地将任意对象转换成请求处理对象,主要实现包括:

      1)SimpleControllerHandlerAdapter/HttpRequestHandlerAdapter/SimpleServletHandlerAdapter/ThrowawayController
         非常简单,面向实现实现了特定接口的处理类的情形,仅仅做一个代理执行处理,看一下其中SimpleControllerHandlerAdapter的代码如下,其特定处理实现了Controller接口的处理类

      1. public class SimpleControllerHandlerAdapter implements HandlerAdapter {
      2. public boolean supports(Object handler) {
      3. return (handler instanceof Controller);
      4. }
      5. public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
      6. throws Exception {
      7. return ((Controller) handler).handleRequest(request, response);
      8. }
      9. public long getLastModified(HttpServletRequest request, Object handler) {
      10. if (handler instanceof LastModified) {
      11. return ((LastModified) handler).getLastModified(request);
      12. }
      13. return -1L;
      14. }
      15. }

      2)AnnotationMethodHandlerAdapter
         通过配置特定的Annotation,定义了该如何注入参数、调用哪个方法、对返回参数如何处理,主要过程如下(AnnotationMethodHandlerAdapter.invokeHandlerMethod)

      1. try {
      2. ServletHandlerMethodResolver methodResolver = getMethodResolver(handler);
      3. Method handlerMethod = methodResolver.resolveHandlerMethod(request);
      4. ServletHandlerMethodInvoker methodInvoker = new ServletHandlerMethodInvoker(methodResolver);
      5. ServletWebRequest webRequest = new ServletWebRequest(request, response);
      6. ExtendedModelMap implicitModel = new ExtendedModelMap();
      7. Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);
      8. ModelAndView mav = methodInvoker.getModelAndView(handlerMethod, result, implicitModel, webRequest);
      9. methodInvoker.updateSessionAttributes(
      10. handler, (mav != null ? mav.getModel() : null), implicitModel, webRequest);
      11. return mav;
      12. }
      13. catch (NoSuchRequestHandlingMethodException ex) {
      14. return handleNoSuchRequestHandlingMethod(ex, request, response);
      15. }

      *ServletHandlerMethodResolver(AnnotationMethodHandlerAdapter内部类):该类通过请求URL、请求Method和处理类的RequestMapping定义,最终确定该使用处理类的哪个方法来处理请求
      *ServletHandlerMethodInvoker(AnnotationMethodHandlerAdapter内部类):检查处理类相应处
      理方法的参数以及相关的Annotation配置,确定如何转换需要的参数传入调用方法,并最终调用返回ModelAndView
      6.视图策略(ViewResolver)
        ViewResolver定义了如何确定处理视图的View对象的策略,见如下接口

      1. public interface ViewResolver {
      2. View resolveViewName(String viewName, Locale locale) throws Exception;
      3. }

spring mvc源码解析的更多相关文章

  1. Spring MVC源码——Servlet WebApplicationContext

    上一篇笔记(Spring MVC源码——Root WebApplicationContext)中记录了下 Root WebApplicationContext 的初始化代码.这一篇来看 Servlet ...

  2. Spring Boot系列(四):Spring Boot源码解析

    一.自动装配原理 之前博文已经讲过,@SpringBootApplication继承了@EnableAutoConfiguration,该注解导入了AutoConfigurationImport Se ...

  3. 精尽Spring MVC源码分析 - 寻找遗失的 web.xml

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  4. 精尽Spring MVC源码分析 - 一个请求的旅行过程

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  5. 精尽Spring MVC源码分析 - MultipartResolver 组件

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  6. 精尽Spring MVC源码分析 - HandlerMapping 组件(一)之 AbstractHandlerMapping

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  7. 精尽Spring MVC源码分析 - HandlerMapping 组件(二)之 HandlerInterceptor 拦截器

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  8. 精尽Spring MVC源码分析 - HandlerMapping 组件(三)之 AbstractHandlerMethodMapping

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  9. 精尽Spring MVC源码分析 - HandlerMapping 组件(四)之 AbstractUrlHandlerMapping

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

随机推荐

  1. 关于Windows Azure的常见问题-执行与维护FAQ

    执行与维护 使用虚拟机运行业务应用有什么需要注意的地方? Windows Azure 会周期性地更新主机环境,以确保平台上运行的所有应用程序和虚拟机始终处于安全的环境.此更新过程可能会导致您的虚拟机重 ...

  2. Codeforces Round #306 (Div. 2) ABCDE(构造)

    A. Two Substrings 题意:给一个字符串,求是否含有不重叠的子串"AB"和"BA",长度1e5. 题解:看起来很简单,但是一直错,各种考虑不周全, ...

  3. 关于学习Scala语言的一些感悟

    进入话题! 我们知道哈,Spark源码采用Scala语言编写,那么阅读Spark源码之前,是否一定先学Scala呢? 我个人认为,不必,只要我们有一些java或c++编写语言的基础,就可以看Spaar ...

  4. javascript中的function对象

    function对象都是Function的实例: > Object.getOwnPropertyNames(Function) [ 'length', 'name', 'arguments', ...

  5. mongdb高级操作(group by )

    首先介绍哈方法 /** * 利用java驱动自带函数分组查询 * @param key 用来分组文档的字段 [group by key] * @param cond 执行过滤的条件 [where na ...

  6. Duff and Weight Lifting - 587A

    题目大意:某个人训练举重,他每次可以举起来2^wi的重量,不过这个人比较懒所以他想尽量减少训练的次数,如果所有的训练重量2^a1 +2^a2+....2^ak = 2^x,那么这些重量可以一次性训练( ...

  7. PAT 1089. Insert or Merge (25)

    According to Wikipedia: Insertion sort iterates, consuming one input element each repetition, and gr ...

  8. iOS 检测网络状态

    一般有两种方式,都是第三方的框架,轮子嘛,能用就先用着,后面再优化. 一:Reachability 1.首先在AppDelegate.h添加头文件"Reachability.h", ...

  9. macos ssh host配置及免密登陆

    windows下面有xshell 这样的可视化ssh管理工具 macos 下面使用终端做下简单配置,也非常方便,具体过程如下 生成秘钥 cd ~/.sshssh-keygen -t rsa 生成了私钥 ...

  10. JQuery EasyUI内Combobox的onChange事件

    1.原始方法 我想写个html代码的都对下拉选择标签select不陌生,关于这个标签,在不加任何渲染的情况下,想要触发其onchange事件是很简单的一件事情,如下: <select id=&q ...