springboot通过ServletWebServerApplicationContext的onRefresh()方法,会创建web服务
- protected void onRefresh() {
- super.onRefresh();
- try {
- // 创建web服务
- createWebServer();
- }
- catch (Throwable ex) {
- throw new ApplicationContextException("Unable to start web server", ex);
- }
- }
进入reateWebServer()方法,通过getWebServerFactory()方法获取对应的web服务工厂
- private void createWebServer() {
- WebServer webServer = this.webServer;
- ServletContext servletContext = getServletContext();
- if (webServer == null && servletContext == null) {
- StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
- // 核心拿到web服务工厂
- ServletWebServerFactory factory = getWebServerFactory();
- createWebServer.tag("factory", factory.getClass().toString());
- this.webServer = factory.getWebServer(getSelfInitializer());
- createWebServer.end();
- getBeanFactory().registerSingleton("webServerGracefulShutdown",
- new WebServerGracefulShutdownLifecycle(this.webServer));
- getBeanFactory().registerSingleton("webServerStartStop",
- new WebServerStartStopLifecycle(this, this.webServer));
- }
- else if (servletContext != null) {
- try {
- getSelfInitializer().onStartup(servletContext);
- }
- catch (ServletException ex) {
- throw new ApplicationContextException("Cannot initialize servlet context", ex);
- }
- }
- initPropertySources();
- }
继续进入getWebServerFactory()方法,发现spring会先从SpringBean容器中的获取所有类型是ServletWebServerFactory.class的bean名称,如果beanNames.length == 0或者>1则会报错,也就是说spring并不会默认去加载tomcat。而是在之前就已经将TomcatServletWebServerFactory放入了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);
- }
进入ServletWebServerFactory
- @FunctionalInterface
- public interface ServletWebServerFactory {
-
- /**
- * Gets a new fully configured but paused {@link WebServer} instance. Clients should
- * not be able to connect to the returned server until {@link WebServer#start()} is
- * called (which happens when the {@code ApplicationContext} has been fully
- * refreshed).
- * @param initializers {@link ServletContextInitializer}s that should be applied as
- * the server starts
- * @return a fully configured and started {@link WebServer}
- * @see WebServer#stop()
- */
- WebServer getWebServer(ServletContextInitializer... initializers);
-
- }
发现ServletWebServerFactory是一个函数式接口,三个实现分别对应了tomcat,jetty和undertow三个服务器。
进入TomcatServletWebServerFactory类,可以看到有这个类对应的Bean,
跳转过去,可以发现在ServletWebServerFactoryConfiguration这个配置类中,已经将tomcat,jetty和undertow三个服务器对应的配置都写入了进去,具体是否要解析这个Bean是通过项目中是否能正常加载@ConditionalOnClass这个注解里的类来决定的。如果无法加载@ConditionalOnClass注解中的类那么spring就不会去解析这个Bean。
- @Configuration(proxyBeanMethods = false)
- class ServletWebServerFactoryConfiguration {
-
- @Configuration(proxyBeanMethods = false)
- @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
- @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
- static class EmbeddedTomcat {
-
- @Bean
- TomcatServletWebServerFactory tomcatServletWebServerFactory(
- ObjectProvider
connectorCustomizers, - ObjectProvider
contextCustomizers, - ObjectProvider
> protocolHandlerCustomizers) { - TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
- factory.getTomcatConnectorCustomizers()
- .addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
- factory.getTomcatContextCustomizers()
- .addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
- factory.getTomcatProtocolHandlerCustomizers()
- .addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
- return factory;
- }
-
- }
-
- /**
- * Nested configuration if Jetty is being used.
- */
- @Configuration(proxyBeanMethods = false)
- @ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })
- @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
- static class EmbeddedJetty {
-
- @Bean
- JettyServletWebServerFactory JettyServletWebServerFactory(
- ObjectProvider
serverCustomizers) { - JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
- factory.getServerCustomizers().addAll(serverCustomizers.orderedStream().collect(Collectors.toList()));
- return factory;
- }
-
- }
-
- /**
- * Nested configuration if Undertow is being used.
- */
- @Configuration(proxyBeanMethods = false)
- @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
- @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
- static class EmbeddedUndertow {
-
- @Bean
- UndertowServletWebServerFactory undertowServletWebServerFactory(
- ObjectProvider
deploymentInfoCustomizers, - ObjectProvider
builderCustomizers) { - UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
- factory.getDeploymentInfoCustomizers()
- .addAll(deploymentInfoCustomizers.orderedStream().collect(Collectors.toList()));
- factory.getBuilderCustomizers().addAll(builderCustomizers.orderedStream().collect(Collectors.toList()));
- return factory;
- }
-
- @Bean
- UndertowServletWebServerFactoryCustomizer undertowServletWebServerFactoryCustomizer(
- ServerProperties serverProperties) {
- return new UndertowServletWebServerFactoryCustomizer(serverProperties);
- }
-
- }
-
- }
介绍@ConditionalOnClass注解之前,首先想一个问题,spring是如何知道哪些类是被@Component注解还有@Bean注解注释的呢?也就是说spring在配置完包扫描路径后是如何将扫描范围的所有需要加载的Bean放入spring容器中的呢?
将扫描路径下的所有类都加载到JVM中然后再判断类是否有@Component或者@Bean注解,然后舍弃掉其他的普通类吗?
这样做的话就会导致加载了很多不需要的类,不仅违反了只加载需要的类到jvm中规则,而且也会影响性能。
那么spring是如何解决这个问题的呢?
答案是spring通过ASM(字节码框架),先加载类的字节码文件,根据java字节码规范来查看这些类是否有@Component或者@Bean注解,只加载有这些注解的类到jvm中。通过ASM框架,spring解决了会加载不必要的类这个问题。
具体的asm框架的使用可以参考asm的官方地址
回到@ConditionalOnClass注解上来在之前的spring加载服务器的讨论中可以看到,ServletWebServerFactoryConfiguration这个配置类中将tomcat,jetty和undertow三个服务器对应的配置都写入了进去,但是只引入了tomcat相关的依赖,为什么spring运行时没有抛出类不存在的问题呢?
这个问题也在ASM中解决
@ConditionalOnClass注解的底层也是通过ASM来解决的。spring在通过ASM读取字节码文件时,会先尝试加载@ConditionalOnClass注解中的类,如果加载失败那么就会修改字节码文件,移除@ConditionalOnClass注解注释的方法,从而保证程序的继续向下运行。
从spring的run方法上开始,run方法会调用refresh()方法,refresh()内部调用onRefresh()方法,然后找到ServletWebServerApplicationContext实现类的onRefresh()方法,进入reateWebServer()方法,在进入factory.getWebServer(getSelfInitializer()),找到TomcatServletWebServerFactory实现类(因为只有tomcat的相关依赖,spring容器只有TomcatServletWebServerFactory这一个Bean),可以看到这里就是对tomcat服务器做的初始化操作。
- public WebServer getWebServer(ServletContextInitializer... initializers) {
- if (this.disableMBeanRegistry) {
- Registry.disableRegistry();
- }
- Tomcat tomcat = new Tomcat();
- File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
- tomcat.setBaseDir(baseDir.getAbsolutePath());
- Connector connector = new Connector(this.protocol);
- connector.setThrowOnFailure(true);
- 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);
- return getTomcatWebServer(tomcat);
- }
进入customizeConnector(connector),spring就是在这里设置的tomcat 服务的端口信息
- protected void customizeConnector(Connector connector) {
- // 设置tomcat服务的端口号
- int port = Math.max(getPort(), 0);
- connector.setPort(port);
- if (StringUtils.hasText(getServerHeader())) {
- connector.setProperty("server", getServerHeader());
- }
- if (connector.getProtocolHandler() instanceof AbstractProtocol) {
- customizeProtocol((AbstractProtocol>) connector.getProtocolHandler());
- }
- invokeProtocolHandlerCustomizers(connector.getProtocolHandler());
- if (getUriEncoding() != null) {
- connector.setURIEncoding(getUriEncoding().name());
- }
- // Don't bind to the socket prematurely if ApplicationContext is slow to start
- connector.setProperty("bindOnInit", "false");
- if (getHttp2() != null && getHttp2().isEnabled()) {
- connector.addUpgradeProtocol(new Http2Protocol());
- }
- if (getSsl() != null && getSsl().isEnabled()) {
- customizeSsl(connector);
- }
- TomcatConnectorCustomizer compression = new CompressionConnectorCustomizer(getCompression());
- compression.customize(connector);
- for (TomcatConnectorCustomizer customizer : this.tomcatConnectorCustomizers) {
- customizer.customize(connector);
- }
- }
进入getPort()方法,就能发现spring在关于WebServer的port默认值就是8080(tomcat,jetty和undertow公共父类,所以不管是tomcat还是jetty和undertow他们的默认端口号都是8080)
有时候,我们希望Spring
容器在创建bean
的过程中,能够使用我们自己定义的逻辑,对创建的bean
做一些处理,或者执行一些业务。而实现方式有多种,比如自定义bean
的初始化话方法等,而BeanPostProcessor
接口也是用来实现类似的功能的。
如果我们希望容器中创建的每一个bean
,在创建的过程中可以执行一些自定义的逻辑,那么我们就可以编写一个类,并让他实现BeanPostProcessor
接口,然后将这个类注册到一个容器中。容器在创建bean
的过程中,会优先创建实现了BeanPostProcessor
接口的bean
,然后,在创建其他bean
的时候,会将创建的每一个bean
作为参数,调用BeanPostProcessor
的方法。而BeanPostProcessor
接口的方法,即是由我们自己实现的。下面就来具体介绍一下BeanPostProcessor
的使用。
想要成功获取修改后的post的值,就要在getPort()这个方法被执行之前就要改变this.port对应的值,首先我们知道目前 AbstractConfigurableWebServerFactory这个类的真正实现是TomcatServletWebServerFactory这个类(因为spring容器中只有这一个匹配的Bean),又因为TomcatServletWebServerFactory这个类是一个Bean,那么根据spring设置的生命周期,就可以在Bean实例化后,spring会调用BeanPostProcessor这个spring提供的后置处理器来重新设置port变量
spring boot自己实现创建的WebServerFactoryCustomizerBeanPostProcessor类就实现了BeanPostProcessor接口,他的postProcessBeforeInitialization()方法调用了postProcessBeforeInitialization(),
- @Override
- public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
- if (bean instanceof WebServerFactory) {
- postProcessBeforeInitialization((WebServerFactory) bean);
- }
- return bean;
- }
在postProcessBeforeInitialization内部就设置调用了WebServerFactory接口的customize()方法,
- private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
- LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory)
- .withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
- .invoke((customizer) -> customizer.customize(webServerFactory));
- }
最终调用的spring容器中的ServletWebServerFactoryCustomizer类的customize() 方法,拿到this.serverProperties中的属性来重新填充包括port属性值在内的配置值。
- @Override
- public void customize(ConfigurableServletWebServerFactory factory) {
- PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
- map.from(this.serverProperties::getPort).to(factory::setPort);
- map.from(this.serverProperties::getAddress).to(factory::setAddress);
- map.from(this.serverProperties.getServlet()::getContextPath).to(factory::setContextPath);
- map.from(this.serverProperties.getServlet()::getApplicationDisplayName).to(factory::setDisplayName);
- map.from(this.serverProperties.getServlet()::isRegisterDefaultServlet).to(factory::setRegisterDefaultServlet);
- map.from(this.serverProperties.getServlet()::getSession).to(factory::setSession);
- map.from(this.serverProperties::getSsl).to(factory::setSsl);
- map.from(this.serverProperties.getServlet()::getJsp).to(factory::setJsp);
- map.from(this.serverProperties::getCompression).to(factory::setCompression);
- map.from(this.serverProperties::getHttp2).to(factory::setHttp2);
- map.from(this.serverProperties::getServerHeader).to(factory::setServerHeader);
- map.from(this.serverProperties.getServlet()::getContextParameters).to(factory::setInitParameters);
- map.from(this.serverProperties.getShutdown()).to(factory::setShutdown);
- for (WebListenerRegistrar registrar : this.webListenerRegistrars) {
- registrar.register(factory);
- }
- }
而ServerProperties类读取的就是配置文件中的以 server开头的属性值,这样spring boot就完成了对通过配置文件来改变默认属性值。如果不写相应的配置文件也会有对应的默认配置(即零配置)
- /**
- * {@link ConfigurationProperties @ConfigurationProperties} for a web server (e.g. port
- * and path settings).
- *
- * @author Dave Syer
- * @author Stephane Nicoll
- * @author Andy Wilkinson
- * @author Ivan Sopov
- * @author Marcos Barbero
- * @author Eddú Meléndez
- * @author Quinten De Swaef
- * @author Venil Noronha
- * @author Aurélien Leboulanger
- * @author Brian Clozel
- * @author Olivier Lamy
- * @author Chentao Qu
- * @author Artsiom Yudovin
- * @author Andrew McGhie
- * @author Rafiullah Hamedy
- * @author Dirk Deyne
- * @author HaiTao Zhang
- * @author Victor Mandujano
- * @author Chris Bono
- * @author Parviz Rozikov
- * @since 1.0.0
- */
- @ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
- public class ServerProperties {
-
- /**
- * Server HTTP port.
- */
- private Integer port;
-
- /**
- * Network address to which the server should bind.
- */
- private InetAddress address;