zoukankan      html  css  js  c++  java
  • Spring Boot 集成undertow作为web容器分析

    背景

    项目中使用了undertow作为web容器,以获得更好的性能,这里先不谈undertow相比其他容器(tomcat,jetty等)的优缺点,只是单纯分析下Spring Boot 是如何集成web容器的

    项目中使用undertow

    Spring Boot默认使用tomcat作为内嵌web容器,可以通过修改pom更改为undertow

    <dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-web</artifactId>
    			<exclusions>
    				<exclusion>
    					<groupId>org.springframework.boot</groupId>
    					<artifactId>spring-boot-starter-tomcat</artifactId>
    				</exclusion>
    			</exclusions>
    		</dependency>
    		<!-- 使用undertow 作为web服务器 -->
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-undertow</artifactId>
    		</dependency>
    

    原理分析

    通过上面简单的修改就可以使用undertow, 那么Spring Boot内部是如何实现的呢? 带着这个疑问,走了下源码(本人使用Spring Boot版本为2.0.6)。

    @SpringBootApplication
    public class UsBridgeApp {
    	
        public static void main( String[] args ) {
        	SpringApplication.run(UsBridgeApp.class, args);
        }
    }
    

    上面是一个简单的入口程序,主要是使用了SpringBootApplication注解和main方法中运行SpringApplication 的静态run方法,进入SpringApplication不难发现,静态run方法最终也是调用SpringApplication类实例run方法

    /**
    	 * Run the Spring application, creating and refreshing a new
    	 * {@link ApplicationContext}.
    	 * @param args the application arguments (usually passed from a Java main method)
    	 * @return a running {@link ApplicationContext}
    	 */
    	public ConfigurableApplicationContext run(String... args) {
    		StopWatch stopWatch = new StopWatch();
    		stopWatch.start();
    		ConfigurableApplicationContext context = null;
    		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    		configureHeadlessProperty();
    		SpringApplicationRunListeners listeners = getRunListeners(args);
    		listeners.starting();
    		try {
    			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
    					args);
    			ConfigurableEnvironment environment = prepareEnvironment(listeners,
    					applicationArguments);
    			configureIgnoreBeanInfo(environment);
    			Banner printedBanner = printBanner(environment);
    			context = createApplicationContext();
    			exceptionReporters = getSpringFactoriesInstances(
    					SpringBootExceptionReporter.class,
    					new Class[] { ConfigurableApplicationContext.class }, context);
    			prepareContext(context, environment, listeners, applicationArguments,
    					printedBanner);
    			refreshContext(context);
    			afterRefresh(context, applicationArguments);
    			stopWatch.stop();
    			if (this.logStartupInfo) {
    				new StartupInfoLogger(this.mainApplicationClass)
    						.logStarted(getApplicationLog(), stopWatch);
    			}
    			listeners.started(context);
    			callRunners(context, applicationArguments);
    		}
    		catch (Throwable ex) {
    			handleRunFailure(context, ex, exceptionReporters, listeners);
    			throw new IllegalStateException(ex);
    		}
    
    		try {
    			listeners.running(context);
    		}
    		catch (Throwable ex) {
    			handleRunFailure(context, ex, exceptionReporters, null);
    			throw new IllegalStateException(ex);
    		}
    		return context;
    	}
    

    代码先是通过createApplicationContext()创建了ApplicationContext,之后调refreshContext方法,它们做了什么呢?

    protected ConfigurableApplicationContext createApplicationContext() {
    		Class<?> contextClass = this.applicationContextClass;
    		if (contextClass == null) {
    			try {
    				switch (this.webApplicationType) {
    				case SERVLET:
    					contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
    					break;
    				case REACTIVE:
    					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
    					break;
    				default:
    					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
    				}
    			}
    			catch (ClassNotFoundException ex) {
    				throw new IllegalStateException(
    						"Unable create a default ApplicationContext, "
    								+ "please specify an ApplicationContextClass",
    						ex);
    			}
    		}
    		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
    	}
    

    通过判断webApplicationType值来选择加载哪个类, WebApplicationType 通过静态方法获得

    static WebApplicationType deduceFromClasspath() {
    		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
    				&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
    				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
    			return WebApplicationType.REACTIVE;
    		}
    		for (String className : SERVLET_INDICATOR_CLASSES) {
    			if (!ClassUtils.isPresent(className, null)) {
    				return WebApplicationType.NONE;
    			}
    		}
    		return WebApplicationType.SERVLET;
    	}
    

    可以看出默认使用WebApplicationType.SERVLET, 所以默认加载的spring context 是 DEFAULT_SERVLET_WEB_CONTEXT_CLASS,

    /**
    	 * The class name of application context that will be used by default for web
    	 * environments.
    	 */
    	public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
    			+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
    

    回到refreshContext,实际是调了AbstractApplicationContext 的refresh方法

    @Override
    	public void refresh() throws BeansException, IllegalStateException {
    		synchronized (this.startupShutdownMonitor) {
    			// Prepare this context for refreshing.
    			prepareRefresh();
    
    			// Tell the subclass to refresh the internal bean factory.
    			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    
    			// Prepare the bean factory for use in this context.
    			prepareBeanFactory(beanFactory);
    
    			try {
    				// Allows post-processing of the bean factory in context subclasses.
    				postProcessBeanFactory(beanFactory);
    
    				// Invoke factory processors registered as beans in the context.
    				invokeBeanFactoryPostProcessors(beanFactory);
    
    				// Register bean processors that intercept bean creation.
    				registerBeanPostProcessors(beanFactory);
    
    				// Initialize message source for this context.
    				initMessageSource();
    
    				// Initialize event multicaster for this context.
    				initApplicationEventMulticaster();
    
    				// Initialize other special beans in specific context subclasses.
    				onRefresh();
    
    				// Check for listener beans and register them.
    				registerListeners();
    
    				// Instantiate all remaining (non-lazy-init) singletons.
    				finishBeanFactoryInitialization(beanFactory);
    
    				// Last step: publish corresponding event.
    				finishRefresh();
    			}
    
    			catch (BeansException ex) {
    				if (logger.isWarnEnabled()) {
    					logger.warn("Exception encountered during context initialization - " +
    							"cancelling refresh attempt: " + ex);
    				}
    
    				// Destroy already created singletons to avoid dangling resources.
    				destroyBeans();
    
    				// Reset 'active' flag.
    				cancelRefresh(ex);
    
    				// Propagate exception to caller.
    				throw ex;
    			}
    
    			finally {
    				// Reset common introspection caches in Spring's core, since we
    				// might not ever need metadata for singleton beans anymore...
    				resetCommonCaches();
    			}
    		}
    	}
    

    这个方法做了很多事情,其中调用的onRefresh 就是调用了AnnotationConfigServletWebServerApplicationContext的父类ServletWebServerApplicationContext方法

    @Override
    	protected void onRefresh() {
    		super.onRefresh();
    		try {
    			createWebServer();
    		}
    		catch (Throwable ex) {
    			throw new ApplicationContextException("Unable to start web server", ex);
    		}
    	}
    

    终于看到了想看到的,createWebServer()似乎就是我要找的了,继续...

    private void createWebServer() {
    		WebServer webServer = this.webServer;
    		ServletContext servletContext = getServletContext();
    		if (webServer == null && servletContext == null) {
    			ServletWebServerFactory factory = getWebServerFactory();
    			this.webServer = factory.getWebServer(getSelfInitializer());
    		}
    		else if (servletContext != null) {
    			try {
    				getSelfInitializer().onStartup(servletContext);
    			}
    			catch (ServletException ex) {
    				throw new ApplicationContextException("Cannot initialize servlet context",
    						ex);
    			}
    		}
    		initPropertySources();
    	}
    

    ServletWebServerFactory 创建了webServer , ServletWebServerFactory 通过从spring容器中获得

    protected ServletWebServerFactory getWebServerFactory() {
    		// Use bean names so that we don't consider the hierarchy
    		String[] beanNames = getBeanFactory()
    				.getBeanNamesForType(ServletWebServerFactory.class);
    		if (beanNames.length == 0) {
    			throw new ApplicationContextException(
    					"Unable to start ServletWebServerApplicationContext due to missing "
    							+ "ServletWebServerFactory bean.");
    		}
    		if (beanNames.length > 1) {
    			throw new ApplicationContextException(
    					"Unable to start ServletWebServerApplicationContext due to multiple "
    							+ "ServletWebServerFactory beans : "
    							+ StringUtils.arrayToCommaDelimitedString(beanNames));
    		}
    		return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
    	}
    

    现在只要弄清楚Spring Boot 此时到底使用的哪个factory 便可,Spring Boot 基本配置都在spring-boot-autoconfigure 这个jar中,首先看org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration

    @Configuration
    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
    @ConditionalOnClass(ServletRequest.class)
    @ConditionalOnWebApplication(type = Type.SERVLET)
    @EnableConfigurationProperties(ServerProperties.class)
    @Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
    		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
    		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
    		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
    public class ServletWebServerFactoryAutoConfiguration {
    
    	@Bean
    	public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(
    			ServerProperties serverProperties) {
    		return new ServletWebServerFactoryCustomizer(serverProperties);
    	}
    
    	@Bean
    	@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
    	public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
    			ServerProperties serverProperties) {
    		return new TomcatServletWebServerFactoryCustomizer(serverProperties);
    	}
        
            ...
    }    
    

    通过Import注解加入了 Tomcat, Jetty,Undertow三种支持,

    /*
     * Copyright 2012-2018 the original author or authors.
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    package org.springframework.boot.autoconfigure.web.servlet;
    
    import javax.servlet.Servlet;
    
    import io.undertow.Undertow;
    import org.apache.catalina.startup.Tomcat;
    import org.apache.coyote.UpgradeProtocol;
    import org.eclipse.jetty.server.Server;
    import org.eclipse.jetty.util.Loader;
    import org.eclipse.jetty.webapp.WebAppContext;
    import org.xnio.SslClientAuthMode;
    
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.boot.autoconfigure.condition.SearchStrategy;
    import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
    import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
    import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
    import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * Configuration classes for servlet web servers
     * <p>
     * Those should be {@code @Import} in a regular auto-configuration class to guarantee
     * their order of execution.
     *
     * @author Phillip Webb
     * @author Dave Syer
     * @author Ivan Sopov
     * @author Brian Clozel
     * @author Stephane Nicoll
     */
    @Configuration
    class ServletWebServerFactoryConfiguration {
    
    	@Configuration
    	@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
    	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
    	public static class EmbeddedTomcat {
    
    		@Bean
    		public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
    			return new TomcatServletWebServerFactory();
    		}
    
    	}
    
    	/**
    	 * Nested configuration if Jetty is being used.
    	 */
    	@Configuration
    	@ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
    			WebAppContext.class })
    	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
    	public static class EmbeddedJetty {
    
    		@Bean
    		public JettyServletWebServerFactory JettyServletWebServerFactory() {
    			return new JettyServletWebServerFactory();
    		}
    
    	}
    
    	/**
    	 * Nested configuration if Undertow is being used.
    	 */
    	@Configuration
    	@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
    	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
    	public static class EmbeddedUndertow {
    
    		@Bean
    		public UndertowServletWebServerFactory undertowServletWebServerFactory() {
    			return new UndertowServletWebServerFactory();
    		}
    
    	}
    
    }
    
    

    最开始的pom配置因为排除了Tomcat.class 类的依赖而引入了Undertow.class类,所有项目实际加载的ServletWebServerFactory 为UndertowServletWebServerFactory;

  • 相关阅读:
    Understanding MaxServicePointIdleTime and DefaultConnectionLimit
    What is the Windows Azure?
    Microsoft Reports Significant Performance Improvements in Entity Framework 5 (InfoQ)
    Upload File using HttpWebRequest (Multipart Form)
    Difference between VM role(paas) and VM(Iaas)
    using sql server or sql azure for session state store in asp.net
    Performance Considerations for Entity Framework 5
    RMQ
    poj 1330 Nearest Common Ancestors
    Lca与Rmq
  • 原文地址:https://www.cnblogs.com/ljgeng/p/11079091.html
Copyright © 2011-2022 走看看