Spring Boot 王炸教程01 —— 相关配置

104 阅读8分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第 01 天,点击查看活动详情

1、SpringApplication 与 Spring 容器

再给大家详细讲讲「主启动类」中的一些细节。

先来看 SpringApplication,它是 Spring Boot 提供的一个工具类,提供的 run() 方法是用来启动 Spring 容器,运行 Spring Boot 应用的。

1.1 配置形式:类配置、XML 配置

在 Spring Boot 中,它推荐使用 Java 配置类来作为配置文件,也就是使用了 @Configuration 注解的类。

不过与使用 XML 文件作为配置文件,在本质上没有太多区别。

当你使用了 @Configuration 注解,被修饰的类,将作为主配置源。

比如,在主启动类中,我们点进 @SpringBootApplication 注解,可以看到它使用了 @SpringBootConfiguration 注解:

 @Target({ElementType.TYPE})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 @Inherited
 @SpringBootConfiguration
 @EnableAutoConfiguration
 @ComponentScan(
     excludeFilters = {@Filter(
     type = FilterType.CUSTOM,
     classes = {TypeExcludeFilter.class}
 ), @Filter(
     type = FilterType.CUSTOM,
     classes = {AutoConfigurationExcludeFilter.class}
 )}
 )
 public @interface SpringBootApplication {
   ……
 }

再点进来,你可以看到 @SpringBootConfiguration 使用了 @Configuration

 @Target({ElementType.TYPE})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 @Configuration
 @Indexed
 public @interface SpringBootConfiguration {
   ……
 }

你看,除了去加载主配置源中所有的配置,Spring Boot 还会用 @ComponentScan 注解自动扫描主配置源所在的包,以及它子包下所有带 @Component 注解(包括了 @Configuration、@Controller、@Service、@Repository 等注解)的配置类或 Bean 组件。

Spring Boot 在加载其他配置类、扫描其他包下的配置类或 Bean 组件,还可以用 @Import 注解,它能显示指定 Spring Boot 要加载的配置类:

 // 加载指定包下的 MyConfig 类作为配置类
 @Import(itman.mall.CommonConfig.class)

还有一种情况,如果项目非要让你使用 XML 配置文件,那你可以用 @ImportResource 注解来导入 XML 文件。

 // 加载类路径下的 spring.xml 文件作为配置文件
 @ImportResource("classpath:spring.xml")

案例代码,如下:

 // 额外指定这俩包,还有子包下所有的配置类和 Bean 组件
 @SpringBootApplication(scanBasePackages = {"itman.boot", "itman.boot2"})
 // 加载类路径下的 spring.xml 文件作为配置文件
 @ImportResource("classpath:spring.xml")
 // 加载指定包下的 CommonConfig 类作为配置类
 @Import(itman.mall.CommonConfig.class)
 public class SpBootApplication {
 ​
     public static void main(String[] args) {
         SpringApplication.run(SpBootApplication.class, args);
     }
 }

接着上面的代码,我们继续深入看一个东西 —— @SpringBootApplication 中的 scanBasePackages 属性

在 @SpringBootApplication 注解的源码中,你往下找,就可以看到一段代码:

 @AliasFor(
     annotation = ComponentScan.class,
     attribute = "basePackages"
 )
 String[] scanBasePackages() default {};

你看,@SpringBootApplication 中的 scanBasePackages 属性,其实就是 @ComponentScan 的 basePackages 属性,通过这属性,就可以显示指定 Spring Boot 扫描指定包及其子包下所有的配置类和 Bean 组件。

如果你不为 @SpringBootApplication 指定 scanBasePackages 属性,则它会默认加载主配置类,也就是 @SpringBootApplication 所修饰的类,所在的包及其子包下所有的配置类和 Bean 组件。

当在指定的包中,扫描到被 @Component 修饰的类,则会被扫描、配置成 Spring 容器中的 Bean。

案例代码,如下:

 @Component
 public class User{
   public String sayHello(){
     return "hello...";
   }
 }

当我们使用 @ImportResource("classpath:spring.xml"),其实就是去加载一个 Spring 框架传统的配置文件。

案例代码,如下:

 <?xml version="1.0" encoding="UTF-8"?>
 <beans
         xmlns="http://www.springframework.org/schema/beans"
         xmlns:context="http://www.springframework.org/schema/context"
         xmlns:tx="http://www.springframework.org/schema/tx"
         xmlns:aop="http://www.springframework.org/schema/aop"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://www.springframework.org/schema/beans
                         http://www.springframework.org/schema/beans/spring-beans.xsd
                         http://www.springframework.org/schema/context
                         http://www.springframework.org/schema/context/spring-context.xsd
                         http://www.springframework.org/schema/tx
                         http://www.springframework.org/schema/tx/spring-tx.xsd
                         http://www.springframework.org/schema/aop
                         http://www.springframework.org/schema/aop/spring-aop.xsd">
   <!-- 配置 Bean 组件 -->
   <bean id="dept" class="itman.boot.po.Dept"/>
 </beans>

@Import(itman.mall.CommonConfig.class),指定了 CommonConfig 作为额外的配置类。

案例代码,如下:

 @Configuration
 public class CommonConfig {
   @Bean
   public DateFormat dateFormat(){
     return DateFormat.getDateInstance();
   }
 }

最后,咱们可以提供一个 HelloController 控制器类,然后 Spring 容器会将上面这几种不同方式配置的 Bean,注入进来。

案例代码,如下:

 @RestController
 public class HelloController {
   @Autowired
   private User user;
   @Autowired
   private Dept dept;
   @Autowired
   private DateFormat dateFormat;
   
   @RequestMapping("/")
   public String test(){
     return user.sayHello() + "--" + dept + "--" + dateFormat.format(new Date());
   }
 }

最后,先去运行 SpBootApplication 主启动类,启动 Spring Boot 应用,在浏览器中访问 http://localhost:8080 就可以测试上面的 test() 方法了。

观察,上面的三个实例变量是否能够实现依赖注入,这也是我们的三种不同配置方式,通过这个案例我们理解得更清楚了。

1.2 启动日志和失败分析器

我们通过 SpringApplication 的 run() 方法来启动应用时,默认会显示 INFO 级别的日志消息,包括一些与启动相关的信息。

如果你想关闭的话,可以把如下属性设为 false 即可,也会同时关闭应用程序的活动 Profile 的日志:

 spring.main.log-startup-info = false

在我们程序启动的过程中,有时会碰到启动失败的情况,一般在控制台上可以看到相关的错误信息,这都是 Spring Boot 的失败分析器(Failure Analyzer) 所提供的,它会给出详细的错误信息和修复建议。

另外,我们还可以通过开启 debug 属性,或者将 ConditionEvaluationReportLoggingListener 的日志级别设为 DEBUG 即可实现。

如果是通过 JAR 包的方式来运行 Spring Boot 应用,那么就通过 --debug 来开启 debug 属性。

 java -jar firstboot-0.0.1-SNAPSHOT.jar --debug

或者在 application.properties 文件中添加下面这个配置信息,它能将 org.springframework.boot.autoconfigure.logging 包下所有类的日志级别都设为 DEBUG,上面说的 ConditionEvaluationReportLoggingListener 监听器就在这个包位置下。

 logging.level.org.springframework.boot.autoconfigure.logging = debug

像这种失败分析器,我们也可以自定义的。

自定义失败分析器需要在 META-INF/spring.factories 文件中注册,首先在项目的 resources 目录下创建 META-INF 文件夹(注意大小写和短横线),然后在 META-INF 文件夹内创建 spring.factories 文件。

该文件的内容如下:

 org.springframework.boot.diagnostics.FailureAnalyzer=\
 itman.boot.HelloAnalyzer

自定义的失败分析器,应继承 AbstractFailureAnalyzer﹤T﹥,该基类中的泛型 T 代表该失败分析器要处理的异常。

接着,实现它的 analyze() 抽象方法,该方法返回的 FailureAnalysis 代表了对该异常的分析结果。

假如,你不想让该失败分析器分析该异常,而是希望将该异常留给下一个分析器去分析,那就让该方法返回 null。

 public class HelloAnalyzer extends AbstractFailureAnalyzer<BindException>{
   
   public FailureAnalysis analyze(Throwable rootFailure, BindException cause){
     cause.printStackTrace();
     return new FailureAnalysis("注意,你的程序绑定的端口,被占用了:" 
                                + cause.getMessage(), 
                               "先找出、停止占用 8080 端口的程序,然后再启动本应用"
                               + cause);
   }
 }

analyze() 方法返回了一个 FailureAnalysis 对象,表明该失败分析器会对 BindException 进行分析。本质上,FailureAnalysis 就是包装这三个信息:

  • description:失败的描述信息,第一个构造参数;
  • action:对该失败的修复建议。第二个构造参数;
  • cause:导致失败的异常。第三个构造参数。

1.3 延迟初始化

我们使用的是 ApplicationContext 作为 Spring 容器,Spring Boot 默认会对容器中所有的 singleton Bean 做预初始化。

但在某些特殊情况下,如何取消预初始化,改为延迟初始化呢?有三种方式:

  • 调用 SpringApplicationBuilder 对象的 lazyInitialization(true) 方法;

  • 调用 SpringApplication 对象的 setLazyInitialization(true) 方法;

  • 在 application.properties 文件中添加如下配置:

     spring.main.lazy-initialization=true
    

延迟初始化,是可以等到程序需要调用 Bean 的方法时才执行初始化,因此可降低 Spring Boot 应用的启动时间。但,也是有一定缺点的:

  • 特别是在 Web 应用中,很多与 Web 相关的 Bean 要等到 HTTP 请求第一次到来时才会初始化,如果延迟初始化则会降低第一次处理 HTTP 请求的响应效率;
  • Bean 错误被延迟发现;
  • 运行过程中的内存紧张。

1.4 自定义 Banner

Spring Boot 应用启动后,在控制台上可以看到 Spring 的 Banner,如果你想关掉它,可以在 application.properties 文件中,设置 spring.main.banner-mode 属性为 off 即可:

 spring.main.banner-mode=off

spring.main.banner-mode 属性有三个属性值:

  • console:在控制台中输出 Banner;
  • log:在日志文件中输出 Banner;
  • off:彻底关闭 Banner。

如果你想自定义 Banner 的话,可以在类加载路径下,添加一个 banner.txt 文件,或者设置 spring.banner.location 来指定 Banner 文件的位置。

默认情况下,Spring Boot 基于 UTF-8 字符集的方式,来读取 Banner 文件中的内容,如果想改其他字符集,可以在文件中设置:

 spring.banner.charset=GBK

推荐一些能自动生成 banner 字符画的网站:

patorjk.com/software/ta…

www.network-science.de/ascii/

www.degraeve.com/img2txt.ph

patorjk.com/software/ta…

www.network-science.de/ascii/

www.degraeve.com/img2txt.php

image-20221010005555807.png

Spring Boot 也可以使用图片文件来添加 Banner,只要在类加载路径下添加一个 banner.gif、banner.jpg 或 banner.png 等图片文件。

也可以通过 spring.banner.image.location 属性设置图片 Banner 的加载路径,Spring Boot就会自动把该图片转换为字符画(ASCII art)形式,并作为应用程序的 Banner 显示。

如果图片和文本类型的 Banner 同时存在,会先显示图片类型的 Banner 所对应的字符画,再显示文本类型的 Banner 的内容。

在 application.properties 文件中,添加一些相关的配置:

 spring.banner.image.height = 20
 spring.banner.image.width = 60
 # 设置字符串的色度
 spring.banner.image.bitdepth = 4

1.5 设置 SpringApplication 与流式 API

我们知道,在主启动类中,直接调用 SpringApplication 的 run() 方法可以运行 Spring Boot 应用,这是创建 SpringAoolication 时自动采用的默认配置。

但如果你想自定义设置 SpringApplication 的话,你就需要先定义 SpringApplication 对象,然后比如你想延迟初始化,可以调用 setLazyInitlization(true),如果你想设置 Banner,则可以调用 setBanner(),最后再调用该对象的 run() 方法来启动。

案例代码,如下:

 @SpringBootApplication
 public class SpBootApplication {
 ​
     public static void main(String[] args) {
       
       // 1、创建 SpringApplication 对象
       var app = new SpringApplication(SpBootApplication.class);
       // 2、设置延迟初始化
       app.setLazyInitlization(true);
       // 3、设置 Banner
       Banner banner = 构造你的 Banner
       app.setBanner(banner)
       // 4、启动
       app.run(args);
     }
 }

除了直接调用构造器来创建 SpringApplication 对象,Spring Boot 还提供了 SpringApplicationBuilder 工具类,通过该工具类能以流式 API 创建 SpringApplication,并启动 Spring Boot 应用。

SpringApplicationBuilder 主要提供了几个方法来加载配置文件,而且可以构建 ApplicationContext 的层次结构(让 Spring 容器具有父子关系):

  • sources(Class﹤?﹥...sources):为应用添加配置类;
  • child(Class﹤?﹥...sources):为当前容器添加子容器配置类;
  • parent(Class﹤?﹥...sources):为当前容器添加父容器配置类。
 @SpringBootApplication
 public class SpBootApplication {
 ​
     public static void main(String[] args) {
       
       new SpringAppliationBuilder()
         .sources(Parent.class)  // 加载父容器对应的配置类
         .child(SpBootApplication.class) // SpBootApplication 类对应的配置作为子容器
         .setLazyInitlization(true)  // 设置延迟初始化
         .bannerMode(Banner.Mode.OFF)  // 关掉 Banner
         .run(args); // 启动
     }
 }

其实,让多个 Spring 容器具有层次结构具有很多好处。

比如,由于子容器是由父容器负责加载的,因此子容器中的Bean可访问父容器中的 Bean,但父容器中的 Bean 不能访问子容器中的 Bean。

原创不易,如果文章能帮到您,能帮我顺手点个赞、转发一下嘛?真心感谢、超级感谢!!!