06.Spring Boot 之 web 容器装配原理

1. 定制容器配置

代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见 tutorial-spring-boot-core/tutorial-spring-boot-servlet 工程

1.1 修改配置文件

修改和 server 有关的配置,见 org.springframework.boot.autoconfigure.web.ServerProperties

# 属性都在 ServerProperties 文件中
server.port=8080
server.servlet.context-path=/servlet
1.2 WebServerFactoryCustomizer

定制化 WebServerFactoryCustomizer 容器

@Configuration
public class WebServerFactoryCustomizerConfig {

    @Bean
    public WebServerFactoryCustomizer<ConfigurableTomcatWebServerFactory> webServerFactoryWebServerFactoryCustomizer() {
        return factory -> {
            // 定制嵌入式的 Servlet 容器相关的规则
            factory.setPort(9999);
        };
    }

}
1.3 Web 容器自动装配原理
1.3.1 Tomcat 自动装配流程图

Tomcat 容器自动装配原理

1.3.2 ServletWebServerFactoryAutoConfiguration 装配原理

它主要是自动装配一个 Web 容器

1. ServletWebServerFactoryAutoConfiguration

给容器中导入嵌入式的容器类,如 EmbeddedTomcat 组件

除此之外,还导入了一个 BeanPostProcessorsRegistrar 组件,这个是用来给容器中添加 WebServerFactoryCustomizerBeanPostProcessor 组件

@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 {
2. EmbeddedTomcat

如果当前环境中有 Servlet、Tomcat、UpgradeProtocol 类,且没有用户自定义的 ServletWebServerFactory 组件,会给容器中注入一个 TomcatServletWebServerFactory 组件

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

}
3. TomcatServletWebServerFactory

Spring IOC 容器在调用 refresh() 方法时,会调用 onRefresh() 方法,然后调用 createWebServer(),它会从容器中获取 ServletWebServerFactory 组件,然后调用 getWebServer() 方法

public WebServer getWebServer(ServletContextInitializer... initializers) {

    // 创建 tomcat
	Tomcat tomcat = new Tomcat();
	File baseDir = (this.baseDirectory != null) ? this.baseDirectory
			: createTempDir("tomcat");
	tomcat.setBaseDir(baseDir.getAbsolutePath());
	Connector connector = new Connector(this.protocol);
	tomcat.getService().addConnector(connector);
	customizeConnector(connector);
	tomcat.setConnector(connector);
	tomcat.getHost().setAutoDeploy(false);
	configureEngine(tomcat.getEngine());
	for (Connector additionalConnector : this.additionalTomcatConnectors) {
		tomcat.getService().addConnector(additionalConnector);
	}
	prepareContext(tomcat.getHost(), initializers);
	
	// 在这里会启动 tomcat,最终会调用 TomcatWebServer 的 initialize 方法启动 tomcat
	return getTomcatWebServer(tomcat);
}
4. TomcatWebServer
private void initialize() throws WebServerException {
	logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
	synchronized (this.monitor) {
		try {
			
			...

			// Start the server to trigger initialization listeners
			// 启动 tomcat
			this.tomcat.start();

			...
		}
		catch (Exception ex) {
			stopSilently();
			throw new WebServerException("Unable to start embedded Tomcat", ex);
		}
	}
}
5. BeanPostProcessorsRegistrar

给容器中导入了 WebServerFactoryCustomizerBeanPostProcessor 组件,用来处理自定义属性的

public static class BeanPostProcessorsRegistrar
		implements ImportBeanDefinitionRegistrar, BeanFactoryAware {

	...

	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
			BeanDefinitionRegistry registry) {
		if (this.beanFactory == null) {
			return;
		}
		
		// 主要是给容器中导入 WebServerFactoryCustomizerBeanPostProcessor
		registerSyntheticBeanIfMissing(registry,
				"webServerFactoryCustomizerBeanPostProcessor",
				WebServerFactoryCustomizerBeanPostProcessor.class);
	}

	...
}
6. WebServerFactoryCustomizerBeanPostProcessor

它是一个 BeanPostProcessor 后置处理器,在这里会调用 WebServerFactoryCustomizer 组件的 customize() 方法

public class WebServerFactoryCustomizerBeanPostProcessor
		implements BeanPostProcessor, BeanFactoryAware {
		
	...	
	
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName)
			throws BeansException {
		// 判断 bean 类型,属于 WebServerFactory,进行特殊处理
		if (bean instanceof WebServerFactory) {
			postProcessBeforeInitialization((WebServerFactory) bean);
		}
		return bean;
	}
	
	...
		
	private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
		LambdaSafe
				.callbacks(WebServerFactoryCustomizer.class, getCustomizers(),
						webServerFactory)
				.withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
				.invoke((customizer) -> customizer.customize(webServerFactory));
	}
	
	...
	
}
1.3.3 EmbeddedWebServerFactoryCustomizerAutoConfiguration 装配原理

它主要是自动装配一个 Web 容器的属性自定义

1. EmbeddedWebServerFactoryCustomizerAutoConfiguration

ConditionalOnWebApplication 注解表示在 web 环境下生效

@EnableConfigurationProperties(ServerProperties.class) 给容器中导入 ServerProperties 组件,这也就是我们通过配置文件修改 tomcat 属性的原因

@Configuration
@ConditionalOnWebApplication
@EnableConfigurationProperties(ServerProperties.class)
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {
	 
	// 如果当前环境中有 `Tomcat`、`UpgradeProtocol` 类,那么会给容器中导入 `TomcatWebServerFactoryCustomizer`,类似的还有 jetty、undertow、netty 等,所以切换 web 容器时,只需要切换 jar 包即可
	@Configuration
	@ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
	public static class TomcatWebServerFactoryCustomizerConfiguration {

		@Bean
		public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(
				Environment environment, ServerProperties serverProperties) {
			return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
		}

	}

    ...

}
2. TomcatWebServerFactoryCustomizer

根据配置文件的属性修改 tomcat 的值

public class TomcatWebServerFactoryCustomizer implements
		WebServerFactoryCustomizer<ConfigurableTomcatWebServerFactory>, Ordered {
		    
	...   
	
	// 根据配置文件的属性修改 tomcat 的值	 
	@Override
	public void customize(ConfigurableTomcatWebServerFactory factory) {
		ServerProperties properties = this.serverProperties;
		ServerProperties.Tomcat tomcatProperties = properties.getTomcat();
		PropertyMapper propertyMapper = PropertyMapper.get();
		propertyMapper.from(tomcatProperties::getBasedir).whenNonNull()
				.to(factory::setBaseDirectory);
		propertyMapper.from(tomcatProperties::getBackgroundProcessorDelay).whenNonNull()
				.as(Duration::getSeconds).as(Long::intValue)
				.to(factory::setBackgroundProcessorDelay);
		customizeRemoteIpValve(factory);
		propertyMapper.from(tomcatProperties::getMaxThreads).when(this::isPositive)
				.to((maxThreads) -> customizeMaxThreads(factory,
						tomcatProperties.getMaxThreads()));
		propertyMapper.from(tomcatProperties::getMinSpareThreads).when(this::isPositive)
				.to((minSpareThreads) -> customizeMinThreads(factory, minSpareThreads));
		propertyMapper.from(this::determineMaxHttpHeaderSize).whenNonNull()
				.asInt(DataSize::toBytes).when(this::isPositive)
				.to((maxHttpHeaderSize) -> customizeMaxHttpHeaderSize(factory,
						maxHttpHeaderSize));
		propertyMapper.from(tomcatProperties::getMaxSwallowSize).whenNonNull()
				.asInt(DataSize::toBytes)
				.to((maxSwallowSize) -> customizeMaxSwallowSize(factory, maxSwallowSize));
		propertyMapper.from(tomcatProperties::getMaxHttpPostSize).asInt(DataSize::toBytes)
				.when((maxHttpPostSize) -> maxHttpPostSize != 0)
				.to((maxHttpPostSize) -> customizeMaxHttpPostSize(factory,
						maxHttpPostSize));
		propertyMapper.from(tomcatProperties::getAccesslog)
				.when(ServerProperties.Tomcat.Accesslog::isEnabled)
				.to((enabled) -> customizeAccessLog(factory));
		propertyMapper.from(tomcatProperties::getUriEncoding).whenNonNull()
				.to(factory::setUriEncoding);
		propertyMapper.from(properties::getConnectionTimeout).whenNonNull()
				.to((connectionTimeout) -> customizeConnectionTimeout(factory,
						connectionTimeout));
		propertyMapper.from(tomcatProperties::getMaxConnections).when(this::isPositive)
				.to((maxConnections) -> customizeMaxConnections(factory, maxConnections));
		propertyMapper.from(tomcatProperties::getAcceptCount).when(this::isPositive)
				.to((acceptCount) -> customizeAcceptCount(factory, acceptCount));
		customizeStaticResources(factory);
		customizeErrorReportValve(properties.getError(), factory);
	}	    

	...
   
}

2. 注册 Servlet 三大组件

2.1 注册 servlet
1. MyServlet
public class MyServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().print("Hello SpringBoot Servlet");
    }

}
2. ServletConfig
@Configuration
public class ServletConfig {

    @Bean
    public ServletRegistrationBean<MyServlet> myServlet() {
        return new ServletRegistrationBean<>(new MyServlet(), "/myServlet");
    }

}
2.2 注册 filter
1. MyFilter
public class MyFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("MyFilter init");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("MyFilter process...");
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        System.out.println("MyFilter destroy");
    }

}
2. FilterConfig
@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<MyFilter> myFilter() {
        FilterRegistrationBean<MyFilter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new MyFilter());
        return filterRegistrationBean;
    }

}
2.3 注册 listener
1. MyListener
public class MyListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("contextInitialized...web 应用启动");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("contextDestroyed...web 应用销毁");
    }

}
2. ListenerConfig
@Configuration
public class ListenerConfig {

    @Bean
    public ServletListenerRegistrationBean<MyListener> myListener() {
        ServletListenerRegistrationBean<MyListener> listenerRegistrationBean = new ServletListenerRegistrationBean<>();
        listenerRegistrationBean.setListener(new MyListener());
        return listenerRegistrationBean;
    }
    
}

3. 使用外置的 Servlet 容器

代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见 tutorial-spring-boot-core/tutorial-spring-boot-war 工程

3.1 嵌入式 Servlet 容器

嵌入式 Servlet 容器可以把应用打成可执行的 jar,但是默认不支持 JSP,优化定制也比较复杂

也可以使用外置的 Servlet 容器,将应用打成 war 包部署

3.2 使用 war 包部署
  1. 修改打包方式
<packaging>war</packaging>
  1. 修改 spring-boot-starter-tomcat 依赖为 provided
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <scope>provided</scope>
</dependency>
3. 编写 ServletInitializer 类
public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        //传入 SpringBoot 应用的主程序
        return application.sources(WarApplication.class);
    }

}
3.3 使用外置容器启动原理
3.3.1 servlet3.0 新特性

服务器启动(web 应用启动)会创建当前 web 应用里面每一个 jar 包里面 ServletContainerInitializer 实例,然后调用它们的 onStartup() 方法

ServletContainerInitializer 的实现放在 jar 包的 META-INF/services 文件夹下,有一个名为 javax.servlet.ServletContainerInitializer 的文件,内容就是 ServletContainerInitializer 的实现类的全类名

可以使用 @HandlesTypes 注解在应用启动的时候加载指定的类

3.3.2 spring-web-xxx.RELEASE.jar

spring-web-xxx.RELEASE.jar 里,META-INF/services/javax.servlet.ServletContainerInitializer 所对应的内容是 org.springframework.web.SpringServletContainerInitializer

3.3.3 SpringServletContainerInitializer

在这个类中,@HandlesTypes 对应的类型是 WebApplicationInitializer

我们写的 ServletInitializer 类是继承 SpringBootServletInitializer,它实现了 WebApplicationInitializer 接口

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

	@Override
	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {

		List<WebApplicationInitializer> initializers = new LinkedList<>();

		if (webAppInitializerClasses != null) {
			for (Class<?> waiClass : webAppInitializerClasses) {
				// Be defensive: Some servlet containers provide us with invalid classes,
				// no matter what @HandlesTypes says...
				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
					try {
					
					    // 实例化 WebApplicationInitializer,然后放到 initializers 中
						initializers.add((WebApplicationInitializer)
								ReflectionUtils.accessibleConstructor(waiClass).newInstance());
					}
					catch (Throwable ex) {
						throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
					}
				}
			}
		}

		if (initializers.isEmpty()) {
			servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
			return;
		}

		servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
		AnnotationAwareOrderComparator.sort(initializers);
		for (WebApplicationInitializer initializer : initializers) {
		
		    // 循环执行 onStartup 方法启动
			initializer.onStartup(servletContext);
		}
	}
    
}
3.3.4 SpringBootServletInitializer
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
	// Logger initialization is deferred in case an ordered
	// LogServletContextInitializer is being used
	this.logger = LogFactory.getLog(getClass());
	
	// 调用下面的方法创建根容器
	WebApplicationContext rootAppContext = createRootApplicationContext(
			servletContext);
	
	...
	
}
protected WebApplicationContext createRootApplicationContext(
		ServletContext servletContext) {
		
	// 创建 SpringApplicationBuilder	
	SpringApplicationBuilder builder = createSpringApplicationBuilder();
	builder.main(getClass());
	ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
	if (parent != null) {
		this.logger.info("Root context already created (using as parent).");
		servletContext.setAttribute(
				WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
		builder.initializers(new ParentContextApplicationContextInitializer(parent));
	}
	builder.initializers(
			new ServletContextApplicationContextInitializer(servletContext));
	builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);

    // 调用 configure 方法,子类重写了这个方法,将 SpringBoot 的主程序类传入了进来
	builder = configure(builder);
	builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
	SpringApplication application = builder.build();
	if (application.getAllSources().isEmpty() && AnnotationUtils
			.findAnnotation(getClass(), Configuration.class) != null) {
		application.addPrimarySources(Collections.singleton(getClass()));
	}
	Assert.state(!application.getAllSources().isEmpty(),
			"No SpringApplication sources have been defined. Either override the "
					+ "configure method or add an @Configuration annotation");
	// Ensure error pages are registered
	if (this.registerErrorPageFilter) {
		application.addPrimarySources(
				Collections.singleton(ErrorPageFilterConfiguration.class));
	}
	
	// 启动 Spring 应用,和之前用 main 方法启动的流程是一样的
	return run(application);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值