spring boot零配置

spring boot是如何选择tomcat还是Jett作为底层服务器的呢?

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

明明没有写关于tomcat的配置 tomcat是如何被加载的呢?

进入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<TomcatConnectorCustomizer> connectorCustomizers,
				ObjectProvider<TomcatContextCustomizer> contextCustomizers,
				ObjectProvider<TomcatProtocolHandlerCustomizer<?>> 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<JettyServerCustomizer> 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<UndertowDeploymentInfoCustomizer> deploymentInfoCustomizers,
				ObjectProvider<UndertowBuilderCustomizer> 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 boot是如何知道哪些类是被@Component注解还有@Bean注解注释的呢?

介绍@ConditionalOnClass注解之前,首先想一个问题,spring是如何知道哪些类是被@Component注解还有@Bean注解注释的呢?也就是说spring在配置完包扫描路径后是如何将扫描范围的所有需要加载的Bean放入spring容器中的呢?

将扫描路径下的所有类都加载到JVM中然后再判断类是否有@Component或者@Bean注解,然后舍弃掉其他的普通类吗?

这样做的话就会导致加载了很多不需要的类,不仅违反了只加载需要的类到jvm中规则,而且也会影响性能。

那么spring是如何解决这个问题的呢?

ASM(字节码框架)

答案是spring通过ASM(字节码框架),先加载类的字节码文件,根据java字节码规范来查看这些类是否有@Component或者@Bean注解,只加载有这些注解的类到jvm中。通过ASM框架,spring解决了会加载不必要的类这个问题。

具体的asm框架的使用可以参考asm的官方地址

ASM

明明没有导入相关的依赖为什么被@ConditionalOnClass注解注释的类在spring运行时没有报错呢?

回到@ConditionalOnClass注解上来在之前的spring加载服务器的讨论中可以看到,ServletWebServerFactoryConfiguration这个配置类中将tomcat,jetty和undertow三个服务器对应的配置都写入了进去,但是只引入了tomcat相关的依赖,为什么spring运行时没有抛出类不存在的问题呢?

这个问题也在ASM中解决

@ConditionalOnClass注解的底层也是通过ASM来解决的。spring在通过ASM读取字节码文件时,会先尝试加载@ConditionalOnClass注解中的类,如果加载失败那么就会修改字节码文件,移除@ConditionalOnClass注解注释的方法,从而保证程序的继续向下运行。

为什么springboot启动的的服务器默认端口是8080

从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 boot是如何通过配置文件来修改port的呢

 BeanPostProcessor

什么是BeanPostProcessor

有时候,我们希望Spring容器在创建bean的过程中,能够使用我们自己定义的逻辑,对创建的bean做一些处理,或者执行一些业务。而实现方式有多种,比如自定义bean的初始化话方法等,而BeanPostProcessor接口也是用来实现类似的功能的。

  如果我们希望容器中创建的每一个bean,在创建的过程中可以执行一些自定义的逻辑,那么我们就可以编写一个类,并让他实现BeanPostProcessor接口,然后将这个类注册到一个容器中。容器在创建bean的过程中,会优先创建实现了BeanPostProcessor接口的bean,然后,在创建其他bean的时候,会将创建的每一个bean作为参数,调用BeanPostProcessor的方法。而BeanPostProcessor接口的方法,即是由我们自己实现的。下面就来具体介绍一下BeanPostProcessor的使用。

springboot中配置文件对于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;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值