环境 spring-framework:5.1.x
spring-boot: v2.1.2.RELEASE
看一眼历史的感觉 先看一眼我们很久以前用的XML的配置方式,我举得用最原始的方式来学习会相对于简单,因为很多的配置都是显性的。我只截取最核心的部分,大概找一下感觉。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <?xml version="1.0" encoding="UTF-8" ?> <web-app version ="2.4" xmlns ="http://java.sun.com/xml/ns/j2ee" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation =" http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" > <context-param > <param-name > contextConfigLocation</param-name > <param-value > classpath:context.xml</param-value > </context-param > <listener > <listener-class > org.springframework.web.context.ContextLoaderListener</listener-class > </listener > <filter > <filter-name > encodingFilter</filter-name > <filter-class > org.springframework.web.filter.CharacterEncodingFilter</filter-class > </filter > <servlet > <servlet-name > dispatcherServlet</servlet-name > <servlet-class > org.springframework.web.servlet.DispatcherServlet</servlet-class > <init-param > <param-name > contextConfigLocation</param-name > <param-value > classpath:config/spring-mvc.xml</param-value > </init-param > <load-on-startup > 1</load-on-startup > </servlet > </web-app >
上面的配置基本就把一个SpringMVC的项目配置完成了,大家都了解。web.xml是一个WEB项目的入口,而这里面就把Spring与Servlet关联起来了。
Loader1:org.springframework.web.context.ContextLoaderListener 父IOC容器,管理所有的Bean。
Loader2:org.springframework.web.servlet.DispatcherServlet 子IOC容器,主要关于与WEB相关的一些配置,比如:Controller、HandlerMapping等等。
这里粗略的描述一下WEB项目的一个加载顺序:listener → filter → servlet。
ContextLoaderListener 1 2 3 4 org.springframework.web.context.ContextLoaderListener javax.servlet.ServletContextListener javax.servlet.ServletContext javax.servlet.ServletContextEvent
ContextLoaderListener(spring中的类)继承ContextLoader(spring中的类),并实现ServletContextListener(servlet中的接口),ServletContextListener监听ServletContext,当容器启动时,会触发ServletContextEvent事件,该事件由ServletContextListener来处理,启动初始化ServletContext时,调用contextInitialized方法。而ContextLoaderListener实现了ServletContextListener,所以,当容器启动时,触发ServletContextEvent事件,让ContextLoaderListener执行实现方法contextInitialized(ServletContextEvent sce);
引自:https://www.jianshu.com/p/c1384f3d5698
1 2 3 4 @Override public void contextInitialized (ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); }
我们细看一下我们是如何初始化一个Context的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 if (this .context == null ) { this .context = createWebApplicationContext(servletContext); } protected Class<?> determineContextClass(ServletContext servletContext) String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM); if (contextClassName != null ) { try { return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader()); } catch (ClassNotFoundException ex) { throw new ApplicationContextException ("Failed to load custom context class [" + contextClassName + "]" , ex); } } else { contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName()); try { return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader()); } catch (ClassNotFoundException ex) { throw new ApplicationContextException ("Failed to load default context class [" + contextClassName + "]" , ex); } } }
servletContext.getInitParameter(CONTEXT_CLASS_PARAM); 这句话实际上是优先用用户配置的,否则才会取默认的。如果我们自己配置要在哪儿配置了。对的还是要在我们的web.xml里面。
1 2 3 4 <context-param > <param-name > contextClass</param-name > <param-value > org.springframework.web.context.support.StaticWebApplicationContext</param-value > </context-param >
那我们默认的是哪个呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 static { try { ClassPathResource resource = new ClassPathResource (DEFAULT_STRATEGIES_PATH, ContextLoader.class); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } catch (IOException ex) { throw new IllegalStateException ("Could not load 'ContextLoader.properties': " + ex.getMessage()); } } private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties" ;
是滴,有一个properties文件,里面就是默认的Context配置。
1 org.springframework.web.context.WebApplicationContext =org.springframework.web.context.support.XmlWebApplicationContext
实际上,我们默认的就是XmlWebApplicationContext。继续扫读web.xml的配置来加载与Spring相关的配置。
1 2 3 4 5 6 String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);if (configLocationParam != null ) { wac.setConfigLocation(configLocationParam); }
调用refresh开始构建
1 wac.refresh(); // 然而这里我觉得要单独拿一个篇章来讲Spring是如何来加载Bean。
DispatcherServlet
HttpServlet 及以上部分是 Servlet 标准中提供的接口及类
DispatcherServlet、FrameworkServlet、HttpServletBean 三者是 SpringMVC 提供的类,且后者依次分别是前者的父类。
因为是Servlet,所有会调用init来初始化。
org.springframework.web.servlet.HttpServletBean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public final void init () throws ServletException { PropertyValues pvs = new ServletConfigPropertyValues (getServletConfig(), this .requiredProperties); if (!pvs.isEmpty()) { try { BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this ); ResourceLoader resourceLoader = new ServletContextResourceLoader (getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor (resourceLoader, getEnvironment())); initBeanWrapper(bw); bw.setPropertyValues(pvs, true ); } catch (BeansException ex) { if (logger.isErrorEnabled()) { logger.error("Failed to set bean properties on servlet '" + getServletName() + "'" , ex); } throw ex; } } initServletBean(); }
org.springframework.web.servlet.FrameworkServlet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 protected final void initServletBean () throws ServletException { getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'" ); if (logger.isInfoEnabled()) { logger.info("Initializing Servlet '" + getServletName() + "'" ); } long startTime = System.currentTimeMillis(); try { this .webApplicationContext = initWebApplicationContext(); initFrameworkServlet(); } catch (ServletException | RuntimeException ex) { logger.error("Context initialization failed" , ex); throw ex; } if (logger.isDebugEnabled()) { String value = this .enableLoggingRequestDetails ? "shown which may lead to unsafe logging of potentially sensitive data" : "masked to prevent unsafe logging of potentially sensitive data" ; logger.debug("enableLoggingRequestDetails='" + this .enableLoggingRequestDetails + "': request parameters and headers will be " + value); } if (logger.isInfoEnabled()) { logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms" ); } }
org.springframework.web.servlet.FrameworkServlet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 protected WebApplicationContext createWebApplicationContext (@Nullable ApplicationContext parent) { Class<?> contextClass = getContextClass(); if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException ( "Fatal initialization error in servlet with name '" + getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext" ); } ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); wac.setEnvironment(getEnvironment()); wac.setParent(parent); String configLocation = getContextConfigLocation(); if (configLocation != null ) { wac.setConfigLocation(configLocation); } configureAndRefreshWebApplicationContext(wac); return wac; }
看到这里,我们的2个容器都是默认用的XmlWebApplicationContext。
那问哪些HandlerMapping、HandlerAdapter、ViewResolver是在哪儿加载进来的?
org.springframework.web.servlet.DispatcherServlet#onRefresh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 protected void initStrategies (ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); }
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 protected void detectHandlerMethods (Object handler) { Class<?> handlerType = (handler instanceof String ? obtainApplicationContext().getType((String) handler) : handler.getClass()); if (handlerType != null ) { Class<?> userType = ClassUtils.getUserClass(handlerType); Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (MethodIntrospector.MetadataLookup<T>) method -> { try { return getMappingForMethod(method, userType); } catch (Throwable ex) { throw new IllegalStateException ("Invalid mapping on handler class [" + userType.getName() + "]: " + method, ex); } }); if (logger.isTraceEnabled()) { logger.trace(formatMappings(userType, methods)); } methods.forEach((method, mapping) -> { Method invocableMethod = AopUtils.selectInvocableMethod(method, userType); registerHandlerMethod(handler, invocableMethod, mapping); }); } }
遍历Handler中的所有方法,找出其中被@RequestMapping注解标记的方法。
然后遍历这些方法,生成RequestMappingInfo实例。
将RequestMappingInfo实例以及处理器方法注册到缓存中。
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Override @Nullable protected RequestMappingInfo getMappingForMethod (Method method, Class<?> handlerType) { RequestMappingInfo info = createRequestMappingInfo(method); if (info != null ) { RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType); if (typeInfo != null ) { info = typeInfo.combine(info); } String prefix = getPathPrefix(handlerType); if (prefix != null ) { info = RequestMappingInfo.paths(prefix).build().combine(info); } } return info; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public void register (T mapping, Object handler, Method method) { this .readWriteLock.writeLock().lock(); try { HandlerMethod handlerMethod = createHandlerMethod(handler, method); assertUniqueMethodMapping(handlerMethod, mapping); this .mappingLookup.put(mapping, handlerMethod); List<String> directUrls = getDirectUrls(mapping); for (String url : directUrls) { this .urlLookup.add(url, mapping); } String name = null ; if (getNamingStrategy() != null ) { name = getNamingStrategy().getName(handlerMethod, mapping); addMappingName(name, handlerMethod); } CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping); if (corsConfig != null ) { this .corsLookup.put(handlerMethod, corsConfig); } this .registry.put(mapping, new MappingRegistration <>(mapping, handlerMethod, directUrls, name)); } finally { this .readWriteLock.writeLock().unlock(); } }
把一些请求的映射关系放入到Map中,为后续的路由功能做数据初始化。
1 2 private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap <>();private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap <>();
对于Request参数的一些封装&映射:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Override public RequestMappingInfo combine (RequestMappingInfo other) { String name = combineNames(other); PatternsRequestCondition patterns = this .patternsCondition.combine(other.patternsCondition); RequestMethodsRequestCondition methods = this .methodsCondition.combine(other.methodsCondition); ParamsRequestCondition params = this .paramsCondition.combine(other.paramsCondition); HeadersRequestCondition headers = this .headersCondition.combine(other.headersCondition); ConsumesRequestCondition consumes = this .consumesCondition.combine(other.consumesCondition); ProducesRequestCondition produces = this .producesCondition.combine(other.producesCondition); RequestConditionHolder custom = this .customConditionHolder.combine(other.customConditionHolder); return new RequestMappingInfo (name, patterns, methods, params, headers, consumes, produces, custom.getCondition()); }
一般我们对关心的是一个url是如何组装的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public PatternsRequestCondition combine (PatternsRequestCondition other) { Set<String> result = new LinkedHashSet <>(); if (!this .patterns.isEmpty() && !other.patterns.isEmpty()) { for (String pattern1 : this .patterns) { for (String pattern2 : other.patterns) { result.add(this .pathMatcher.combine(pattern1, pattern2)); } } } else if (!this .patterns.isEmpty()) { result.addAll(this .patterns); } else if (!other.patterns.isEmpty()) { result.addAll(other.patterns); } else { result.add("" ); } return new PatternsRequestCondition (result, this .pathHelper, this .pathMatcher, this .useSuffixPatternMatch, this .useTrailingSlashMatch, this .fileExtensions); }
这是从注释上copy下来的注解,主要有这里的pathMatcher来组装。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 public String combine (String pattern1, String pattern2) { if (!StringUtils.hasText(pattern1) && !StringUtils.hasText(pattern2)) { return "" ; } if (!StringUtils.hasText(pattern1)) { return pattern2; } if (!StringUtils.hasText(pattern2)) { return pattern1; } boolean pattern1ContainsUriVar = (pattern1.indexOf('{' ) != -1 ); if (!pattern1.equals(pattern2) && !pattern1ContainsUriVar && match(pattern1, pattern2)) { return pattern2; } if (pattern1.endsWith(this .pathSeparatorPatternCache.getEndsOnWildCard())) { return concat(pattern1.substring(0 , pattern1.length() - 2 ), pattern2); } if (pattern1.endsWith(this .pathSeparatorPatternCache.getEndsOnDoubleWildCard())) { return concat(pattern1, pattern2); } int starDotPos1 = pattern1.indexOf("*." ); if (pattern1ContainsUriVar || starDotPos1 == -1 || this .pathSeparator.equals("." )) { return concat(pattern1, pattern2); } String ext1 = pattern1.substring(starDotPos1 + 1 ); int dotPos2 = pattern2.indexOf('.' ); String file2 = (dotPos2 == -1 ? pattern2 : pattern2.substring(0 , dotPos2)); String ext2 = (dotPos2 == -1 ? "" : pattern2.substring(dotPos2)); boolean ext1All = (ext1.equals(".*" ) || ext1.isEmpty()); boolean ext2All = (ext2.equals(".*" ) || ext2.isEmpty()); if (!ext1All && !ext2All) { throw new IllegalArgumentException ("Cannot combine patterns: " + pattern1 + " vs " + pattern2); } String ext = (ext1All ? ext2 : ext1); return file2 + ext; }
还是稍微的有点粗,也只描述了我们最最关心的一些点。后面再继续的对每个细节点做一个总结。
参考地址: