zoukankan      html  css  js  c++  java
  • SpringMVC源码:整合tomcat原理

    环境搭建

    搭建出spring源码环境(这里不再介绍),创建一个web的gradle子工程。

    引入servlet和spring mvc依赖:

    image-20211208173447346

    配置tomcat:

    image-20211209104641636

    根据官方文档描述,配置DispatcherServlet:

    /**
     * 只要写了这个,相当于配置了SpringMVC的DispatcherServlet
     * tomcat只要一启动就会加载它
     * 	1.创建了容器、指定配置类(所有ioc、aop等spring功能就ok了)
     * 	2.注册一个servlet:DispatcherServlet
     * 	3.以后所有的请求都交给了DispatcherServlet
     *
     * 必须Servlet3.0以上才可以,Tomcat6.0以上才支持Servlet3.0规范
     * Servlet3.0时JavaEE的Web规范标准,Tomcat是Servlet规范的一个实现
     * @author wen.jie
     * @date 2021/12/8 17:08
     */
    public class MyWebApplicationInitializer implements WebApplicationInitializer {
    
    	@Override
    	public void onStartup(ServletContext servletContext) {
    
    		// 自己创建一个IOC容器
    		AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    		//注册主配置类
    		context.register(AppConfig.class);
    
    		// 自己创建出DispatcherServlet,并且保存IOC容器
    		DispatcherServlet servlet = new DispatcherServlet(context);
    		//利用servlet规范添加servlet
    		ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
    		registration.setLoadOnStartup(1);
            //映射路径
    		registration.addMapping("/");
    	}
    }
    

    创建AppConfig配置类:

    @ComponentScan("com.wj")
    @Configuration
    public class AppConfig {
    }
    

    创建controller:

    @RestController
    public class MyController {
    	@RequestMapping("test")
    	public String test() {
    		return "hello spring mvc";
    	}
    }
    

    启动tomcat观察控制台:初始化了名字叫做"app"的servlet,这个也是我们在MyWebApplicationInitializer中配置的Servlet的名字。

    image-20211208174228704

    访问:localhost:8080/mvc/test:

    image-20211208174402378

    整合成功。

    整合原理

    ServletContainerInitializer和@HandlesTypes

    要说tomcat启动springmvc应用的原理,先要说一个Servlet规范。

    进入:https://download.oracle.com/otndocs/jcp/servlet-4-final-eval-spec/index.html

    下载一个关于Servlet4.0规范的文档:

    image-20211209093238713

    在pdf的8-95页中,有这样一段描述:

    The ServletContainerInitializer class is looked up via the jar services API.
    For each application, an instance of the ServletContainerInitializer is
    created by the container at application startup time. The framework providing an
    implementation of the ServletContainerInitializer MUST bundle in the
    META-INF/services directory of the jar file a file called
    javax.servlet.ServletContainerInitializer, as per the jar services API,
    that points to the implementation class of the ServletContainerInitializer.

    In addition to the ServletContainerInitializer we also have an annotation -
    HandlesTypes. The HandlesTypes annotation on the implementation of the
    ServletContainerInitializer is used to express interest in classes that may
    have annotations (type, method or field level annotations) specified in the value of
    the HandlesTypes or if it extends / implements one those classes anywhere in the
    class’ super types. The HandlesTypes annotation is applied irrespective of the
    setting of metadata-complete.

    When examining the classes of an application to see if they match any of the criteria
    specified by the HandlesTypes annotation of a ServletContainerInitializer,
    the container may run into class loading problems if one or more of the application's
    optional JAR files are missing. Since the container is not in a position to decide
    whether these types of class loading failures will prevent the application from
    working correctly, it must ignore them, while at the same time providing a
    configuration option that would log them.

    If an implementation of ServletContainerInitializer does not have the
    @HandlesTypes annotation, or if there are no matches to any of the HandlesType
    specified, then it will get invoked once for every application with null as the value
    of the Set. This will allow for the initializer to determine based on the resources
    available in the application whether it needs to initialize a servlet / filter or not.

    The onStartup method of the ServletContainerInitializer will be invoked
    when the application is coming up before any of the servlet listener events are fired.

    The onStartup method of the ServletContainerInitializer is called with a
    Set of Classes that either extend / implement the classes that the initializer
    expressed interest in or if it is annotated with any of the classes specified via the
    @HandlesTypes annotation.

    大致含义如下:

    对于每个应用程序,ServletContainerInitializer的一个实例是由容器在应用程序启动时创建。 框架提供的ServletContainerInitializer必须声明在jar包中的META-INF/services目录且文件名必须为javax.servlet.ServletContainerInitializer,文件内容指向ServletContainerInitializer的实现类。

    除了ServletContainerInitializer,还有一个注解@HandlesTypesServletContainerInitializer实现类上的注解@HandlesTypes,用来获取感兴趣的类。

    如果ServletContainerInitializer的实现没有@HandlesType注释,或者与任何一个HandlesType都不匹配,它会将null值设置到Set集合中。这将允许初始化器根据应用程序中可用的资源来确定是否需要初始化servlet /Filter。

    当应用程序在任何Servlet监听事件触发之前启动时,ServletContainerinitializeronStartup方法将被调用。

    SpringServletContainerInitializer和WebApplicationInitializer

    tomcat中有一个org.apache.catalina.startup.ContextConfig类,该类是启动上下文的时间监听器,它配置该上下文的属性以及相关的已定义servlet。 其中有一个processServletContainerInitializers方法:

    	/**
        * 此方法处理了ServletContainerInitializer和@HandlesTypes
        */
    	protected void processServletContainerInitializers() {
    
            List<ServletContainerInitializer> detectedScis;
            try {
                //创建一个WebappServiceLoader(这个是tomcat自己实现的ServiceLoader,并非是jdk原生的ServiceLoader,不过内部实现都大同小异,也遵循了SPI的规范)
                WebappServiceLoader<ServletContainerInitializer> loader = new WebappServiceLoader<>(context);
                //加载所有的ServletContainerInitializer
                detectedScis = loader.load(ServletContainerInitializer.class);
            } catch (IOException e) {
                log.error(sm.getString(
                        "contextConfig.servletContainerInitializerFail",
                        context.getName()),
                    e);
                ok = false;
                return;
            }
    
            for (ServletContainerInitializer sci : detectedScis) {
                initializerClassMap.put(sci, new HashSet<>());
    
                HandlesTypes ht;
                try {
                    //获取ServletContainerInitializer类上的HandlesTypes注解
                    ht = sci.getClass().getAnnotation(HandlesTypes.class);
                } catch (Exception e) {
                    if (log.isDebugEnabled()) {
                        log.info(sm.getString("contextConfig.sci.debug",
                                sci.getClass().getName()),
                                e);
                    } else {
                        log.info(sm.getString("contextConfig.sci.info",
                                sci.getClass().getName()));
                    }
                    continue;
                }
                //等于null就跳出循环
                if (ht == null) {
                    continue;
                }
                //获取HandlesTypes注解中配置的所有感兴趣的类
                Class<?>[] types = ht.value();
                if (types == null) {
                    continue;
                }
    
                for (Class<?> type : types) {
                    //判断感兴趣的类是否是注解
                    if (type.isAnnotation()) {
                        handlesTypesAnnotations = true;
                    } else {
                        handlesTypesNonAnnotations = true;
                    }
                    //将感兴趣的类和ServletContainerInitializer保存起来
                    Set<ServletContainerInitializer> scis =
                            typeInitializerMap.get(type);
                    if (scis == null) {
                        scis = new HashSet<>();
                        typeInitializerMap.put(type, scis);
                    }
                    scis.add(sci);
                }
            }
        }
    

    tomcat启动时找到ServletContainerInitializer和@HandlesTypes后,会遍历/WEB-INF/classes和jar包中的所有类,去查看该类是否是@HandlesTypes中感兴趣的类。

    如果对这个过程感兴趣,可以查看org.apache.catalina.startup.ContextConfig中的processClasses(WebXml webXml, Set<WebXml> orderedFragments)方法,这里不进行介绍。

    tomcat在启动时会启动Context,默认是StandardContext继承了LifecycleBase类,所以会回调startInternal方法,而在startInternal方法中就有一段调用ServletContainerInitializer的onStartup方法:

                // Call ServletContainerInitializers
                for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
                    initializers.entrySet()) {
                    try {
                        //entry.getValue()即 @HandlesTypes中配置的所有类
                        entry.getKey().onStartup(entry.getValue(),
                                getServletContext());
                    } catch (ServletException e) {
                        log.error(sm.getString("standardContext.sciFail"), e);
                        ok = false;
                        break;
                    }
                }
    
    image-20211209135232497

    在spring web模块中,有一个这样的文件:javax.servlet.ServletContainerInitializer,内容是org.springframework.web.SpringServletContainerInitializer

    因为符合java的SPI规范,所以这个SpringServletContainerInitializer类会被tomcat读取到,并调用onstartUp方法。

    image-20211209135623021

    再看SpringServletContainerInitializer类:

    @HandlesTypes(WebApplicationInitializer.class) //标注了感兴趣的类是WebApplicationInitializer
    public class SpringServletContainerInitializer implements ServletContainerInitializer {
    
        //webAppInitializerClasses : 传入了所有实现了WebApplicationInitializer接口的类
    	@Override
    	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
    			throws ServletException {
    
    		List<WebApplicationInitializer> initializers = Collections.emptyList();
    
    		if (webAppInitializerClasses != null) {
    			initializers = new ArrayList<>(webAppInitializerClasses.size());
    			for (Class<?> waiClass : webAppInitializerClasses) {
    				// Be defensive: Some servlet containers provide us with invalid classes,
    				// no matter what @HandlesTypes says...
                                    //判断不是接口、不是抽象类,并且实现了WebApplicationInitializer接口
    				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
    						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
    					try {
                                                    //调用Constructor的newInstance方法创建实例,并添加到initializers集合中
    						initializers.add((WebApplicationInitializer)
    								ReflectionUtils.accessibleConstructor(waiClass).newInstance());
    					}
    					catch (Throwable ex) {
    						throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
    					}
    				}
    			}
    		}
    		//initializers集合为空,直接结束该方法
    		if (initializers.isEmpty()) {
    			servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
    			return;
    		}
    
    		servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
                    //根据Order Priority排序
    		AnnotationAwareOrderComparator.sort(initializers);
                    //遍历initializers,依次调用onStartup方法
    		for (WebApplicationInitializer initializer : initializers) {
    			initializer.onStartup(servletContext);
    		}
    	}
    
    }
    

    而我们在之前案例中新建了一个MyWebApplicationInitializer实现了WebApplicationInitializer,所以它的onStartup方法会被调用。

    配置并刷新容器

    在刚才的Servlet规范文档中有这样一段:

    A servlet is managed through a well defined life cycle that defines how it is loaded and instantiated, is initialized, handles requests from clients, and is taken out of service. This life cycle is expressed in the API by the init, service, and destroy methods of the javax.servlet.Servlet interface that all servlets must implement directly or indirectly through the GenericServlet or HttpServlet abstract classes.

    解释如下:

    servlet是通过一个定义良好的生命周期来管理的,该生命周期定义了如何加载和实例化servlet、如何初始化它、如何处理来自客户机的请求以及如何退出服务。 这个生命周期通过javax.servlet.Servlet接口的init、service和destroy方法在API中表示,所有的servlet都必须通过GenericServlet或HttpServlet抽象类直接或间接地实现这些接口。

    init方法是在Servlet初始化时候就会调用。我们再看DispatcherServlet的父类继承关系:

    image-20211209161124015

    GenericServletHttpServlet都实现了init方法,不过都是空实现,HttpServletBean重写init方法:

    init方法中留了一个空方法initServletBean给子类实现。

    	@Override
    	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();
    	}
    

    子类FrameworkServletinitServletBean方法进行了实现:

    	@Override
    	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 {
    			//初始化web容器
    			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");
    		}
    	}
    

    其中有重要一步:initWebApplicationContext,ioc容器就是在这里初始化的:

    	protected WebApplicationContext initWebApplicationContext() {
    		WebApplicationContext rootContext =
    				WebApplicationContextUtils.getWebApplicationContext(getServletContext());//父容器
    		WebApplicationContext wac = null;//子容器
    		//this.webApplicationContext 是之前new DispatcherServlet(webApplicationContext) 传入的那一个容器
    		if (this.webApplicationContext != null) {
    			// A context instance was injected at construction time -> use it
    			wac = this.webApplicationContext;//当前web的IOC容器
    			if (wac instanceof ConfigurableWebApplicationContext) {
    				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
    				if (!cwac.isActive()) {
    					// The context has not yet been refreshed -> provide services such as
    					// setting the parent context, setting the application context id, etc
    					if (cwac.getParent() == null) {
    						// The context instance was injected without an explicit parent -> set
    						// the root application context (if any; may be null) as the parent
    						cwac.setParent(rootContext);//设置父子容器关系
    					}
    					//配置和刷新容器
    					configureAndRefreshWebApplicationContext(cwac);
    				}
    			}
    		}
    		if (wac == null) {
    			// No context instance was injected at construction time -> see if one
    			// has been registered in the servlet context. If one exists, it is assumed
    			// that the parent context (if any) has already been set and that the
    			// user has performed any initialization such as setting the context id
    			wac = findWebApplicationContext();
    		}
    		if (wac == null) {
    			// No context instance is defined for this servlet -> create a local one
    			wac = createWebApplicationContext(rootContext);
    		}
    
    		if (!this.refreshEventReceived) {
    			// Either the context is not a ConfigurableApplicationContext with refresh
    			// support or the context injected at construction time had already been
    			// refreshed -> trigger initial onRefresh manually here.
    			synchronized (this.onRefreshMonitor) {
    				//onRefresh方法模板,DispatcherServlet实现了改方法
    				onRefresh(wac);
    			}
    		}
    
    		if (this.publishContext) {
    			// Publish the context as a servlet context attribute.
    			String attrName = getServletContextAttributeName();
    			getServletContext().setAttribute(attrName, wac);
    		}
    
    		return wac;
    	}
    

    configureAndRefreshWebApplicationContext方法如下:

    	protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
    		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
    			// The application context id is still set to its original default value
    			// -> assign a more useful id based on available information
    			if (this.contextId != null) {
    				wac.setId(this.contextId);
    			}
    			else {
    				// Generate default id...
    				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
    						ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
    			}
    		}
    		//设置一些属性
    		wac.setServletContext(getServletContext());
    		wac.setServletConfig(getServletConfig());
    		wac.setNamespace(getNamespace());
                    //设置SourceFilteringListener,并通过它完成DispatcherServlet的九大组件初始化
    		wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
    
    		// The wac environment's #initPropertySources will be called in any case when the context
    		// is refreshed; do it eagerly here to ensure servlet property sources are in place for
    		// use in any post-processing or initialization that occurs below prior to #refresh
    		//web环境准备
    		ConfigurableEnvironment env = wac.getEnvironment();
    		if (env instanceof ConfigurableWebEnvironment) {
    			((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
    		}
    		//在刷新容器前的后置处理,默认为空方法
    		postProcessWebApplicationContext(wac);
    		//执行所有的ApplicationContextInitializer<ConfigurableApplicationContext>
    		applyInitializers(wac);
    		//调用容器刷新方法
    		wac.refresh();
    	}
    

    至此:容器的创建和刷新完成。

    流程图

    整合原理流程如下:

    spring web ioc容器加载

  • 相关阅读:
    RK3399之时钟
    C之{}注意点
    ARM之不用段寄存猜想
    linux驱动之入口
    android之HAL
    git
    消息中间之ActiveMQ
    Maven之阿里云镜像仓库配置
    清理Oracle临时表空间
    Tomcat控制台日志输出到本地文件
  • 原文地址:https://www.cnblogs.com/wwjj4811/p/15673030.html
Copyright © 2011-2022 走看看