• spring boot零配置


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

    springboot通过ServletWebServerApplicationContext的onRefresh()方法,会创建web服务

    1. protected void onRefresh() {
    2. super.onRefresh();
    3. try {
    4. // 创建web服务
    5. createWebServer();
    6. }
    7. catch (Throwable ex) {
    8. throw new ApplicationContextException("Unable to start web server", ex);
    9. }
    10. }

    进入reateWebServer()方法,通过getWebServerFactory()方法获取对应的web服务工厂

    1. private void createWebServer() {
    2. WebServer webServer = this.webServer;
    3. ServletContext servletContext = getServletContext();
    4. if (webServer == null && servletContext == null) {
    5. StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
    6. // 核心拿到web服务工厂
    7. ServletWebServerFactory factory = getWebServerFactory();
    8. createWebServer.tag("factory", factory.getClass().toString());
    9. this.webServer = factory.getWebServer(getSelfInitializer());
    10. createWebServer.end();
    11. getBeanFactory().registerSingleton("webServerGracefulShutdown",
    12. new WebServerGracefulShutdownLifecycle(this.webServer));
    13. getBeanFactory().registerSingleton("webServerStartStop",
    14. new WebServerStartStopLifecycle(this, this.webServer));
    15. }
    16. else if (servletContext != null) {
    17. try {
    18. getSelfInitializer().onStartup(servletContext);
    19. }
    20. catch (ServletException ex) {
    21. throw new ApplicationContextException("Cannot initialize servlet context", ex);
    22. }
    23. }
    24. initPropertySources();
    25. }

    继续进入getWebServerFactory()方法,发现spring会先从SpringBean容器中的获取所有类型是ServletWebServerFactory.class的bean名称,如果beanNames.length == 0或者>1则会报错,也就是说spring并不会默认去加载tomcat。而是在之前就已经将TomcatServletWebServerFactory放入了spring容器中。

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

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

    进入ServletWebServerFactory

    1. @FunctionalInterface
    2. public interface ServletWebServerFactory {
    3. /**
    4. * Gets a new fully configured but paused {@link WebServer} instance. Clients should
    5. * not be able to connect to the returned server until {@link WebServer#start()} is
    6. * called (which happens when the {@code ApplicationContext} has been fully
    7. * refreshed).
    8. * @param initializers {@link ServletContextInitializer}s that should be applied as
    9. * the server starts
    10. * @return a fully configured and started {@link WebServer}
    11. * @see WebServer#stop()
    12. */
    13. WebServer getWebServer(ServletContextInitializer... initializers);
    14. }

    发现ServletWebServerFactory是一个函数式接口,三个实现分别对应了tomcat,jetty和undertow三个服务器。

    进入TomcatServletWebServerFactory类,可以看到有这个类对应的Bean,

    跳转过去,可以发现在ServletWebServerFactoryConfiguration这个配置类中,已经将tomcat,jetty和undertow三个服务器对应的配置都写入了进去,具体是否要解析这个Bean是通过项目中是否能正常加载@ConditionalOnClass这个注解里的类来决定的。如果无法加载@ConditionalOnClass注解中的类那么spring就不会去解析这个Bean。

    1. @Configuration(proxyBeanMethods = false)
    2. class ServletWebServerFactoryConfiguration {
    3. @Configuration(proxyBeanMethods = false)
    4. @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
    5. @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
    6. static class EmbeddedTomcat {
    7. @Bean
    8. TomcatServletWebServerFactory tomcatServletWebServerFactory(
    9. ObjectProvider connectorCustomizers,
    10. ObjectProvider contextCustomizers,
    11. ObjectProvider> protocolHandlerCustomizers) {
    12. TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
    13. factory.getTomcatConnectorCustomizers()
    14. .addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
    15. factory.getTomcatContextCustomizers()
    16. .addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
    17. factory.getTomcatProtocolHandlerCustomizers()
    18. .addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
    19. return factory;
    20. }
    21. }
    22. /**
    23. * Nested configuration if Jetty is being used.
    24. */
    25. @Configuration(proxyBeanMethods = false)
    26. @ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })
    27. @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
    28. static class EmbeddedJetty {
    29. @Bean
    30. JettyServletWebServerFactory JettyServletWebServerFactory(
    31. ObjectProvider serverCustomizers) {
    32. JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
    33. factory.getServerCustomizers().addAll(serverCustomizers.orderedStream().collect(Collectors.toList()));
    34. return factory;
    35. }
    36. }
    37. /**
    38. * Nested configuration if Undertow is being used.
    39. */
    40. @Configuration(proxyBeanMethods = false)
    41. @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
    42. @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
    43. static class EmbeddedUndertow {
    44. @Bean
    45. UndertowServletWebServerFactory undertowServletWebServerFactory(
    46. ObjectProvider deploymentInfoCustomizers,
    47. ObjectProvider builderCustomizers) {
    48. UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
    49. factory.getDeploymentInfoCustomizers()
    50. .addAll(deploymentInfoCustomizers.orderedStream().collect(Collectors.toList()));
    51. factory.getBuilderCustomizers().addAll(builderCustomizers.orderedStream().collect(Collectors.toList()));
    52. return factory;
    53. }
    54. @Bean
    55. UndertowServletWebServerFactoryCustomizer undertowServletWebServerFactoryCustomizer(
    56. ServerProperties serverProperties) {
    57. return new UndertowServletWebServerFactoryCustomizer(serverProperties);
    58. }
    59. }
    60. }

    @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服务器做的初始化操作。

    1. public WebServer getWebServer(ServletContextInitializer... initializers) {
    2. if (this.disableMBeanRegistry) {
    3. Registry.disableRegistry();
    4. }
    5. Tomcat tomcat = new Tomcat();
    6. File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
    7. tomcat.setBaseDir(baseDir.getAbsolutePath());
    8. Connector connector = new Connector(this.protocol);
    9. connector.setThrowOnFailure(true);
    10. tomcat.getService().addConnector(connector);
    11. customizeConnector(connector);
    12. tomcat.setConnector(connector);
    13. tomcat.getHost().setAutoDeploy(false);
    14. configureEngine(tomcat.getEngine());
    15. for (Connector additionalConnector : this.additionalTomcatConnectors) {
    16. tomcat.getService().addConnector(additionalConnector);
    17. }
    18. prepareContext(tomcat.getHost(), initializers);
    19. return getTomcatWebServer(tomcat);
    20. }

    进入customizeConnector(connector),spring就是在这里设置的tomcat 服务的端口信息

    1. protected void customizeConnector(Connector connector) {
    2. // 设置tomcat服务的端口号
    3. int port = Math.max(getPort(), 0);
    4. connector.setPort(port);
    5. if (StringUtils.hasText(getServerHeader())) {
    6. connector.setProperty("server", getServerHeader());
    7. }
    8. if (connector.getProtocolHandler() instanceof AbstractProtocol) {
    9. customizeProtocol((AbstractProtocol) connector.getProtocolHandler());
    10. }
    11. invokeProtocolHandlerCustomizers(connector.getProtocolHandler());
    12. if (getUriEncoding() != null) {
    13. connector.setURIEncoding(getUriEncoding().name());
    14. }
    15. // Don't bind to the socket prematurely if ApplicationContext is slow to start
    16. connector.setProperty("bindOnInit", "false");
    17. if (getHttp2() != null && getHttp2().isEnabled()) {
    18. connector.addUpgradeProtocol(new Http2Protocol());
    19. }
    20. if (getSsl() != null && getSsl().isEnabled()) {
    21. customizeSsl(connector);
    22. }
    23. TomcatConnectorCustomizer compression = new CompressionConnectorCustomizer(getCompression());
    24. compression.customize(connector);
    25. for (TomcatConnectorCustomizer customizer : this.tomcatConnectorCustomizers) {
    26. customizer.customize(connector);
    27. }
    28. }

    进入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(),

    1. @Override
    2. public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    3. if (bean instanceof WebServerFactory) {
    4. postProcessBeforeInitialization((WebServerFactory) bean);
    5. }
    6. return bean;
    7. }

    在postProcessBeforeInitialization内部就设置调用了WebServerFactory接口的customize()方法,

    1. private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
    2. LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory)
    3. .withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
    4. .invoke((customizer) -> customizer.customize(webServerFactory));
    5. }

    最终调用的spring容器中的ServletWebServerFactoryCustomizer类的customize() 方法,拿到this.serverProperties中的属性来重新填充包括port属性值在内的配置值。

    1. @Override
    2. public void customize(ConfigurableServletWebServerFactory factory) {
    3. PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
    4. map.from(this.serverProperties::getPort).to(factory::setPort);
    5. map.from(this.serverProperties::getAddress).to(factory::setAddress);
    6. map.from(this.serverProperties.getServlet()::getContextPath).to(factory::setContextPath);
    7. map.from(this.serverProperties.getServlet()::getApplicationDisplayName).to(factory::setDisplayName);
    8. map.from(this.serverProperties.getServlet()::isRegisterDefaultServlet).to(factory::setRegisterDefaultServlet);
    9. map.from(this.serverProperties.getServlet()::getSession).to(factory::setSession);
    10. map.from(this.serverProperties::getSsl).to(factory::setSsl);
    11. map.from(this.serverProperties.getServlet()::getJsp).to(factory::setJsp);
    12. map.from(this.serverProperties::getCompression).to(factory::setCompression);
    13. map.from(this.serverProperties::getHttp2).to(factory::setHttp2);
    14. map.from(this.serverProperties::getServerHeader).to(factory::setServerHeader);
    15. map.from(this.serverProperties.getServlet()::getContextParameters).to(factory::setInitParameters);
    16. map.from(this.serverProperties.getShutdown()).to(factory::setShutdown);
    17. for (WebListenerRegistrar registrar : this.webListenerRegistrars) {
    18. registrar.register(factory);
    19. }
    20. }

    而ServerProperties类读取的就是配置文件中的以 server开头的属性值,这样spring boot就完成了对通过配置文件来改变默认属性值。如果不写相应的配置文件也会有对应的默认配置(即零配置)

    1. /**
    2. * {@link ConfigurationProperties @ConfigurationProperties} for a web server (e.g. port
    3. * and path settings).
    4. *
    5. * @author Dave Syer
    6. * @author Stephane Nicoll
    7. * @author Andy Wilkinson
    8. * @author Ivan Sopov
    9. * @author Marcos Barbero
    10. * @author Eddú Meléndez
    11. * @author Quinten De Swaef
    12. * @author Venil Noronha
    13. * @author Aurélien Leboulanger
    14. * @author Brian Clozel
    15. * @author Olivier Lamy
    16. * @author Chentao Qu
    17. * @author Artsiom Yudovin
    18. * @author Andrew McGhie
    19. * @author Rafiullah Hamedy
    20. * @author Dirk Deyne
    21. * @author HaiTao Zhang
    22. * @author Victor Mandujano
    23. * @author Chris Bono
    24. * @author Parviz Rozikov
    25. * @since 1.0.0
    26. */
    27. @ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
    28. public class ServerProperties {
    29. /**
    30. * Server HTTP port.
    31. */
    32. private Integer port;
    33. /**
    34. * Network address to which the server should bind.
    35. */
    36. private InetAddress address;

  • 相关阅读:
    c++(26) 输入输出流、文件操作
    HJ73 计算日期到天数转换
    SpringCloud Alibaba Nacos服务注册和配置中心
    WebSocket快速入门及基本使用
    【SpringBean】bean的作用域和bean的生命周期
    geemap学习笔记011:可视化遥感影像随时间的变化
    九章云极DataCanvas公司入选可信开源大模型产业推进方阵首批成员
    运维07:堡垒机
    334 - 线程通信问题
    我的第一个项目(十二) :分数和生命值的更新(后端增删查改的"改")
  • 原文地址:https://blog.csdn.net/qq_45743985/article/details/134506998