美文网首页
Spring在Web容器中的启动过程

Spring在Web容器中的启动过程

作者: Vernon | 来源:发表于2019-11-15 09:43 被阅读0次

    环境

    spring-framework:5.1.x

    spring-boot: v2.1.2.RELEASE

    看一眼历史的感觉

    先看一眼我们很久以前用的XML的配置方式,我举得用最原始的方式来学习会相对于简单,因为很多的配置都是显性的。我只截取最核心的部分,大概找一下感觉。

    <?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.ContextLoaderListenerIOC容器,管理所有的Bean

    Loader2:org.springframework.web.servlet.DispatcherServletIOC容器,主要关于与WEB相关的一些配置,比如:ControllerHandlerMapping等等。

    这里粗略的描述一下WEB项目的一个加载顺序:listener → filter → servlet。

    ContextLoaderListener

    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

    @Override
    public void contextInitialized(ServletContextEvent event) {
        initWebApplicationContext(event.getServletContext());
    }
    

    我们细看一下我们是如何初始化一个Context的:

    if (this.context == null) {
        this.context = createWebApplicationContext(servletContext);
    }
    // 确定我们容器是哪个Context
    protected Class<?> determineContextClass(ServletContext servletContext) 
      // public static final String CONTEXT_CLASS_PARAM = "contextClass";
        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里面。

    <context-param>   
      <param-name>contextClass</param-name>   
      <param-value>org.springframework.web.context.support.StaticWebApplicationContext</param-value>   
    </context-param> 
    

    那我们默认的是哪个呢?

    static {
            // Load default strategy implementations from properties file.
            // This is currently strictly internal and not meant to be customized
            // by application developers.
            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配置。

    org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
    

    实际上,我们默认的就是XmlWebApplicationContext。继续扫读web.xml的配置来加载与Spring相关的配置。

    // public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
    // 这里就是all.xml/application.xml/context.xml 等的加载地方
    String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
    if (configLocationParam != null) {
        wac.setConfigLocation(configLocationParam);
    }
    

    调用refresh开始构建

    wac.refresh(); // 然而这里我觉得要单独拿一个篇章来讲Spring是如何来加载Bean。
    

    DispatcherServlet

    • HttpServlet 及以上部分是 Servlet 标准中提供的接口及类
    • DispatcherServlet、FrameworkServlet、HttpServletBean 三者是 SpringMVC 提供的类,且后者依次分别是前者的父类。
    http://static.cyblogs.com/WX20191114-20465.png

    因为是Servlet,所有会调用init来初始化。

    org.springframework.web.servlet.HttpServletBean

    public final void init() throws ServletException {
            // Set bean properties from init parameters.
            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;
                }
            }
    
            // Let subclasses do whatever initialization they like.
            initServletBean();
        }
    

    org.springframework.web.servlet.FrameworkServlet

    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 {
        // 看到这里,回到之前说初始化ContextLoaderListener的initWebApplicationContext
            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

    protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
      //public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
            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); // 这里面又会调用wac.refresh();
            return wac;
        }
    

    看到这里,我们的2个容器都是默认用的XmlWebApplicationContext

    那问哪些HandlerMappingHandlerAdapterViewResolver是在哪儿加载进来的?

    �org.springframework.web.servlet.DispatcherServlet#onRefresh

    /**
         * Initialize the strategy objects that this servlet uses.
         * <p>May be overridden in subclasses in order to initialize further strategy objects.
         */
        protected void initStrategies(ApplicationContext context) {
            initMultipartResolver(context);
            initLocaleResolver(context);
            initThemeResolver(context);
            // 初始化HandlerMapping
            initHandlerMappings(context);
        // 初始化HandlerAdapter
            initHandlerAdapters(context);
            initHandlerExceptionResolvers(context);
            initRequestToViewNameTranslator(context);
            initViewResolvers(context);
            initFlashMapManager(context);
        }
    

    org.springframework.web.servlet.handler.AbstractHandlerMethodMapping

    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

    @Override
    @Nullable
    protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
     // RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class); 只要打了注解@RequestMapping的方法
        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;
    }
    
    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中,为后续的路由功能做数据初始化。

    private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
    private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
    

    对于Request参数的一些封装&映射:

    @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是如何组装的。

    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来组装。

    http://static.cyblogs.com/WX20191115-093733@2x.png
    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)) {
                // /* + /hotel -> /hotel ; "/*.*" + "/*.html" -> /*.html
                // However /user + /user -> /usr/user ; /{foo} + /bar -> /{foo}/bar
                return pattern2;
            }
    
            // /hotels/* + /booking -> /hotels/booking
            // /hotels/* + booking -> /hotels/booking
            if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnWildCard())) {
                return concat(pattern1.substring(0, pattern1.length() - 2), pattern2);
            }
    
            // /hotels/** + /booking -> /hotels/**/booking
            // /hotels/** + booking -> /hotels/**/booking
            if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnDoubleWildCard())) {
                return concat(pattern1, pattern2);
            }
    
            int starDotPos1 = pattern1.indexOf("*.");
            if (pattern1ContainsUriVar || starDotPos1 == -1 || this.pathSeparator.equals(".")) {
                // simply concatenate the two patterns
                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;
        }
    

    还是稍微的有点粗,也只描述了我们最最关心的一些点。后面再继续的对每个细节点做一个总结。

    参考地址:

    相关文章

      网友评论

          本文标题:Spring在Web容器中的启动过程

          本文链接:https://www.haomeiwen.com/subject/vvsjictx.html