0%

SpringBoot 核心-基础入门

SpringBoot基础入门

优缺点

优点:

  • 创建独立的Spring应用
  • 内嵌web服务器
  • 自动starter依赖,简化构建配置
  • 自动配置Spring以及第三方功能
  • 提供生产级别的监控、健康检查及外部化配置
  • 无代码生成、无需编写XML

缺点:

  • 迭代快,需要时刻关注变化
  • 封装太深,内部原理复杂,不容易精通

入门案例

1. 创建maven工程

2. 引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.zephon</groupId>
<artifactId>HelloWorld</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.3</version>
</parent>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

</project>

3. 创建主程序

1
2
3
4
5
6
7
8
9
/**
* 主程序类
*/
@SpringBootApplication // 说明这是一个SpringBoot应用
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}

4. 编写业务

1
2
3
4
5
6
7
@RestController
public class HelloController {
@RequestMapping("/hello")
public String handle01(){
return "Hello SpringBoot";
}
}

5. 测试

直接运行main方法即可

6. 简化配置

编写application.properties

1
2
# 修改服务端口号
server.port=8888

其它可配置内容

7. 简化部署

在maven配置中添加:

1
2
3
4
5
6
7
8
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
  • mvn clean
  • mvn package
  • java -jar HelloWorld-1.0-SNAPSHOT.jar

自动配置原理入门

SpringBoot特点

依赖管理

  • 父项目依赖管理

    • 父项目中引入了几乎所有常用依赖,并且自带一个版本
  • 开发导入starter场景启动器

    • 有很多spring-boot-starter-表示某种场景
    • 只要引入starter,这个场景的所有常规需要的依赖都会自动引入
    • 所有支持的场景
    • *-spring-boot-starter:格式通常表示第三方提供的简化开发的场景启动器
  • 无需关注版本号,自动版本仲裁

    • 引入依赖默认都可以不写版本
    • 引入非版本仲裁的依赖就需要写版本号
  • 可以修改版本号

    • 如果需要使用自己需要的版本,则可以通过配置进行修改

      1. 查看spring-boot-dependencies里面规定的当前依赖的版本用的key
      2. 在当前项目的pom.xml中重写所需版本号
      1
      2
      3
      <properties>
      <mysql.version>5.1.43</mysql.version>
      </properties>

自动配置

  • 自动配好Tomcat
    • 引入Tomcat依赖
    • 配置Tomcat
  • 自动配好SpringMVC
    • 引入SpringMVC全套组件
    • 自动配好SpringMVC常用组件(功能)
  • 自动配好Web常见功能,如字符编码问题
    • dispatcherServlet
    • characterEncodingFilter
    • multipartResolver
  • 默认的包结构
    • 主程序所在的包及其下面的所有子包里面的组件都会被默认扫描到
    • 手动配置则需要:@SpringBootApplication(scanBasePackages = "com.zephon")@ComponentScan("com.zephon")
  • 各种配置拥有默认值
    • 默认配置最终都是映射到某个类上
    • 配置文件的值最终会绑定到某个类上,这个类会在容器中创建对象
  • 按需加载所有自动配置项
    • 父项目依赖中有很多starter
    • 引入哪些场景就会自动配置哪些场景
    • 实际上所有自动配置都在spring-boot-autoconfigure包中

容器功能

组件(bean)添加

  • @Configuration:说明被注解的类是一个配置类,等同于之前的xml配置文件

    • @Beain:给容器中添加组件,以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例,默认是单例的,即使是通过Configuration中的方法调用的,因为会通过一个代理模式调用

    • 属性proxyBeanMethods,默认是true(Full模式),表示获取的Config类实例是被Spring动态代理增强过的代理对象,在获取内部bean时就会保持组件单实例,如果设置为false(Lite模式)则会直接调用对应的实例,在获取内部bean时就不会保持组件单实例

      • 通常,如果创建的组件(bean)只是单纯的直接使用,则通常可以使用Lite模式,加载更快
      • 如果创建的组件(bean)之间存在依赖关系,对应xml配置中的ref,则通常需要使用Full模式,保证其依赖的组件就是窗器中对应的组件
    • 例:

      1
      2
      3
      4
      5
      6
      7
      @Configuration
      public class MyConfig {
      @Bean("custUser") // ()中自定义名字
      public User user01(){
      return new User("zhangsan", 23);
      }
      }
  • 常用的@Component、@Controller@Service@Repository

  • @ComponentScan@Import

    • @Import:写在一个容器中组件的类(配置类、Controller、Service等)上,用于导入组件类,例:@Import({User.class, DBHelper.class})表示给容器中自动创建出这两个类型的组件,默认组件名就是全类名
  • @Conditional:条件装配,表示满足Conditional指定的条件则进行组件注入

    • ConditionalOnBean:当存在Bean时才...

    • ConditionalOnMissingBean:当不存在Bean时才...

    • ConditionalOnClass:当容器中存在某个类时才...

    • ConditionalOnMissingClass:当容器中没有某个类时才...

    • ConditionalOnResource:当项目路径中存在某个资源时才...

    • ConditionalOnJava:当Java版本号符合条件时才...

    • ConditionalOnWebApplication:当应用是Web应用时才...

    • ConditionalOnNotWebApplication:当应用不是Web应用时才...

    • ConditionalOnSingleCondidate:当容器中是单实例或有主实例(用@Primary标注)才...

    • ConditionalOnProperty:当配置了某个属性才...

    • 例:

      1
      2
      3
      4
      5
      6
      7
      8
      @Configuration(proxyBeanMethods = false)
      public class MyConfig {
      @Bean
      @ConditionalOnBean(name="zhangsan_dog")
      public User user01(){
      return new User("zhangsan", 23);
      }
      }

原生配置文件引入

  • @ImportResource:导入原生xml配置文件
    • 例:@ImportResource("classpath:beans.xml")

配置绑定

目的:读取properties文件中的内容并将其封装到JavaBean中,以供随时使用

原始做法:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class getProperties {
public static void main(String[] args) throws FileNotFoundException, IOException {
Properties pps = new Properties();
pps.load(new FileInputStream("a.properties"));
Enumeration enum1 = pps.propertyNames();//得到配置文件的名字
while(enum1.hasMoreElements()) {
String strKey = (String) enum1.nextElement();
String strValue = pps.getProperty(strKey);
System.out.println(strKey + "=" + strValue);
//封装到JavaBean。
}
}
}
  • @ConfigurationProperties

  • @Component+@ConfigurationProperties

    配置文件中:

    1
    2
    user.name=zhangsan
    user.age=20

    配置类:

    1
    2
    3
    4
    5
    6
    @Component
    @ConfigurationProperties(prefix = "user")
    public class User {
    private String name;
    private Integer age;
    }
  • @EnableConfigurationProperties+@ConfigurationProperties

    • @EnableConfigurationProperties(User.class):开启User配置绑定功能,并且将这个Car组件自动注册到容器中,用于无法直接在对应类上添加@Component的情况
    • @ConfigurationProperties:配置文件

自动配置原理入门

引导加载自动配置类

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
  • @SpringBootConfiguration:相当于一个@Configuration,表示当前是一个配置类

  • @ComponentScan:指定包扫描路径

  • @EnableAutoConfiguration

    1
    2
    3
    @AutoConfigurationPackage
    @Import({AutoConfigurationImportSelector.class})
    public @interface EnableAutoConfiguration {
    • @AutoConfigurationPackage:自动配置包

      1
      2
      @Import({AutoConfigurationPackages.Registrar.class}) // 向容器中导入组件
      public @interface AutoConfigurationPackage {
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      // Registrar:向容器中导入一系列组件,实际功能:将指定的包(此处是指MainApplication对应的包)下所有的组件导入
      static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
      Registrar() {
      }

      public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
      AutoConfigurationPackages.register(registry, (String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0]));
      }

      public Set<Object> determineImports(AnnotationMetadata metadata) {
      return Collections.singleton(new PackageImports(metadata));
      }
      }
    • @Import({AutoConfigurationImportSelector.class})

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      // 给容器中批量导入一些组件
      protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
      if (!this.isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
      } else {
      AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
      List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
      configurations = this.removeDuplicates(configurations);
      Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
      this.checkExcludedClasses(configurations, exclusions);
      configurations.removeAll(exclusions);
      configurations = this.getConfigurationClassFilter().filter(configurations);
      this.fireAutoConfigurationImportEvents(configurations, exclusions);
      return new AutoConfigurationEntry(configurations, exclusions);
      }
      }

      要导入的组件由List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);决定:

      1
      2
      3
      4
      5
      6
      protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
      List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
      ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
      Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
      return configurations;
      }

      利用工厂加载得到所有组件:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
      Map<String, List<String>> result = (Map)cache.get(classLoader);
      if (result != null) {
      return result;
      } else {
      Map<String, List<String>> result = new HashMap();

      try {
      Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");

      while(urls.hasMoreElements()) {
      URL url = (URL)urls.nextElement();
      UrlResource resource = new UrlResource(url);
      Properties properties = PropertiesLoaderUtils.loadProperties(resource);
      Iterator var6 = properties.entrySet().iterator();

      while(var6.hasNext()) {
      Map.Entry<?, ?> entry = (Map.Entry)var6.next();
      String factoryTypeName = ((String)entry.getKey()).trim();
      String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
      String[] var10 = factoryImplementationNames;
      int var11 = factoryImplementationNames.length;

      for(int var12 = 0; var12 < var11; ++var12) {
      String factoryImplementationName = var10[var12];
      ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
      return new ArrayList();
      })).add(factoryImplementationName.trim());
      }
      }
      }

      result.replaceAll((factoryType, implementations) -> {
      return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
      });
      cache.put(classLoader, result);
      return result;
      } catch (IOException var14) {
      throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
      }
      }
      }

      META-INF/spring.factories位置加载一个文件,默认扫描当前系统里所有META-INF/spring.factories位置的文件

      其中spring-boot-autoconfigure-2.7.3.jarMETA-INF/spring-autoconfigure-metadata.properties定义了所有在SpringBoot启动时要加载的AutoConfiguration配置类

按需开启自动配置项

  • 虽然上述定义的所有AutoConfiguration在启动时默认全部加载,但根据条件装配规则,最终会按需配置

定制化修改自动配置

  • SpringBoot默认会在底层配置好所有的组件,但如果用户自己配置了则以用户的优先

    • @ConditionalOnMissingBean:当容器中没有时才配置
  • SpringBoot先加载所有自动配置类 xxxxxAutoConfiguration

  • 每个自动配置类按条件进行生效,默认都会绑定配置文件指定的值 xxxxProperties里面,xxxProperties又会和配置文件进行绑定

  • 生效的配置类就会向容器中装配很多组件

  • 只要容器中有这些组件,相当于这些功能就有了

  • 只要有用户自己配置的,就以用户的优先

  • 定制化配置

    • 用户自己通过@Bean替换底层的组件

    • 看对应组件是获取配置文件的哪个值,就去修改

      如:server.servlet.encoding.charset=gbk

最佳实践

  • 引入场景依赖
  • 查看自动配置了哪些
    • 自己分析
    • 在配置文件中添加debug=true开启自动配置报告
  • 是否需要修改
    • 参照文档修改配置项
    • 自定义加入或替换组件
    • 自定义器 xxxxCustomizer
    • ......

开发技巧

Lombok

简化JavaBean开发

1. 引入依赖
1
2
3
4
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
2. 安装lombok插件
3. 使用lombok
  • @Data:自动生成对应类的gettersetter方法
  • @ToString:自动生成toString()方法
  • @NoArgsConstructor:生成无参构造器
  • @AllArgsConstructor:生成所有参数构造器
  • @EqualsAndHashCode:生成equals()hashcode()方法
  • @Slf4j:注入日志类,之后就可以直接用log.info()等打印日志了

dev-tools

自动重启

1.引入依赖
1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
2. 在每次更新内容后,Ctrl+F9(Build Project),或设置成自动编译

Spring Initailizer