02-SpringBoot中的概念

nobility 发布于 2022-05-24 868 次阅读


SpringBoot中的概念

依赖管理

SpringBoot项目自动引入依赖的原因是引用了一个父项目和starter场景启动器

场景启动器

依赖场景启动器才会将某个场景下的所有依赖引入,SpringBoot官方提供的启动器可从SpringBoot官方网站中查到,通常以spring-boot-starter-*开头的都是官方提供的场景启动器,使用*-spring-boot-starter结尾的都是第三方提供的场景启动器

版本控制

父项目又依赖与一个版本控制(spring-boot-dependencies)的父项目,该项目中声明了几乎所有常用的jar包的版本号,所以官方场景启动器中无需声明依赖版本,对于非官方场景启动器一般要声明依赖版本

若想在当前项目中更改某个依赖的版本,覆盖版本控制的父项目配置即可,具体如下:

<properties>
  <mysql.version>5.1.38</mysql.version>
</properties>

自动配置

在某个场景启动器下自动引入的依赖由SpringBoot自动配置该依赖,若想改变这些自动配置的默认值,就可以在SpringBoot的配置文件appllication.properties中进行修改,并且这些配置都是按需加载的,只有引入某个场景启动器后配置才会生效

比如Web场景下就配置了如下:

  • Tomcat:整合SpringMVC的中央调度器,字符编码过滤器等
  • Spring注解扫描规则:默认是主程序所在的包以及所有子包
  • SpringMVC全套常用功能:JSON解析等
  • ...

配置文件的转变

配置类

使用配置类和常规配置文件代替Spring的XML配置,使用配置类代替XML的优势在于,配置更加灵活,可通过代码有选择的进行配置,以下这些注解其实都是SpringFramework中的注解

  • 配置类就是使用@Configuration注解标注的类,对应XML配置文件中beans标签,配置类本身也会被加入到IOC容器中
  • 配置类中的方法使用@Bean注解标注,对应XML配置文件中的bean标签
package com.config;

import com.entity.Student;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration  //标注该类为配置类
public class MyConfiguration {
  @Value("${student.name}")  //用于注入application.properties配置文件中内容的表达式语法
  private String studentName;

  @Bean  //向IOC容器中添加的beanID默认是方法名,bean是该方法返回的对象
  public Student student() {  //该Bean在IOC容器中的ID就是该方法名
    return new Student(this.studentName);
  }
}

对应application.properties文件中的内容如下

student.name=zhangsan

在入口方法中进行如下测试

@SpringBootApplication
public class Main {
  public static void main(String[] args) {
    ConfigurableApplicationContext run = SpringApplication.run(Main.class, args);  //该方法会返回SpringBean工厂对象
    Student student = (Student) run.getBean("student");
    System.out.println(student);
  }
}
调用配置类的Bean方法

从容器中获取的配置类实例,默认模式(lite模式)下调用其中返回Bean方法是单例的,这种模式下在每次调用Bean方法时都会检查该容器中是否已经存在了该类型的Bean,可使用@Configuration注解中的proxyBeanMethods属性对该行为进行控制

@Configuration(proxyBeanMethods = false)  //使用full模式
public class MyConfiguration {
  @Bean
  public Student student() {
    return new Student();
  }
}

在入口方法中进行如下测试

@SpringBootApplication
public class Main {
  public static void main(String[] args) {
    ConfigurableApplicationContext run = SpringApplication.run(Main.class, args); //该方法会返回SpringBean工厂对象
    MyConfiguration myConfiguration = (MyConfiguration) run.getBean("myConfiguration");
    Student student1 = myConfiguration.student();
    Student student2 = myConfiguration.student();
    System.out.println(student1 == student2);  //false
  }
}

自动配置底层注解

配置绑定

使用@ConfigurationProperties注解指定前缀进行配置绑定,对应appllication.properties中定义的配置进行自动注入,使用@PropertySource注解可指定其他非appllication.properties名的文件,使用setter方法进行的注入,所以要保证该属性有setter方法

主动注册

将普通Bean加入到容器即可

@Component  //也可以使用其他加入容器的注解
@ConfigurationProperties("student")  //指定前缀
public class Student {
  private String name;
  //setter、getter和toString方法省
}

对应application.properties文件中的内容如下

student.name=zhangsan

在入口方法中进行如下测试

@SpringBootApplication
public class Main {
  public static void main(String[] args) {
    ConfigurableApplicationContext run = SpringApplication.run(Main.class, args);  //该方法会返回SpringBean工厂对象
    Student student = (Student) run.getBean("student");
    System.out.println(student);
  }
}
被动注册

普通Bean未加入到容器中,使用配置类将其加入到容器中

@ConfigurationProperties("student")  //指定前缀,当前未加入容器
public class Student {
  private String name;
  //setter、getter和toString方法省
}

配置类如下

@Configuration
@EnableConfigurationProperties(Student.class)  //若加入多个可使用数组方式
//该注解只能在配置类中使用,并且加入的Bean中必须有@ConfigurationProperties注解
public class MyConfiguration {
}

在入口方法中进行如下测试

@SpringBootApplication
public class Main {
  public static void main(String[] args) {
    ConfigurableApplicationContext run = SpringApplication.run(Main.class, args);  //该方法会返回SpringBean工厂对象
    Student student = run.getBean(Student.class);  //获取该类的全类名
    System.out.println(student);
  }
}
批量注册

将普通Bean加入到容器,属性是Map集合

@Component  //也可以使用其他加入容器的注解
@ConfigurationProperties("student")  //指定前缀
public class Student {
  private Map map;
}

对应application.properties文件中的内容如下

student.map[key1] = value1
student.map[1] = 1

在入口方法中进行如下测试

@SpringBootApplication
public class Main {
  public static void main(String[] args) {
    ConfigurableApplicationContext run = SpringApplication.run(Main.class, args);
    Student student = (Student) run.getBean("student");
    System.out.println(student);
  }
}
配置提示

默认情况下,自定义配置绑定在IDEA中是没有代码提示的,若想要代码提示,首先将下面依赖加入到pom.xml中,重启程序,版本由SpringBoot控制即可

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-configuration-processor</artifactId>
  <optional>true</optional>  <!--保证optional为true才能开启-->
</dependency>

再将下面配置加入到SpringBoot打包插件中,打包时将该工具排除在外

<configuration>
  <excludes>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
  </excludes>
</configuration>
配置导入
XML配置导入

使用@ImportResource注解可向配置类中导入之前使用XML方式的配置文件的配置,比如

@Configuration
@ImportResource("classpath:applicationContext.xml")
public class MyConfiguration {
}
Bean直接导入

使用@Import注解可向配置类中导入一些Bean,使用该方式导入的BeanID默认是这些Bean的全类名,调用的是无参构造方法,所以要保证该类有无参构造

@Configuration
@Import(Student.class)  //若导入多个可使用数组方式
public class MyConfiguration {
}
Bean批量导入

使用@Import注解可向配置类中导入一个实现了ImportSelector接口的类,就会调用其中selectImports()方法返回的全类名数组,批量注册这些Bean

class MyImportSelector implements ImportSelector {  //实现ImportSelector接口中的selectImports()方法
  @Override
  public String[] selectImports(AnnotationMetadata importingClassMetadata) {
    //这之中就可以从配置文件中读取到要导入的某些Bean的全类名
    return new String[]{Student.class.getName()};  //返回要导入Bean的全类名
  }
}

@Configuration
@Import(MyImportSelector.class)  //根据MyImportSelector中selectImports()方法返回的Bean的全类名数组进行批量注入
public class MyConfiguration {
}
通用测试代码

在入口方法中进行如下测试

@SpringBootApplication
public class Main {
  public static void main(String[] args) {
    ConfigurableApplicationContext run = SpringApplication.run(Main.class, args);  //该方法会返回SpringBean工厂对象
    Student student = (Student) run.getBean(Student.class.getName());  //获取该类的全类名
    System.out.println(student);
  }
}
条件注入
内置条件注解
条件注解 描述
@ConditionalOnProperty 当配置文件中有指定的配置时注入,name属性指定key,havingValue属性指定value,matchIfMissing属性指定当无配置时自动注入
@ConditionalOnBean 当IOC容器内存在指定的Bean时注入
@ConditionalOnMissingBean 当IOC容器内不存在指定的Bean时注入
@ConditionalOnWebApplication 当前项目是Web项目时注入
@AutoConfigureAfter 当前Bean需要在该注解指定的Bean注入后才能注入
@AutoConfigureBefore 当前Bean需要在该注解指定的Bean注入前才能注入
@AutoConfigureOrder 指定Bean注入的顺序
@ConditionalOnClass 当前classpath下存在某个类时注入
@ConditionalOnMissingClass 当前classpath不存在某个类时注入
@ConditionalOnExpression 基于SpELl表达式的判断条件
自定义条件注解

使用@Conditional注解和Condition接口配合生成自定义条件注解,具体如下

class MyCondition implements Condition {  //实现Condition接口的matches方法
  @Override
  public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    //可根据context获取容器对象进行逻辑判断,常用的有如下两条:
    //    context.getEnvironment()获取application.properties配置文件,再调用getProperty()方法即可获取具体某个配置
    //    context.getBeanFactory()获取Bean工厂
    //也可根据注解的获取到信息进行逻辑判断 
    return true;  //返回true才会被注入到IOC容器中
  }
}

@Configuration
public class MyConfiguration {
  @Bean
  @Conditional(MyCondition.class)  //将条件类设置到条件注解上,只有当MyCondition类中的matches方法返回true才会被注入
  public Student student() {  //该Bean在IOC容器中的ID就是该方法名
    return new Student("zhangsan");
  }
}

自动配置原理

自动配置就是为了让第三方的配置类加载到IOC容器中,从而得到了自动配置的效果

  • 这些配置类都是以用户的配置类优先的,也就是说用户配置了底层的自动配置类就会失效
  • 这些配置类大部分都绑定了配置文件,也可以通过修改配置文件进行配置的修改
基于源码的理解
  1. 从入口类的main()方法开始,进入SpringApplication.run()方法中(2.4.1版本进入三层)调用了this.refreshContext(context)方法用来刷新容器,从而开始扫描入口类上的@SpringBootApplication注解
@SpringBootApplication
public class Main {
  public static void main(String[] args) {
    SpringApplication.run(Main.class, args);
  }
}
  1. 扫描到@SpringBootApplication注解,该注解中包含下面三个主要注解
//省略...
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
                                 @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class} )}
)
public @interface SpringBootApplication {
//省略...
}
  • @SpringBootConfiguration:表明该类是一个配置类,进入该注解可以看到,该注解其实就是@Configuration注解

  • @ComponentScan:指定包扫描的注解,只是扫描,下面默认两个扫描器,默认扫描的是主程序所在的包以及所有子包

  • @EnableAutoConfiguration:进入该注解可以看到,又包含下面两个注解

    //省略...
    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
    //省略...
    }
    
    • @AutoConfigurationPackage:进入该注解可以看到,又包含注解@Import(AutoConfigurationPackages.Registrar.class),进入AutoConfigurationPackages.Registrar.class类中可以看到实现了registerBeanDefinitions()方法

      //省略...
      @Override
      public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
        //该方法表示导入主程序所在的包以及所有子包中所有的Bean
      }
      //省略...
      
    • @Import(AutoConfigurationImportSelector.class):进入AutoConfigurationImportSelector类中可以看到该类实现了selectImports()方法

      //省略...
      @Override
      public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
          return NO_IMPORTS;
        }
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
      }
      //省略...
      
      1. selectImports()方法又调用getAutoConfigurationEntry()方法获取自动配置类
      2. getAutoConfigurationEntry()方法中又调用了 getCandidateConfigurations()方法获取所有候选配置类,进行处理返回需要加载的自动配置类
      3. getCandidateConfigurations()方法又调用了SpringFactoriesLoader.loadFactoryNames()方法获取候选配置的List集合
      4. SpringFactoriesLoader.loadFactoryNames()中又调用了loadSpringFactories()方法加载配置文件
      5. loadSpringFactories()中又调用了classLoader.getResources(FACTORIES_RESOURCE_LOCATION)方法加载配置文件
      6. FACTORIES_RESOURCE_LOCATION常量值为META-INF/spring.factories配置文件
      7. META-INF/spring.factories配置文件出现在spring-boot-autoconfigure-xxx.jar中,这之中内置了大部分场景的自动配置类的全类名;当然若引入其他场景也有对应的spring-boot-xxx-autoconfigure-xxx.jar包,其中的META-INF/spring.factories配置文件和自动配置的注解
手动实现自动配置

假设Student是自动配置类

class MyImportSelector implements ImportSelector {
  @Override
  public String[] selectImports(AnnotationMetadata importingClassMetadata) {
    //这里可以从文件中读取配置类的全类名
    return new String[]{Student.class.getName()};
  }
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MyImportSelector.class)  //使用@Import注解批量导入Bean
@interface MyEnableAutoConfiguration {  //自定义@MyEnableAutoConfiguration注解导入配置类
}


@MyEnableAutoConfiguration
public class Main {
  public static void main(String[] args) {
    ConfigurableApplicationContext run = new SpringApplicationBuilder(Main.class)
        .web(WebApplicationType.NONE)  //不启动web服务器
        .run(args);
    
    Student student = (Student) run.getBean(Student.class.getName());
    System.out.println(student);
  }
}
此作者没有提供个人介绍
最后更新于 2022-05-24