Springboot常用功能特性

930 阅读5分钟

Springboot常用功能特性

应用启动的三种主要方式

首先应该在程序入口处加上 @SpringBootApplication

@SpringBootApplication
public class SpringBootStudyApplication {}

1、SpringApplication静态方法run

SpringApplication.run(SpringBootStudyApplication.class, args);

2、通过Api调整应用行为

SpringApplication application =
        new SpringApplication(SpringBootStudyApplication.class);
application.setBannerMode(Banner.Mode.OFF);
application.setWebApplicationType(WebApplicationType.NONE);//设置了非web环境程序会结束
application.run(args);

3、SpringApplication的Fluent Api,实现链式调用

new SpringApplicationBuilder(SpringBootStudyApplication.class)
.bannerMode(Banner.Mode.OFF)
//                .web(WebApplicationType.NONE)
.run(args);
SpringBoot自动配置原理

@SpringBootConfiguration   //相当于Configuration,实际上也是继承自它
@EnableAutoConfiguration   //开启自动配置核心注解
@ComponentScan(excludeFilters = {//扫描注解  Service Component...
      @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM,
            classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}

分析 EnableAutoConfiguration

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class) // 导入AutoConfigurationImportSelector
public @interface EnableAutoConfiguration {}

分析 AutoConfigurationPackage

@Import(AutoConfigurationPackages.Registrar.class) // 导入Registrar
public @interface AutoConfigurationPackage {}

分析 Registrar

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
   // 注册了主应用程序bean
   @Override
   public void registerBeanDefinitions(AnnotationMetadata metadata,
         BeanDefinitionRegistry registry) {
      register(registry, new PackageImport(metadata).getPackageName());
   }
   @Override
   public Set<Object> determineImports(AnnotationMetadata metadata) {
      return Collections.singleton(new PackageImport(metadata));
   }

}

分析 AutoConfigurationImportSelector

org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#selectImports

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
   }
   AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
         .loadMetadata(this.beanClassLoader);
   AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
         autoConfigurationMetadata, annotationMetadata);//点下去
   return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

protected AutoConfigurationEntry getAutoConfigurationEntry(
      AutoConfigurationMetadata autoConfigurationMetadata,
      AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
   }
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   List<String> configurations = getCandidateConfigurations(annotationMetadata,
         attributes);//点进去
   configurations = removeDuplicates(configurations);
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   checkExcludedClasses(configurations, exclusions);
   configurations.removeAll(exclusions);
   configurations = filter(configurations, autoConfigurationMetadata);
   fireAutoConfigurationImportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
      AnnotationAttributes attributes) {
   List<String> configurations = SpringFactoriesLoader.loadFactoryNames(//点进去
         getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
   Assert.notEmpty(configurations,
         "No auto configuration classes found in META-INF/spring.factories. If you "
               + "are using a custom packaging, make sure that file is correct.");
   return configurations;
}

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
   String factoryClassName = factoryClass.getName();
   return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

可追踪到 org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories

Enumeration<URL> urls = (classLoader != null ?
      //public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";                
      classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
      ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
   URL url = urls.nextElement();
   UrlResource resource = new UrlResource(url);
   Properties properties = PropertiesLoaderUtils.loadProperties(resource);
   for (Map.Entry<?, ?> entry : properties.entrySet()) {
      String factoryClassName = ((String) entry.getKey()).trim();
      for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
         result.add(factoryClassName, factoryName.trim());
      }
   }
}
cache.put(classLoader, result);

SpringBoot配置文件
同一目录下的application和bootstrap

bootstrap优先级高于application,优先被加载

bootstrap用于应用程序上下文的引导阶段,由父ApplicationContext加载

bootstrap是系统级别的配置(不变的参数),application是应用级别的配置

不同位置的配置文件再加顺序(优先级)

高优先级覆盖低优先级相同配置,多个配置文件互补

file:./config/ - 优先级最高(项目根路径下的config)

file:./ - 优先级第二(项目根路径下)

classpath:/config/ - 优先级第三(项目resources/config下)

classpath:/ - 优先级第四(项目resources目录下)

配置注入方式
luyuni:
  springboot:
    version: 2.1,2.1.4
    name: study

  • 方式一:@value
@Value(value = "${luyuni.springboot.version}")
private String version;
@Value(value = "${luyuni.springboot.name}")
private String name;

  • 方式二:@ConfigurationProperties
@Data
@Component
@ConfigurationProperties(prefix = "luyuni.springboot")
public class SpringBootConfig {
    private String version;
    private String name;
}

定时任务
  • 1、开启定时任务
@EnableScheduling  //开启定时任务

  • 2、编写定时任务
@Slf4j
@Component
public class BootSchedule {

    private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");

    /**
     * 上一次开始执行时间点之后3000毫秒再执行
     * */
    @Scheduled(fixedRate = 3000)
    public void schedule01() {
        log.info("schedule01 -> {}", LocalDateTime.now().format(formatter));
    }

    /**
     * 上一次执行完毕时间点之后3s再执行
     * */
    @Scheduled(fixedDelay = 3000)
    public void schedule02() {
        log.info("schedule02 -> {}", LocalDateTime.now().format(formatter));
    }

    /**
     * 第一次延迟2s之后执行, 之后按照每3s执行一次
     * */
    @Scheduled(initialDelay = 2000, fixedRate = 3000)
    public void schedule03() {
        log.info("schedule03 -> {}", LocalDateTime.now().format(formatter));
    }

    /**
     * 每3s执行一次
     * */
    @Scheduled(cron = "*/3 * * * * ?")
    public void schedule04() {
        log.info("schedule04 -> {}", LocalDateTime.now().format(formatter));
    }
}

异步任务
  • 1、引入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

  • 2、开启异步任务
@EnableAsync  //开启异步支持

  • 3、编写异步方法
@Slf4j
@Component
public class AsyncService {

    @Async   //新开一个线程执行任务
    public void asyncProcess() throws InterruptedException{
        log.info("async process task, current thread name -> {}", Thread.currentThread().getName());
        TimeUnit.SECONDS.sleep(2); //模拟处理任务花费时间
    }

    @Async
    public Future<Integer> asyncProcessHasReturn() throws InterruptedException{
        log.info("async process task (has return), current thread name -> {}", Thread.currentThread().getName());
        TimeUnit.SECONDS.sleep(2); //模拟处理任务花费时间
        return new AsyncResult<Integer>(100);
    }
}

异步任务所使用线程池相关定义

@Slf4j
@Configuration
public class AsyncPoolConfig implements AsyncConfigurer {//实现AsyncConfigurer

    @Bean
    @Override
    public Executor getAsyncExecutor() {

        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(20);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("LuyuniAsync_");
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(60);
        // 拒绝策略,这里是直接抛出异常
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        executor.initialize();

        return executor;
    }

    /**
     * 定义异步任务异常处理类
     * */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new AsyncExceptionHandler();
    }

    class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
        @Override
        public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
            log.info("AsyncError: {}, Method: {}, Param: {}", throwable.getMessage(), method.getName(), JSON.toJSONString(objects));
            throwable.printStackTrace();

            // TODO 发送邮件或者短信
        }
    }
}

SpringBoot单元测试

对上面的异步服务功能进行测试

@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class AsyncServiceTest {

    @Autowired
    private AsyncService asyncService;

    @Test
    public void testAsyncProcess() throws InterruptedException{
        asyncService.asyncProcess();
        log.info("coming in testAsyncProcess ...");
    }

    @Test
    public void testAsyncProcessHasReturn() throws InterruptedException, ExecutionException, TimeoutException {

        long start = System.currentTimeMillis();
        Future<Integer> result = asyncService.asyncProcessHasReturn();
        log.info("get async task value -> {}", result.get(1, TimeUnit.SECONDS));
        log.info("time spend for async task: {}ms", System.currentTimeMillis() - start);
    }
}

SpringBoot开机启动
  • 1、实现ApplicationRunner
@Slf4j
@Component
public class BootApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("this is BootApplicationRunner ...");
    }
}

  • 2、实现CommandLineRunner
@Slf4j
@Component
public class BootCommandLineRunner implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        log.info("this is BootCommandLineRunner ...");
    }
}

默认ApplicationRunner优先执行、有指定需求可指定顺序 @Order(1) 值越小越先执行,或实现 Order 接口

SpringBoot - Actuator监控
Actuator是什么,它能做什么
  • 是什么:是SpringBoot提供的对应用系统的自醒和监控的集成功能
  • 能做什么:查看应用(配置)信息、环境信息以及对应用进行操控
Actuator监控分类

原生端点

  • 应用配置类

    • 自己配置的info信息
    # 暴露的info信息
    info:
      app:
        name: luyuni-springboot-study
        groupId: top.luyuni.springboot.study
        version: 1.0-SNAPSHOT
    
    
    localhost:8000/luyuni/actuator/info
    
    
    • 应用中的bean信息
    localhost:8000/luyuni/actuator/beans
    
    
    • 应用中的uri路径信息
    localhost:8000/luyuni/actuator/mappings
    
    
  • 度量指标类

    • 检查应用的运行状态
    localhost:8000/luyuni/actuator/health
    
    
    • 当前线程活动快照
    localhost:8000/luyuni/actuator/threaddump
    
    
  • 操作控制类

    • 关闭应用 慎用
    localhost:8000/luyuni/actuator/shutdown
    
    

自定义端点

@Endpoint(id = "datetime")
public class DateTimeEndPoint {

    private String format = "yyyy-MM-dd HH:mm:ss";

    /**
     * 用来显示监控指标
     * localhost:8000/luyuni/actuator/datetime
     * */
    @ReadOperation
    public Map<String, Object> info() {
        Map<String, Object> info = new HashMap<>();
        info.put("name", "yulu");
        info.put("age", "19");
        info.put("datetime", new SimpleDateFormat(format).format(new Date()));
        return info;
    }

    /**
     * 动态更改监控指标  Post方式传入{"format":"yyyy-MM-dd"}
     * */
    @WriteOperation
    public void setFormat(String format) {
        this.format = format;
    }
}

自定义端点配置类

@Configuration
public class DateTimeEndpointConfig {

    @Bean
    @ConditionalOnMissingBean  //这个bean缺少时就注入bean
    @ConditionalOnEnabledEndpoint //只有开启时才注入
    public DateTimeEndPoint dateTimeEndPoint() {
        return new DateTimeEndPoint();
    }
}
自定义Starter

主要是定义 spring.factories ,让其加载自动配置类 XxxAutoConfigure

  • 创建maven工程定义好相应坐标
<groupId>top.luyuni.springboot</groupId>
<artifactId>split-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
  • 编写服务功能
// ...
  • 定义自实现 XxxAutoConfigure
@Configuration
@ConditionalOnClass(value = {ISplitService.class, SplitServiceImpl.class})//classpath发现这些类就进行自动配置
public class SplitAutoConfigure {
    @Bean
    @ConditionalOnMissingBean //上下文环境中不存在就配置
    ISplitService starterService(){
        return new SplitServiceImpl();
    }
}
  • 定义 spring.factories META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=top.luyuni.springboot.configure.SplitAutoConfigure
  • 放入maven仓库
split-spring-boot-starter>mvn clean install -Dmaven.test.skip=true