高性能服务中间件Tomcat工作原理解析(三)

高性能服务中间件Tomcat工作原理解析(三),第1张

高性能服务中间件Tomcat工作原理解析(三) 九 spring整合tomcat核心

 

9.1 核心思想

我们也许有疑问,不管是Springmvc框架还是Springboot框架都需求嵌入一个Tomcat服务中间件,当然也有可能是Jetty,由于本文主要讲的是tomcat所以我们应该想问的是tomcat启动的时候做了什么呢?


	
   	 	org.springframework.web.context.ContextLoaderListener
             
	
    		contextConfigLocation
    		/WEB-INF/root-context.xml
	
	
    		app1
   	 	org.springframework.web.servlet.DispatcherServlet
    	
      	 	 contextConfigLocation
       	 	/WEB-INF/app1-context.xml
   	 
    		1
	
	
   		 app1
    		/app1
        fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);

            
        for (Map.Entry>> entry :
            initializers.entrySet()) {
            try {
                entry.getKey().onStartup(entry.getValue(),getServletContext());
            } catch (ServletException e) {
                log.error(sm.getString("standardContext.sciFail"), e);
                ok = false;
                break;
            }
        }

if (ok) {
         if (!listenerStart()) {
           log.error(sm.getString("standardContext.listenerFail"));
           ok = false;
      }
}

如上代码分别表示了在ServletContext中添加Servlet和ContextLoaderListener这个启动Spring容器的的监听器类.通过这个启动器就可以完成Spring的生命周期了.

10.1 嵌入Servlet

public static final String CONFIGURE_START_EVENT = "configure_start";

protected void fireLifecycleEvent(String type, Object data) {
    LifecycleEvent event = new LifecycleEvent(this, type, data);
    for (LifecycleListener listener : lifecycleListeners) {
        listener.lifecycleEvent(event);
    }
}

我们可以看到ContextConfig这个类是实现了 LifecycleListener接口的,所以所有实现了这个接口的类都会被调用lifecycleEvent方法,那我们来看看contextConfig类这个方法主要做了些什么事情.

public void lifecycleEvent(LifecycleEvent event) {
    try {
        context = (Context) event.getLifecycle();
    } catch (ClassCastException e) { }
    if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) { configureStart();
    } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) { beforeStart();
    } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
        if (originalDocbase != null) {  context.setDocbase(originalDocbase);}
    } else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {configureStop();
    } else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
        init();
    } else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
        destroy();
    }
}

f (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) { configureStart(); 抓取了这段核心代码.行那我们看看在这个观察者里面上下文启动以后这个观察者做了什么.

protected synchronized void configureStart() {
    if (log.isDebugEnabled()) {
        log.debug(sm.getString("contextConfig.start"));
    }
    if (log.isDebugEnabled()) {
        log.debug(sm.getString("contextConfig.xmlSettings",
                context.getName(), Boolean.valueOf(context.getXmlValidation()),
                Boolean.valueOf(context.getXmlNamespaceAware())));
    }
    webConfig();
    if (!context.getIgnoreAnnotations()) {
        applicationAnnotationsConfig();
    }
    if (ok) {
        validateSecurityRoles();
    }
    if (ok) {
        authenticatorConfig();
    }
    if (log.isDebugEnabled()) {
        log.debug("Pipeline Configuration:");
        Pipeline pipeline = context.getPipeline();
        Valve valves[] = null;
        if (pipeline != null) { valves = pipeline.getValves();}
        if (valves != null) {
            for (Valve valve : valves) {
                log.debug("  " + valve.getClass().getName());
            }
        }
    }
    if (ok) {
        context.setConfigured(true);
    } else {
        log.error(sm.getString("contextConfig.unavailable"));
        context.setConfigured(false);
    }
}

webConfig() 核心代码在这个方法里面,那么这个方法里面做了什么呢?我们可以跟进去看一看.

protected void webConfig() {
    	  
if (ok) {
           processServletContainerInitializers();
    }
}

如上所示就是我们的核心代码了.我们看看这个processServletContainerInitializers()方法做了什么呢?

protected void processServletContainerInitializers() {
    List detectedScis;
    try {
        WebappServiceLoader loader = new WebappServiceLoader<>(context);
        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 {
            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;
        }
        if (ht == null) {continue;}
        Class[] types = ht.value();
        if (types == null) {continue;}
        for (Class type : types) {
            if (type.isAnnotation()) {
                handlesTypesAnnotations = true;
            } else {
                handlesTypesNonAnnotations = true;
            }
            Set scis = typeInitializerMap.get(type);
            if (scis == null) {
                scis = new HashSet<>();
                typeInitializerMap.put(type, scis);
            }
            scis.add(sci);
        }
    }
}

detectedScis = loader.load(ServletContainerInitializer.class);核心代码加载context上下文目录里 meta-INF/services/下面所有的ServletContainerInitializer实现类.看明白了这里就是类似于java的spi机制,只是tomcat自己做了实现.通过WebappServiceLoader这个类来对整个上下文路径下的所有meta-INF/services路径下的的接口文件进行来扫描,然后找到接口的实现类获取这个注解 @HandlesTypes,然后制作一个通过注解的值和实现类的map的映射.好了当制作好的映射以后自然就是执行了.

contextConfig类

if (ok) {
    for (Map.Entry>> entry : initializerClassMap.entrySet()) {
        if (entry.getValue().isEmpty()) {
            context.addServletContainerInitializer(entry.getKey(), null);
        } else {
            context.addServletContainerInitializer( entry.getKey(), entry.getValue());
        }
    }
}

   如上核心代码所示,通过遍历映射类把它添加到的private Map>> initializers =new linkedHashMap<>();中,在context启动的时候,这个就是整个configure_start的事件处理过程.那么整个??构造好以后就是调用了我们再回到Standcontext的启动过程.

  
        for (Map.Entry>> entry :
            initializers.entrySet()) {
            try {
                entry.getKey().onStartup(entry.getValue(),getServletContext());
            } catch (ServletException e) {
                log.error(sm.getString("standardContext.sciFail"), e);
                ok = false;
                break;
            }
        }

      如上代码所示, ServletContainerInitializer调用了所有这个类的子类的onStartup()方法.springmvc的启动就是巧妙的运用了这个类的onStartup()方法.所以我们只要在springmvc下运用spi机制就可以带动Spring启动.好了我们看到了tomcat通过调用接口ServletContainerInitializer的调用,来启动tomcat相关容器的所有这里我们可以想想,假如我们要引入Springmvc的话,在tomcat启动的时候,我们其实应该把配置好的servlet映射就注入到ServletContext中.这样在请求执行的时候才能找到对应的servlet类.好了我们来看看这里Springmvc就巧妙的运用了我们的Spi机制.

      如上图所示,是不是感觉似曾相识.ServletContainerInitializer在tomcat中的接口,在spring-web中定义了一个实现类.好的那么就是说tomcat启动的时候,也会调用SpringServletContainerInitializer类的 onStartup()方法了呢.接下来我们看看这个类的源代码做了什么? 

@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    public SpringServletContainerInitializer() {
    }
    public void onStartup(Set> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
        List initializers = new linkedList();
        Iterator var4;
        if(webAppInitializerClasses != null) {
            var4 = webAppInitializerClasses.iterator();
            while(var4.hasNext()) {
                Class waiClass = (Class)var4.next();
                if(!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        initializers.add((WebApplicationInitializer)waiClass.newInstance());
                    } catch (Throwable var7) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
                    }
                }
            }
        }
        if(initializers.isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
        } else {
            servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
            AnnotationAwareOrderComparator.sort(initializers);
            var4 = initializers.iterator();
            while(var4.hasNext()) {
                WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
                initializer.onStartup(servletContext);
            }
        }
    }
}

       从上面代码可以看出 SpringServletContainerInitializer 这里我们主要是定义了这个类,迭代的调用了这个类的onStartup()方法.一看这个类是从ServletContainerInitializer 类的onStartup()方法,通过遍历WebApplicationInitializer调用onStartup方法,首先,SpringServletContainerInitializer作为ServletContainerInitializer的实现类,通过SPI机制,在web容器加载的时候会自动的被调用。这个类上还有一个注解@HandlesTypes,它的作用是将感兴趣的一些类注入到ServletContainerInitializer,在tomcat中有获取这个注解把ServletContainerInitializer的spi实现类作为key注解的value值作为值的一个集合可以看到这个时候从SpringServletContainerInitializer 的onStartup()方法传进来的这个集合参数就是注解扫描注入的类, 而这个类的方法又会扫描找到WebApplicationInitializer的实现类,调用它的onStartup方法,从而起到启动web.xml相同的作用。这样我们就可以通过在启动tomcat的时候通过这个SPI的扩展点来做一些事情了.那么我们看看SpringMVC做了什么.

public class MyWebApplicationInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(AppConfig.class);
        DispatcherServlet servlet = new DispatcherServlet(context);
        ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
        registration.setLoadonStartup(1);
        registration.addMapping("
    protected final void register(String description, ServletContext servletContext) {
        D registration = this.addRegistration(description, servletContext);
        if (registration == null) {
            logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
        } else {
            this.configure(registration);
        }
    }

    protected abstract D addRegistration(String description, ServletContext servletContext);

    protected void configure(D registration) {
        registration.setAsyncSupported(this.asyncSupported);
        if (!this.initParameters.isEmpty()) {
            registration.setInitParameters(this.initParameters);
        }
    }
}

 如上面类所示addRegistration这个方法我们再深入的看看这个方法主要是做了什么.

public class ServletRegistrationBean extends DynamicRegistrationBean {
    private static final String[] DEFAULT_MAPPINGS = new String[]{"/*"};
    private T servlet;
    private Set urlMappings;
    private boolean alwaysMapUrl;
    private int loadOnStartup;
    private MultipartConfigElement multipartConfig;
    public ServletRegistrationBean() {
        this.urlMappings = new linkedHashSet();
        this.alwaysMapUrl = true;
        this.loadonStartup = -1;
    }
    public ServletRegistrationBean(T servlet, String... urlMappings) {
        this(servlet, true, urlMappings);
    }
    public ServletRegistrationBean(T servlet, boolean alwaysMapUrl, String... urlMappings) {
        this.urlMappings = new linkedHashSet();
        this.alwaysMapUrl = true;
        this.loadonStartup = -1;
        Assert.notNull(servlet, "Servlet must not be null");
        Assert.notNull(urlMappings, "UrlMappings must not be null");
        this.servlet = servlet;
        this.alwaysMapUrl = alwaysMapUrl;
        this.urlMappings.addAll(Arrays.asList(urlMappings));
    }
    public void setServlet(T servlet) {
        Assert.notNull(servlet, "Servlet must not be null");
        this.servlet = servlet;
    }
    public T getServlet() {
        return this.servlet;
    }
    public void setUrlMappings(Collection urlMappings) {
        Assert.notNull(urlMappings, "UrlMappings must not be null");
        this.urlMappings = new linkedHashSet(urlMappings);
    }
    public Collection getUrlMappings() {
        return this.urlMappings;
    }

    public void addUrlMappings(String... urlMappings) {
        Assert.notNull(urlMappings, "UrlMappings must not be null");
        this.urlMappings.addAll(Arrays.asList(urlMappings));
    }
    public void setLoadonStartup(int loadOnStartup) {
        this.loadonStartup = loadOnStartup;
    }
    public void setMultipartConfig(MultipartConfigElement multipartConfig) {
        this.multipartConfig = multipartConfig;
    }
    public MultipartConfigElement getMultipartConfig() {
        return this.multipartConfig;
    }
    protected String getDescription() {
        Assert.notNull(this.servlet, "Servlet must not be null");
        return "servlet " + this.getServletName();
    }
    protected Dynamic addRegistration(String description, ServletContext servletContext) {
        String name = this.getServletName();
        return servletContext.addServlet(name, this.servlet);
    }
    protected void configure(Dynamic registration) {
        super.configure(registration);
        String[] urlMapping = StringUtils.toStringArray(this.urlMappings);
        if (urlMapping.length == 0 && this.alwaysMapUrl) {
            urlMapping = DEFAULT_MAPPINGS;
        }

        if (!ObjectUtils.isEmpty(urlMapping)) {
            registration.addMapping(urlMapping);
        }

        registration.setLoadonStartup(this.loadOnStartup);
        if (this.multipartConfig != null) {
            registration.setMultipartConfig(this.multipartConfig);
        }

    }

    public String getServletName() {
        return this.getOrDeduceName(this.servlet);
    }

    public String toString() {
        return this.getServletName() + " urls=" + this.getUrlMappings();
    }
}

如上代码所示servletContext.addServlet(name, this.servlet);核心代码就是往servletContext中放入对应的Serverlet,好的那么我们在看一段代码.

@Conditional({DispatcherServletAutoConfiguration.DispatcherServletRegistrationCondition.class})
@ConditionalOnClass({ServletRegistration.class})
@EnableConfigurationProperties({WebMvcProperties.class})
@import({DispatcherServletAutoConfiguration.DispatcherServletConfiguration.class})
protected static class DispatcherServletRegistrationConfiguration {
    protected DispatcherServletRegistrationConfiguration() {
    }
    @Bean( name = {"dispatcherServletRegistration"})
    @ConditionalOnBean(
        value = {DispatcherServlet.class},
        name = {"dispatcherServlet"}
    )
    public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties, ObjectProvider multipartConfig) {
        DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, webMvcProperties.getServlet().getPath());
        registration.setName("dispatcherServlet");
        registration.setLoadonStartup(webMvcProperties.getServlet().getLoadonStartup());
        multipartConfig.ifAvailable(registration::setMultipartConfig);
        return registration;
    }
}
@Configuration(proxyBeanMethods = false)
@Conditional({DispatcherServletAutoConfiguration.DefaultDispatcherServletCondition.class})
@ConditionalOnClass({ServletRegistration.class})
@EnableConfigurationProperties({HttpProperties.class, WebMvcProperties.class})
protected static class DispatcherServletConfiguration {
    protected DispatcherServletConfiguration() {
    }
    @Bean(name = {"dispatcherServlet"})
    public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {
        DispatcherServlet dispatcherServlet = new DispatcherServlet();
        dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
        dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
      dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
        dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
        dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.isLogRequestDetails());
        return dispatcherServlet;
    }
}

如上代码所示就是Springboot启动装配的时候执行的代码.会在Springboot启动的时候.加载meta-INF下的spring.factories文件下所有的自动装配类.把需要注入到spring中的bean对象添加到spring中.好了这整个过程就串起来了.这里主要是往Tomcat中添加 DispatcherServlet类.

11.3 启动tomcat
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
    return new TomcatWebServer(tomcat, getPort() >= 0);
}

   getWebServer这个方法创建了Tomcat对象,并且做了两件重要的事情:把Connector对象添加到tomcat中,configureEngine(tomcat.getEngine());getWebServer方法返回的是TomcatWebServer。

//TomcatWebServer.java //这里调用构造函数实例化TomcatWebServer

public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
    Assert.notNull(tomcat, "Tomcat Server must not be null");
    this.tomcat = tomcat;
    this.autoStart = autoStart;
    initialize();
}
private void initialize() throws WebServerException {
    logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
    synchronized (this.monitor) {
        try {
            addInstanceIdToEngineName();
            Context context = findContext();
            context.addLifecycleListener((event) -> {
                if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
                    removeServiceConnectors();
                }
            });
            this.tomcat.start();

            rethrowDeferredStartupExceptions();
            try {
                ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
            }
            catch (NamingException ex) {}
            startDaemonAwaitThread();
        }
        catch (Exception ex) {
            stopSilently();
            destroySilently();
            throw new WebServerException("Unable to start embedded Tomcat", ex);
        }
    }
}

this.tomcat.start(); 如上代码所示核型代码逻辑,这里的话我们就看到了springboot的run()函数其实是调用了服务的创建工厂,创建了tomcat服务,然后调用了tomcat的start()方法启动,然后调用了Server()服务启动了tomcat的.

public void start() throws LifecycleException {
    this.getServer();
    this.server.start();
}

public void stop() throws LifecycleException {
    this.getServer();
    this.server.stop();
}
public Server getServer() {
    if (this.server != null) {
        return this.server;
    } else {
        System.setProperty("catalina.useNaming", "false");
        this.server = new StandardServer();
        this.initbaseDir();
        ConfigFileLoader.setSource(new CatalinabaseConfigurationSource(new File(this.basedir), (String)null));
        this.server.setPort(-1);
        Service service = new StandardService();
        service.setName("Tomcat");
        this.server.addService(service);
        return this.server;
    }
}

以上就是整个Springboot跟tomcat的集成过程.

 https://blog.csdn.net/worn_xiao/article/details/122261313

高性能服务中间件Tomcat工作原理解析(一)_worn_xiao的博客-CSDN博客

 高性能服务中间件Tomcat工作原理解析(三)_worn_xiao的博客-CSDN博客

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5685937.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存