玩转 Spring Boot 原理篇(自动装配前凑之自定义Stater)

2022-04-12 14:41:42 浏览数 (1)

0.

0.0. 历史文章整理

玩转 Spring Boot 入门篇

玩转 Spring Boot 集成篇(MySQL、Druid、HikariCP)

玩转 Spring Boot 集成篇(MyBatis、JPA、事务支持)

玩转 Spring Boot 集成篇(Redis)

玩转 Spring Boot 集成篇(Actuator、Spring Boot Admin)

玩转 Spring Boot 集成篇(RabbitMQ)

玩转 Spring Boot 集成篇(@Scheduled、静态、动态定时任务)

玩转 Spring Boot 集成篇(任务动态管理代码篇)

玩转 Spring Boot 集成篇(定时任务框架Quartz)

玩转 Spring Boot 原理篇(源码环境搭建)

玩转 Spring Boot 原理篇(核心注解知多少)

0.1. Spring Boot 自动装配原理前凑之自定义Starter

坊间 Spring Boot 如此受宠,自动装配的架构设计则功不可没。

为了清晰理解 Spring Boot 自动装配的原理,本次一起自定义一个 Spring Boot Starter,先从代码层面感受一下自动装配的能力。

缺少任何场景的代码实现都是耍流氓,假定一个场景,定义一个猜数字游戏的服务,然后借助自动装配来实现猜数字游戏。

俗话说:照着葫芦画个瓢。所以不着急去实现,咱们先找一个可以参考的葫芦,然后照着画个瓢。

1. 找到葫芦

以 mybatis-spring-boot-starter 启动依赖作为葫芦来参考。

代码语言:javascript复制
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.1</version>
</dependency>

项目中引入 MyBatis 启动依赖后,可以看到 mybatis-spring-boot-starter 包中并没有任何源代码,只有一些配置文件。

而在 mybatis-spring-boot-starter 的 pom 文件中可以看出,mybatis-spring-boot-starter 包会自动引入 mybatis-spring-boot-autoconfigure 以及 mybatis 相关依赖包。

此时,重点关注 mybatis-spring-boot-autoconfigure 包的内容。

先瞅瞅 META-INF/spring.factories 文件内容,配置 mybatis 自动配置的包名类名。

再瞅瞅 MybatisAutoConfiguration 自动配置类。

代码语言:javascript复制
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean {

  @Bean
  @ConditionalOnMissingBean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    // ... ...
  }

  @org.springframework.context.annotation.Configuration
  @Import(AutoConfiguredMapperScannerRegistrar.class)
  @ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
  public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {

    @Override
    public void afterPropertiesSet() {
      logger.debug(
          "Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
    }
  }
}
  • @Configuration 注解的类可以看作 Bean 实例的工厂,能生产让 SpringIoC 容器管理的 Bean。
  • @Bean 注解告诉 Spring,一个带有 @Bean 的注解方法将返回一个对象,该对象应该被注册到 Spring 容器中。
  • @ConditionalOnClass:某个class位于类路径上,才会实例化这个Bean。
  • @ConditionalOnBean:仅在当前上下文中存在某个bean时,才会实例化这个Bean。
  • @ConditionalOnSingleCandidate类似与@ConditionalOnBean。

MybatisAutoConfiguration 类能自动生成 SqlSessionFactory、SqlSessionTemplate 等 MyBatis 的重要实例并交给 Spring 容器管理,从而完成 Bean 的自动注册。

通过葫芦,想自定义 spring boot starter 大体要实现如下操作:

  • 提供 XxxAutoConfiguration 自动配置类
  • 提供 META-INF/spring.factories 配置文件

话不多说,我们开始自定义 Spring Boot Starter。

2. 猜数字游戏 stater 实现

2.1 项目结构

2.2 pom.xml 依赖

代码语言:javascript复制
<?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>org.example</groupId>
    <artifactId>game-spring-boot-starter</artifactId>
    <version>2.6.3</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.3</version>
    </parent>
    
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
    </dependencies>
</project>

2.3 定义猜数字游戏服务 Service

代码语言:javascript复制
package org.growup.starter;

/**
 * 猜数游戏Service
 **/
public class GameService {

    public GameService() {
    }

    public String guess(int number) {
        int gen = (int) (Math.random() * 1000);
        return gen == number ? gen   "猜对了,加鸡腿!" : "哦,猜错了,数字为:"   gen   ",选择真心话 or 大冒险?";
    }
}

2.4 定义自动配置类(这一处关键)

代码语言:javascript复制
package org.growup.starter;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 配置类
 **/
@Configuration
@ConditionalOnProperty(prefix = "game", name = "enabled", matchIfMissing = true)
public class GameAutoConfiguration {

    @Bean
    public GameService gameService() {
        return new GameService();
    }
}

@ConditionalOnProperty(prefix = "game", name = "enabled", matchIfMissing = true)

  • prefix 配置文件中属性的前缀;
  • name 指定属性名;
  • matchIfMissing 在没有指定属性的时候,是否启用 configuration,默认为 false 不启用。

2.5 定义配置文件

代码语言:javascript复制
# -------Game Starter自动装配---------
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.growup.starter.GameAutoConfiguration

在 resources 下创建 META-INF 目录,创建 spring.factories 文件,文件内容配置如上。

2.6 编译打成 jar 包

IDEA 中 Maven -> install 生成 jar 文件。

3. 猜数字游戏服务端安实现

3.1 项目结构

3.2 引入 pom 依赖

引入自定义的 game-spring-boot-starter-2.6.3.jar 启动依赖包。

代码语言:javascript复制
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>game_demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>game_demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.growup</groupId>
            <artifactId>game-spring-boot-starter</artifactId>
            <version>2.6.3</version>
            <systemPath>/Users/codeonce/growup/springboot/game_demo/lib/game-spring-boot-starter-2.6.3.jar</systemPath>
        </dependency>
    </dependencies>

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

</project>

3.3 配置

在 application.properties 文件中开启配置。

代码语言:javascript复制
game.enabled=true

3.4 定义 Controller

代码语言:javascript复制
package com.example.demo_jpa.controller;

import org.growup.starter.GameService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
public class GameController {

    @Resource
    private GameService gameService;

    @GetMapping("/guess")
    public String say(@RequestParam(name = "number") int number) {
        return gameService.guess(number);
    }
}

3.5 定义游戏服务启动入口

代码语言:javascript复制
package com.example.demo_jpa;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class GameApplication {

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

4. 玩一玩

运行GameApplication,浏览器访问 http://localhost:8080/guess?number=300,效果如下

至此,自定义 Spring Boot 启动依赖就完成了,其主要是 GameAutoConfiguration 配置类的立下的功劳。

5. 例行回顾

本文主要是一起探讨如何完成 Spring Boot 自定义 Starter,从代码层面先感受一下 Spring Boot 自动装配的能力。

Spring Boot 如何实现自动装配的呢?通过本次自定义 Stater,脑海中有一些大胆的猜测,猜测跟 XxxAutoConfiguration 以及 spring.factories 文件有点关系,也大胆的构思的一张图,留了一些空白,相信通过后续的源码解读,会把空白填上。

另外,本篇是 Spring Boot 自动装配的前凑篇,至于是如何实现的呢?下次将顺着主线往下捋,感兴趣的可以顺着我画的这个流程图先自己体会体会自动装配的思想。

一起聊技术、谈业务、喷架构,少走弯路,不踩大坑,会持续输出更多精彩分享,敬请期待!

0 人点赞