当前位置 : 首页 » 文章分类 :  开发  »  Spring-Boot

Spring-Boot

Spring Boot 学习笔记

Spring Boot
https://spring.io/projects/spring-boot

Spring Boot 纯洁的微笑(Java/JVM/Spring Boot/Spring Cloud)
http://www.ityouknow.com/


Spring Cloud 和 Spring Boot 版本不兼容导致无法启动

Spring Boot 无法启动,或者说启动后立即退出,不开 debug 看不到错误,日志级别开到 debug 后报错:
2019-11-15 18:10:38,222 [DEBUG] [main] org.springframework.boot.context.logging.ClasspathLoggingApplicationListener.onApplicationEvent [56]: Application failed to start with classpath:

在 ClasspathLoggingApplicationListener.onApplicationEvent 方法内打断点,发现错误是

java.lang.NoSuchMethodError: org.springframework.boot.builder.SpringApplicationBuilder.<init>([Ljava/lang/Object;)V
    at org.springframework.cloud.bootstrap.BootstrapApplicationListener.bootstrapServiceContext(BootstrapApplicationListener.java:161)
    at org.springframework.cloud.bootstrap.BootstrapApplicationListener.onApplicationEvent(BootstrapApplicationListener.java:102)
    at org.springframework.cloud.bootstrap.BootstrapApplicationListener.onApplicationEvent(BootstrapApplicationListener.java:68)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:127)
    at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:74)
    at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:54)
    at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:358)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:317)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1255)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1243)
    at com.masikkk.blog.BlogApplication.main(BlogApplication.java:22)

原因
Spring Cloud 版本 和 Spring Boot 版本不兼容
出问题时,我的Spring Boot 版本: 2.2.1.RELEASE,未指定 Spring Cloud版本

解决
Spring Boot 2.2.1.RELEASE 应搭配 Spring Cloud Hoxton.RC2 使用
见 Spring Cloud Hoxton.RC2 的 release note https://spring.io/blog/2019/11/12/spring-cloud-hoxton-rc2-released Spring Cloud Hoxton.RC2 is built upon Spring Bot 2.2.1.RELEASE
或使用 http://start.spring.io/info 查看兼容性

java.lang.NoSuchMethodError: org.springframework.boot.builder.SpringApplicationBuilder
https://stackoverflow.com/questions/55498624/java-lang-nosuchmethoderror-org-springframework-boot-builder-springapplicationb

https://stackoverflow.com/questions/44166221/spring-boot-2-0-0-m1-nosuchmethoderror-org-springframework-boot-builder-springa


Spring Boot 自动配置

@EnableAutoConfiguration的作用

@SpringBootApplication 注解中包含了 @EnableAutoConfiguration , 所以无需额外再开启自动配置
Spring boot 的自动配置会基于你的classpath中的jar包,试图猜测和配置您可能需要的bean。
@EnableAutoConfiguration 可以帮助 Spring Boot 应用将所有符合条件的 @Configuration 配置类的 bean 都加载到 Spring IoC 容器中。

@EnableAutoConfiguration 自动配置原理

通过 @EnableAutoConfiguration 启用Spring应用程序上下文的自动配置
这个注解会导入一个名为 EnableAutoConfigurationImportSelector 的类, 而这个类会去读取配置文件 spring.factories 中 key 为 org.springframework.boot.autoconfigure.EnableAutoConfiguratio 的 value, 这些 value 就是需要自动配置的标注有 @Configuration 的类

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

spring.factories 文件

读取 spring.factories 文件的实现是通过 org.springframework.core.io.support.SpringFactoriesLoader 实现的。

SpringFactoriesLoader 的实现类似于 SPI(Service Provider Interface,在java.util.ServiceLoader的文档里有比较详细的介绍。java SPI提供一种服务发现机制,为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要[3])。

SpringFactoriesLoader 会加载 classpath 下所有 JAR 文件里面的 META-INF/spring.factories 文件, 并实例化其中的 自动配置类

其中加载spring.factories文件的代码在loadFactoryNames方法里:

    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

  public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
    // org.springframework.core.io.support.SpringFactoriesLoader
        String factoryClassName = factoryClass.getName();
        try {
            Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            List<String> result = new ArrayList<String>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
                String propertyValue = properties.getProperty(factoryClassName);
                for (String factoryName : StringUtils.commaDelimitedListToStringArray(propertyValue)) {
                    result.add(factoryName.trim());
                }
            }
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

第5章 Spring Boot自动配置原理
https://www.jianshu.com/p/346cac67bfcc

使用spring.factories指定自己的自动配置类

如果不在 spring.factories 中指定, 我们写的 @Configuration 配置类需要 @Import{MyAutoConfiguration.class}) 才能起作用
假如我们自己提供了一个 Jar 包模块,里面有个自动配置类,想要使用方不做任何代码侵入就让自动配置类生效,可以增加一个 src/main/resources/META-INF/spring.factories 文件

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.nio.uds.user.config.MyAutoConfiguration

在日常工作中,我们可能需要实现一些 Spring Boot Starter 给被人使用,这个时候我们就可以使用这个 Factories 机制,将自己的 Starter 注册到 org.springframework.boot.autoconfigure.EnableAutoConfiguration 命名空间下。这样用户只需要在服务中引入我们的 jar 包即可完成自动加载及配置。


Spring Boot加载应用上下文并启动过程

SpringApplication 部分源码如下:

package org.springframework.boot;

public class SpringApplication {
  // 构造方法
  public SpringApplication(Class<?>... primarySources) {
    this(null, primarySources);
  }

  public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    //通过Resource和ResourceLoader加载\META-INF\spring.factories文件
    //并通过反射,创建对应的实例
    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
  }

  public static ConfigurableApplicationContext run(Class<?> primarySource,
            String... args) {
        return run(new Class<?>[] { primarySource }, args);
    }

    public static ConfigurableApplicationContext run(Class<?>[] primarySources,
            String[] args) {
        return new SpringApplication(primarySources).run(args);
    }

  public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            configureIgnoreBeanInfo(environment);
            Banner printedBanner = printBanner(environment);
      // 创建并初始化ApplicationContext, 即 Spring 的 Bean 容器
            context = createApplicationContext();
            exceptionReporters = getSpringFactoriesInstances(
                    SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);

      // 调用 AbstractApplicationContext.refresh(), 扫描并初始化所有Bean
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        try {
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }
}

其中的 run() 方法中,调用 createApplicationContext() 创建并初始化 ApplicationContext, 可以看到其实就是通过反射实例化了一个 AnnotationConfigApplicationContext 并将其初始化:

public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context.annotation.AnnotationConfigApplicationContext";
public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";

protected ConfigurableApplicationContext createApplicationContext() {
  Class<?> contextClass = this.applicationContextClass;
  if (contextClass == null) {
    try {
      switch (this.webApplicationType) {
      case SERVLET:
        contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
        break;
      case REACTIVE:
        contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
        break;
      default:
        contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
      }
    }
    catch (ClassNotFoundException ex) {
      throw new IllegalStateException(
          "Unable create a default ApplicationContext, "
              + "please specify an ApplicationContextClass",
          ex);
    }
  }
  return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

然后 refreshContext 中最终调用了 AbstractApplicationContextrefresh 方法,其中会扫描所有的bean并初始化

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // Prepare this context for refreshing.
        prepareRefresh();

        //旧版本的Spring在这里初始化BeanFactory,读取xml配置,并解析成一个个Bean,
        // Tell the subclass to refresh the internal bean factory.
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // Prepare the bean factory for use in this context.
        prepareBeanFactory(beanFactory);

        try {
            // Allows post-processing of the bean factory in context subclasses.
            postProcessBeanFactory(beanFactory);

            //调用工厂后处理器
            //扫描Bean文件,并解析成一个个Bean
            // Invoke factory processors registered as beans in the context.
            invokeBeanFactoryPostProcessors(beanFactory);

            //注册Bean后处理器
            // Register bean processors that intercept bean creation.
            registerBeanPostProcessors(beanFactory);

            //初始化消息源
            // Initialize message source for this context.
            initMessageSource();

            //初始化应用上下文时间广播器
            // Initialize event multicaster for this context.
            initApplicationEventMulticaster();

            //初始化其他特殊的Bean,由具体子类实现
            // Initialize other special beans in specific context subclasses.
            onRefresh();

            //注册事件监听器
            // Check for listener beans and register them.
            registerListeners();

            //初始化所有单例的Bean,使用懒加载模式的Bean除外
            // Instantiate all remaining (non-lazy-init) singletons.
            finishBeanFactoryInitialization(beanFactory);

            //完成刷新并发布容器刷新事件
            // Last step: publish corresponding event.
            finishRefresh();
        }
        ...
    }
}

其中的 finishBeanFactoryInitialization() 负责 beanFactory 的初始化,包括所有非lazy-init的单例bean的初始化:

/**
 * Finish the initialization of this context's bean factory,
 * initializing all remaining singleton beans.
 */
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
  // Initialize conversion service for this context.
  if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
      beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
    beanFactory.setConversionService(
        beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
  }

  // Register a default embedded value resolver if no bean post-processor
  // (such as a PropertyPlaceholderConfigurer bean) registered any before:
  // at this point, primarily for resolution in annotation attribute values.
  if (!beanFactory.hasEmbeddedValueResolver()) {
    beanFactory.addEmbeddedValueResolver(new StringValueResolver() {
      @Override
      public String resolveStringValue(String strVal) {
        return getEnvironment().resolvePlaceholders(strVal);
      }
    });
  }

  // Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
  String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
  for (String weaverAwareName : weaverAwareNames) {
    getBean(weaverAwareName);
  }

  // Stop using the temporary ClassLoader for type matching.
  beanFactory.setTempClassLoader(null);

  // Allow for caching all bean definition metadata, not expecting further changes.
  beanFactory.freezeConfiguration();

  // 初始化所有非 lazy-init 的单例bean
  // Instantiate all remaining (non-lazy-init) singletons.
  beanFactory.preInstantiateSingletons();
}

最后一步,org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons 进行 bean 的初始化, 该方法首先循环所有的BeanNames,并且调用getBean方法,该方法实际上就是创建bean并递归构建依赖关系。
getBean会调用org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean 进行最终的 bean 初始化,这也就到了原生 Spring 的初始化过程,里面有一些处理循环依赖的逻辑。

@Override
public void preInstantiateSingletons() throws BeansException {
  if (logger.isDebugEnabled()) {
    logger.debug("Pre-instantiating singletons in " + this);
  }

  // Iterate over a copy to allow for init methods which in turn register new bean definitions.
  // While this may not be part of the regular factory bootstrap, it does otherwise work fine.
  List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);

  // Trigger initialization of all non-lazy singleton beans...
  for (String beanName : beanNames) {
    RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
    if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
      if (isFactoryBean(beanName)) {
        final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
        boolean isEagerInit;
        if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
          isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
            @Override
            public Boolean run() {
              return ((SmartFactoryBean<?>) factory).isEagerInit();
            }
          }, getAccessControlContext());
        }
        else {
          isEagerInit = (factory instanceof SmartFactoryBean &&
              ((SmartFactoryBean<?>) factory).isEagerInit());
        }
        if (isEagerInit) {
          getBean(beanName);
        }
      }
      else {
        getBean(beanName);
      }
    }
  }

  // Trigger post-initialization callback for all applicable beans...
  for (String beanName : beanNames) {
    Object singletonInstance = getSingleton(beanName);
    if (singletonInstance instanceof SmartInitializingSingleton) {
      final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
      if (System.getSecurityManager() != null) {
        AccessController.doPrivileged(new PrivilegedAction<Object>() {
          @Override
          public Object run() {
            smartSingleton.afterSingletonsInstantiated();
            return null;
          }
        }, getAccessControlContext());
      }
      else {
        smartSingleton.afterSingletonsInstantiated();
      }
    }
  }
}

SpringBoot源码之旅——IoC容器
https://zhuanlan.zhihu.com/p/54578816

SpringApplication.run做了什么?

1、创建并初始化应用上下文 ApplicationContext, 即 Spring 的 IOC 容器, 或者叫 Bean 容器。
2、扫描所有 Bean 并初始化所有非lazy-init的单例bean。


Spring Boot DataSource

不需要配置spring.datasource.driver-class-name

数据源配置:

spring.datasource.driver-class-name = com.mysql.jdbc.Driver
spring.datasource.url = jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=UTF-8&useAffectedRows=true
spring.datasource.username = username
spring.datasource.password = password

启动时提示
Loading class com.mysql.jdbc.Driver'. This is deprecated. The new driver class iscom.mysql.cj.jdbc.Driver’. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
使用 mysql-connector-java 8+ 时, 不需要再指定 spring.datasource.driver-class-name 驱动类会通过 SPI 机制自动注册,不需要手动指定。
如果你非要手动指定,com.mysql.jdbc.Driver 已废弃,改为 com.mysql.cj.jdbc.Driver 即可。


启动Spring Boot应用

指定端口号启动

Springboot 默认以端口号 8080 启动容器

在Spring配置文件中配置server.port

application.properties 配置:

server.port=8888

application.yml 配置:

server:
  port: 8888

server.port修改容器端口原理

为什么配置 server.port 就可以实现修改SpringBoot的默认端口呢?
因为在SpringBoot中有这样的一个类 ServerProperties

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered {
 private Integer port;

 @Override
 public void customize(ConfigurableEmbeddedServletContainer container) {
   if (getPort() != null) {
     container.setPort(getPort());
   }
 }
}

在这个类里有一个 @ConfigurationProperties 注解,这个注解会读取SpringBoot的默认配置文件 application.properties 的值注入到bean里。
这里定义了一个server的前缀和一个port字段,所以在SpringBoot启动的时候会从 application.properties 读取到 server.port 的值。
重写了 EmbeddedServletContainerCustomizer 接口的 customize 的方法,这个方法里会给容器设置读取到的端口号。
仅限 spring boot 1.5中, spring boot 2.x 中没有这个类了

使用命令行参数–server.port

如果你只是想在启动的时候修改一次端口号的话,可以用命令行参数来修改端口号。
java -jar app.jar --server.port=8000

使用虚拟机参数-Dserver.port

你同样也可以把修改端口号的配置放到JVM参数里。
-Dserver.port=8009

启动类实现EmbeddedServletContainerCustomizer接口

仿照 ServerProperties 类,实现 EmbeddedServletContainerCustomizer 接口的 customize 方法来设定容器属性
仅限 spring boot 1.5中, spring boot 2.x 中没有这个类了

@SpringBootApplication
public class Application implements ApplicationRunner, EmbeddedServletContainerCustomizer {
    public static void main(String[] args) {
        SpringApplication.run(ConfigMain.class, args);
    }

    @Override
    public void customize(ConfigurableEmbeddedServletContainer container) {
        //指定项目名称
        container.setContextPath("/demo");
        //指定端口地址
        container.setPort(8090);
    }

    @Override
    public void run(ApplicationArguments applicationArguments) throws Exception {
        // do something
        logger.info("welcomes you!");
    }
}

指定profile启动

配置文件spring.profiles.active

spring:
  profiles:
    active: dev

使用命令行参数–spring.profiles.active

java -jar app.jar --spring.profiles.active=prod

使用虚拟机参数-Dspring.profiles.active

也可以把修改端口号的配置放到JVM参数里。
-Dspring.profiles.active=prod


停止Spring Boot应用的几种方法

调用Actuator停止端点

Actuator 有个 shutdown 端点

这个关闭应用程序对应的Endpoint是ShutdownEndpoint,直接调用ShutdownEndpoint提供的rest接口即可。得先开启ShutdownEndpoint(默认不开启),以及不进行安全监测:

endpoints.shutdown.enabled: true
endpoints.shutdown.sensitive: false

然后调用rest接口:

curl -X POST http://localhost:8080/shutdown

可以使用spring-security进行安全监测:

endpoints.shutdown.sensitive: true
security.user.name: admin
security.user.password: admin
management.security.role: SUPERUSER

然后使用用户名和密码进行调用:

curl -u admin:admin -X POST http://127.0.0.1:8080/shutdown

这个ShutdownEndpoint底层其实就是调用了Spring容器的close方法:

暴露SpringApplication的exit静态方法

SpringApplication提供了一个exit静态方法,用于关闭Spring容器,该方法还有一个参数exitCodeGenerators表示ExitCodeGenerator接口的数组。ExitCodeGenerator接口是一个生成退出码exitCode的生成器。

public static int exit(ApplicationContext context,
        ExitCodeGenerator... exitCodeGenerators) {
    Assert.notNull(context, "Context must not be null");
    int exitCode = 0; // 默认的退出码是0
    try {
        try {
            // 构造ExitCodeGenerator集合
            ExitCodeGenerators generators = new ExitCodeGenerators();
            // 获得Spring容器中所有的ExitCodeGenerator类型的bean
            Collection<ExitCodeGenerator> beans = context
                    .getBeansOfType(ExitCodeGenerator.class).values();
            // 集合加上参数中的ExitCodeGenerator数组
            generators.addAll(exitCodeGenerators);
            // 集合加上Spring容器中的ExitCodeGenerator集合
            generators.addAll(beans);
            // 遍历每个ExitCodeGenerator,得到最终的退出码exitCode
            // 这里每个ExitCodeGenerator生成的退出码如果比0大,那么取最大的
            // 如果比0小,那么取最小的
            exitCode = generators.getExitCode();
            if (exitCode != 0) { // 如果退出码exitCode不为0,发布ExitCodeEvent事件
                context.publishEvent(new ExitCodeEvent(context, exitCode));
            }
        }
        finally {
            // 关闭Spring容器
            close(context);
        }

    }
    catch (Exception ex) {
        ex.printStackTrace();
        exitCode = (exitCode == 0 ? 1 : exitCode);
    }
    return exitCode;
}

写个Controller直接调用exit方法:

@Autowired
private ApplicationContext applicationContext;

@PostMapping("/stop")
public String stop() {
        // 加上自己的权限验证
    SpringApplication.exit(applicationContext);
    return "ok";
}

SpringBoot应用程序的关闭
https://fangjian0423.github.io/2017/06/28/springboot-application-exit/


spring boot undertow

Undertow 是红帽公司开发的一款基于 NIO 的高性能 Web 嵌入式服务器

Untertow 的特点:

  • 轻量级:它是一个 Web 服务器,但不像传统的 Web 服务器有容器概念,它由两个核心 Jar 包组成,加载一个 Web 应用可以小于 10MB 内存
  • Servlet3.1 支持:它提供了对 Servlet3.1 的支持
  • WebSocket 支持:对 Web Socket 完全支持,用以满足 Web 应用巨大数量的客户端
  • 嵌套性:它不需要容器,只需通过 API 即可快速搭建 Web 服务器

默认情况下 Spring Cloud 使用 Tomcat 作为内嵌 Servlet 容器,可启动一个 Tomcat 的 Spring Boot 程序与一个 Undertow 的 Spring Boot 程序,通过 VisualVM 工具进行比较,可看到 Undertow 性能优于 Tomcat

使用undertow

从starter-web中排除Tomcat,同时增加undertow依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

配置undertow

# Undertow 日志存放目录
server.undertow.accesslog.dir
# 是否启动日志
server.undertow.accesslog.enabled=false
# 日志格式
server.undertow.accesslog.pattern=common
# 日志文件名前缀
server.undertow.accesslog.prefix=access_log
# 日志文件名后缀
server.undertow.accesslog.suffix=log
# HTTP POST请求最大的大小
server.undertow.max-http-post-size=0
# 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程
server.undertow.io-threads=4
# 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载
server.undertow.worker-threads=20
# 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
# 每块buffer的空间大小,越小的空间被利用越充分
server.undertow.buffer-size=1024
# 每个区分配的buffer数量 , 所以pool的大小是buffer-size * buffers-per-region
server.undertow.buffers-per-region=1024
# 是否分配的直接内存
server.undertow.direct-buffers=true

SpringBoot国际化

spring boot默认就支持国际化的,而且不需要你过多的做什么配置,只需要在resources/下定义国际化配置文件即可,注意名称必须以messages开头。

定义如下几个文件:
messages.properties (默认,当找不到语言的配置的时候,使用该文件进行展示)。
messages_zh_CN.properties(中文)
messages_en_US.properties(英文)

不同语言环境下看到不同内容如何实现的?

为了让web应用程序支持国际化,必须识别每个用户的首选区域,并根据这个区域显示内容。在Spring MVC应用程序中,用户的区域是通过区域解析器来识别的,它必须是实现LocaleResolver接口。Spring MVC提供了几个LocaleResolver实现,让你可以按照不同的条件来解析区域。除此之外,你还可以实现这个接口创建自己的区域解析器。如果没有做特殊的处理的话,Spring 采用的默认区域解析器是AcceptHeaderLocaleResolver。它通过检验HTTP请求的头部信息accept-language来解析区域。这个头部是由用户的wb浏览器底层根据底层操作系统的区域设置进行设定的。请注意,这个区域解析器无法改变用户的区域,因为它无法修改用户操作系统的区域设置。

58 Spring Boot国际化(i18n)【从零开始学Spring Boot】
http://412887952-qq-com.iteye.com/blog/2312274


IDEA spring boot打包

View -> Tool Windows -> Maven Projects 调出Maven Projects窗口,打开“项目名”下的Lifecycle,双击package,开始自动打包

跳过测试

再Maven Projects边栏中,点击闪电图标 Toggle ‘Skip Tests’ Mode,选中后即打开跳过测试模式。
或者
mvn clean package -DskipTests
或mvn clean package -Dmaven.test.skip=true
或者在pom中配置:

<plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <configuration>
          <skip>true</skip>
        </configuration>
      </plugin>
</plugins>

spring boot 配置文件

实际上,Spring Boot应用程序有多种设置途径,Spring Boot能从多重属性源获得属性,包括如下几种:
根目录下的开发工具全局设置属性(当开发工具激活时为~/.spring-boot-devtools.properties)。
测试中的 @TestPropertySource 注解。
测试中的 @SpringBootTest#properties 注解特性。
命令行参数
SPRING_APPLICATION_JSON 中的属性(环境变量或系统属性中的内联JSON嵌入)。
ServletConfig初始化参数。
ServletContext初始化参数。
java:comp/env里的JNDI属性
JVM系统属性
操作系统环境变量
随机生成的带random.* 前缀的属性(在设置其他属性时,可以应用他们,比如${random.long})
应用程序以外的 application.properties 或者 appliaction.yml 文件
打包在应用程序内的 application.properties 或者 appliaction.yml 文件
通过 @PropertySource 标注的属性源
默认属性(通过 SpringApplication.setDefaultProperties 指定).

这里列表按组优先级排序,也就是说,任何在高优先级属性源里设置的属性都会覆盖低优先级的相同属性,列如我们上面提到的命令行属性就覆盖了application.properties的属性。

application.properties和application.yml同时存在

spring boot 项目中同时存在 application.properties 和 application.yml 文件时,两个文件都有效,但是 application.properties 的优先级会比 application.yml 高。
如果工程中同时存在 application.properties 文件和 application.yml 文件,yml 文件会先加载,而后加载的 properties 文件会覆盖 yml 文件。
所以建议工程中,只使用其中一种类型的文件即可。

application.yml 与 bootstrap.yml

yml 和 properties 文件是一样的原理,随意选择哪种格式都行。

bootstrap.yml(bootstrap.properties)先加载
application.yml(application.properties)后加载

bootstrap.yml 可以理解成系统级别的一些参数配置,这些参数一般是不会变动的。
application.yml 可以用来定义应用级别的配置。

搭配 spring-cloud-config 使用时在 bootstrap.yml 中指定配置中心地址 spring.cloud.config.uri 等参数, 而 application.yml 中的配置内容保存在配置中心中。

当使用Spring Cloud时,通常从服务器加载配置数据。为了获取URL(和其他连接配置,如密码等),您需要一个较早的或“bootstrap”配置。因此,您将配置服务器属性放在bootstrap.yml中,该属性用于加载实际配置数据(通常覆盖application.yml 中的内容)。

SpringCloud入门之常用的配置文件 application.yml和 bootstrap.yml区别
https://www.cnblogs.com/BlogNetSpace/p/8469033.html

spring boot 配置中引用maven pom.xml 属性

想要在 spring boot 中访问 maven pom.xml 中的属性,只需要在配置项前后加 @ 引用即可

例如 application.properties 中:

app.version=@project.version@
app.name=@project.name@

java 代码中

@Service
public class SomeService {

   @Value("${app.version}")
   private String appVersion;

   // other stuff
}

Cannot get maven project.version property in a Spring application with @Value
https://stackoverflow.com/questions/38983934/cannot-get-maven-project-version-property-in-a-spring-application-with-value


创建Spring boot项目

通过IntelliJ IDEA使用(个人推荐)
IntelliJ IDEA是非常流行的IDE,IntelliJ IDEA 14.1已经支持Spring Boot了。
创建Spring Boot操作步骤如下:
1.在File菜单里面选择 New > Project,然后选择Spring Initializr


Spring Boot父级依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.1.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

这块配置就是Spring Boot父级依赖,有了这个,当前的项目就是Spring Boot项目了,spring-boot-starter-parent是一个特殊的starter,它用来提供相关的Maven默认依赖,使用它之后,常用的包依赖可以省去version标签。

在maven页面查看 spring-boot-starter-parent/2.2.1.RELEASE 中管理的依赖版本
https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-parent/2.2.1.RELEASE

如果你不想使用某个依赖默认的版本,您还可以通过覆盖自己的项目中的属性来覆盖各个依赖项,例如,要升级到另一个Spring Data版本系列,您可以将以下内容添加到pom.xml中。


Spring Boot Maven插件

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

上面的配置就是Spring Boot Maven插件,Spring Boot Maven插件提供了许多方便的功能:
1、把项目打包成一个可执行的超级JAR, 或者叫 fat jar,包括把应用程序的所有依赖打入JAR文件内,并为JAR添加一个描述文件,其中的内容能让你用java -jar来运行应用程序。
2、搜索public static void main()方法来标记为可运行类。


spring boot自定义属性乱码问题

#设置spring-boot 编码格式
server.tomcat.uri-encoding=UTF-8
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true
spring.messages.encoding=UTF-8

设置 File Encodings的Transparent native-to-ascii conversion为true,具体步骤如下:
依次点击
File -> Settings -> Editor -> File Encodings
将Properties Files (*.properties)下的Default encoding for properties files设置为UTF-8,将Transparent native-to-ascii conversion前的勾选上。

Spring Boot 自定义属性 以及 乱码问题
https://blog.csdn.net/m0_37995707/article/details/77506184

Spring Boot干货系列:(一)优雅的入门篇
http://tengj.top/2017/02/26/springboot1/

Spring Boot干货系列总纲
http://tengj.top/2017/04/24/springboot0/


Spring Boot ApplicationRunner 实例

使用的一个 Spring Boot 应用类

package com.masikkk;

import com.masikkk.common.config.MyServiceConfig;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration;
import org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
import org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration;
import org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration;
import org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration;
import org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Import;
import org.springframework.scheduling.annotation.EnableAsync;

@Import({
        MyServiceConfig.class,
        CassandraConfig.class,
        MariadbConfig.class,
        RedisConfig.clas
})
@MapperScan(value = {
        "com.masikkk.mariadb.mapper",
        "com.masikkk.mariadb.data.mapper",
        "com.masikkk.common.mapper",
        "com.masikkk.job.mapper",
        "com.masikkk.tidb.mapper"
})
@SpringBootApplication(exclude = {
        FlywayAutoConfiguration.class,
        ErrorMvcAutoConfiguration.class,
        GsonAutoConfiguration.class,
        JdbcTemplateAutoConfiguration.class,
        JmxAutoConfiguration.class,
        MultipartAutoConfiguration.class,
        RedisAutoConfiguration.class,
        WebClientAutoConfiguration.class,
        WebSocketAutoConfiguration.class,
        DataSourceAutoConfiguration.class
})
@EnableAsync
@EnableDiscoveryClient
public class Application implements ApplicationRunner {

    private static final Logger logger = LoggerFactory.getLogger(Application.class);

    @Value("${service.name}")
    private String serviceName;

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Override
    public void run(ApplicationArguments applicationArguments) throws Exception {
        // 服务启动时所作的初始化动作
        logger.info("{} welcomes you!", serviceName);
    }
}

上一篇 DataGrip使用笔记

下一篇 Phabricator使用笔记

阅读
评论
5,918
阅读预计27分钟
创建日期 2018-06-11
修改日期 2020-01-07
类别

页面信息

location:
protocol:
host:
hostname:
origin:
pathname:
href:
document:
referrer:

评论