当前位置: 首页 > news >正文

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 hierarchyString[] 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 {@BeanTomcatServletWebServerFactory 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 {@BeanJettyServletWebServerFactory 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 {@BeanUndertowServletWebServerFactory 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;}@BeanUndertowServletWebServerFactoryCustomizer 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 startconnector.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(),

@Overridepublic 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属性值在内的配置值。

	@Overridepublic 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;

相关文章:

  • 「Docker」如何在苹果电脑上构建简单的Go云原生程序「MacOS」
  • OpenGL 绘制圆形平面(Qt)
  • debian10 开启rdp安装firefox并解决firefox 中文乱码
  • element UI表格中设置文字提示(tooltip)或弹出框(popover)时候注意的地方
  • Spring Cloud五大组件
  • css中5种属性选择器
  • 【C++设计模式】单例模式singleton
  • 场景交互与场景漫游-交运算与对象选取(8-1)
  • 12个最佳WordPress投票插件
  • 【追求卓越08】算法--排序算法
  • vue history路径编码
  • 人工智能-循环神经网络的简洁实现
  • QSplitter分裂器
  • 如何应用ChatGPT撰写、修改论文及工作报告,提供写作能力及优化工作??
  • 3、基础入门——抓包封包协议APP小程序PC应用web应用
  • 【个人向】《HTTP图解》阅后小结
  • 30天自制操作系统-2
  • Codepen 每日精选(2018-3-25)
  • go语言学习初探(一)
  • Java IO学习笔记一
  • java 多线程基础, 我觉得还是有必要看看的
  • JavaScript标准库系列——Math对象和Date对象(二)
  • Linux快速配置 VIM 实现语法高亮 补全 缩进等功能
  • Magento 1.x 中文订单打印乱码
  • mysql外键的使用
  • node入门
  • ReactNativeweexDeviceOne对比
  • SQLServer之创建数据库快照
  • vue 个人积累(使用工具,组件)
  • 看完九篇字体系列的文章,你还觉得我是在说字体?
  • 聊聊directory traversal attack
  • 适配mpvue平台的的微信小程序日历组件mpvue-calendar
  • 无服务器化是企业 IT 架构的未来吗?
  • 小程序测试方案初探
  • 一些基于React、Vue、Node.js、MongoDB技术栈的实践项目
  • nb
  • LevelDB 入门 —— 全面了解 LevelDB 的功能特性
  • ​比特币大跌的 2 个原因
  • #单片机(TB6600驱动42步进电机)
  • (3)llvm ir转换过程
  • (大众金融)SQL server面试题(1)-总销售量最少的3个型号的车及其总销售量
  • (算法)Travel Information Center
  • (转)程序员疫苗:代码注入
  • .a文件和.so文件
  • .NET / MSBuild 扩展编译时什么时候用 BeforeTargets / AfterTargets 什么时候用 DependsOnTargets?
  • .net 反编译_.net反编译的相关问题
  • .NET 中让 Task 支持带超时的异步等待
  • .NET(C#、VB)APP开发——Smobiler平台控件介绍:Bluetooth组件
  • .NET/C# 将一个命令行参数字符串转换为命令行参数数组 args
  • .w文件怎么转成html文件,使用pandoc进行Word与Markdown文件转化
  • /ThinkPHP/Library/Think/Storage/Driver/File.class.php  LINE: 48
  • [ 代码审计篇 ] 代码审计案例详解(一) SQL注入代码审计案例
  • [100天算法】-二叉树剪枝(day 48)
  • [20180129]bash显示path环境变量.txt
  • [2023-年度总结]凡是过往,皆为序章