手写SpringBoot Starter(五):深度解析MyBatis Starter,学习大厂设计思想!

深度解析MyBatis Starter设计

手写SpringBoot Starter(五):深度解析MyBatis Starter,学习大厂设计思想!🔬

系列文章第5篇(完结篇)| 共5篇
难度:⭐⭐⭐⭐ | 适合人群:想深入理解SpringBoot的高级开发者


📝 系列回顾

经过前面四篇的学习,我们已经:

  • 第一篇: 理解了Starter的概念和价值
  • 第二篇: 手写了第一个完整的Starter
  • 第三篇: 实现了可插拔功能
  • 第四篇: 添加了配置元数据智能提示

现在我们的Starter已经很完善了,但是…


💭 开场:一次技术分享会

时间: 周五下午
地点: 公司技术分享会

我: “经过一个月的努力,我完成了公司内部的通用Starter,大家可以直接引用使用…”(PPT展示)

架构师老王: “做得不错!不过我有个问题。”

我: “您说。” 😊

老王: “你这个Starter把所有功能都打包在一起了,如果有人只想用你的工具类,不想要自动配置呢?”

我: “这…” 😰(没想到这个问题)

老王: “你看MyBatis的设计,它分了两个包:mybatis-spring-boot-startermybatis-spring-boot-autoconfigure,为什么要这样设计?”

我: “emmm…不知道…” 😓

老王: “回去研究一下大厂的设计思想,下周再聊。”


回到工位,我陷入了沉思…

我: “为什么要拆成两个包?直接一个包不是更简单吗?” 🤔

经过一番研究后: “原来大有讲究!” 💡


🔍 第一问:MyBatis Starter的目录结构

查看Maven仓库

访问: https://mvnrepository.com/artifact/org.mybatis.spring.boot

看到两个包:

📦 mybatis-spring-boot-starter
   └─ 用户引用这个

📦 mybatis-spring-boot-autoconfigure  
   └─ 真正的自动配置在这里

第一反应: “为啥要搞两个包?一个不够吗?” 🤨


下载源码查看

mybatis-spring-boot-starter 目录结构:

mybatis-spring-boot-starter/
├── pom.xml          ← 只有pom.xml!
└── README.md

打开pom.xml:

<project>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <name>mybatis-spring-boot-starter</name>
    
    <dependencies>
        <!-- Spring Boot Starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        
        <!-- Spring Boot Starter JDBC -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        
        <!-- MyBatis Spring Boot AutoConfigure -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
        </dependency>
        
        <!-- MyBatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
        </dependency>
        
        <!-- MyBatis Spring -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
        </dependency>
    </dependencies>
</project>

发现: 只有依赖声明,没有任何Java代码!

恍然大悟: “原来Starter包只是一个依赖聚合器!” 💡


mybatis-spring-boot-autoconfigure 目录结构:

mybatis-spring-boot-autoconfigure/
├── src/main/java/
│   └── org/mybatis/spring/boot/autoconfigure/
│       ├── MybatisAutoConfiguration.java       ← 自动配置类
│       ├── MybatisProperties.java              ← 配置属性类
│       ├── ConfigurationCustomizer.java        ← 配置定制器
│       └── SpringBootVFS.java                  ← VFS实现
├── src/main/resources/
│   └── META-INF/
│       ├── spring.factories                    ← 自动配置注册
│       └── spring-configuration-metadata.json  ← 配置元数据
└── pom.xml

发现: 真正的自动配置代码都在这里!


🎯 第二问:为什么要拆成两个包?

设计哲学:关注点分离

Starter包职责:

📦 mybatis-spring-boot-starter
   └─ 职责:依赖管理
       ├─ 聚合所有需要的依赖
       ├─ 统一版本管理
       └─ 用户只需引入这一个

AutoConfigure包职责:

📦 mybatis-spring-boot-autoconfigure
   └─ 职责:自动配置
       ├─ 包含自动配置类
       ├─ 包含配置属性类
       └─ 包含spring.factories

优势分析

优势1:灵活性

场景: 用户想用MyBatis,但不想要自动配置

<!-- 方式A:引入Starter(自动配置) -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>

<!-- 方式B:只引入核心库(手动配置) -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
</dependency>

如果不拆分: 用户无法选择!


优势2:可维护性

场景: 需要升级MyBatis版本

拆分设计:

<!-- Starter的pom.xml -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.10</version>  ← 只需改这里
</dependency>

不拆分: 自动配置代码和依赖混在一起,改起来容易出错


优势3:复用性

场景: 其他Starter也想用MyBatis的自动配置

<!-- 某个自定义Starter -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
    <!-- 只引入自动配置,不引入全部依赖 -->
</dependency>

如果不拆分: 无法单独复用自动配置逻辑


优势4:符合SpringBoot官方规范

SpringBoot官方建议:

Starter应该只包含依赖,不包含代码。
自动配置应该放在单独的模块中。

官方Starter结构:

spring-boot-starter-web         ← Starter包(只有依赖)
spring-boot-autoconfigure        ← AutoConfigure包(所有自动配置)
    ├─ WebMvcAutoConfiguration
    ├─ DispatcherServletAutoConfiguration
    └─ ...

MyBatis遵循了这个规范!


对比图

【合并设计】一个包
mybatis-spring-boot-starter
├── 依赖声明
├── 自动配置代码
└── 配置元数据
   ↓
优点:简单
缺点:不灵活、难维护、不规范

【分离设计】两个包
mybatis-spring-boot-starter      ← 用户引用
├── 只有依赖声明

mybatis-spring-boot-autoconfigure ← 真正实现
├── 自动配置代码
└── 配置元数据
   ↓
优点:灵活、好维护、符合规范
缺点:多一个包(但值得)

💻 第三问:深入MyBatis自动配置源码

MybatisProperties 配置属性类

位置: org.mybatis.spring.boot.autoconfigure.MybatisProperties

源码(简化版):

package org.mybatis.spring.boot.autoconfigure;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.io.Resource;

/**
 * MyBatis配置属性
 */
@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)
public class MybatisProperties {

    public static final String MYBATIS_PREFIX = "mybatis";

    /**
     * Mapper XML文件位置
     */
    private String[] mapperLocations;

    /**
     * 类型别名包
     */
    private String typeAliasesPackage;

    /**
     * 类型处理器包
     */
    private String typeHandlersPackage;

    /**
     * 检查配置文件是否存在
     */
    private boolean checkConfigLocation = false;

    /**
     * 执行器类型:SIMPLE, REUSE, BATCH
     */
    private ExecutorType executorType;

    /**
     * MyBatis配置文件位置
     */
    private Resource configLocation;

    /**
     * 全局配置
     */
    private Configuration configuration;

    // Getter 和 Setter...
    public String[] getMapperLocations() {
        return mapperLocations;
    }

    public void setMapperLocations(String[] mapperLocations) {
        this.mapperLocations = mapperLocations;
    }

    public String getTypeAliasesPackage() {
        return typeAliasesPackage;
    }

    public void setTypeAliasesPackage(String typeAliasesPackage) {
        this.typeAliasesPackage = typeAliasesPackage;
    }

    // ...其他getter/setter
}

学习点:

  1. 清晰的常量定义: MYBATIS_PREFIX
  2. 详细的字段注释: 每个字段都有说明
  3. 合理的默认值: checkConfigLocation = false
  4. 类型安全: 用枚举ExecutorType而不是String

MybatisAutoConfiguration 自动配置类

位置: org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

源码(简化版):

package org.mybatis.spring.boot.autoconfigure;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;

import javax.sql.DataSource;

/**
 * MyBatis自动配置类
 */
@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration {

    private final MybatisProperties properties;
    private final ResourceLoader resourceLoader;

    public MybatisAutoConfiguration(
            MybatisProperties properties,
            ResourceLoader resourceLoader) {
        this.properties = properties;
        this.resourceLoader = resourceLoader;
    }

    /**
     * 创建SqlSessionFactory
     */
    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        
        // 设置配置文件位置
        if (this.properties.getConfigLocation() != null) {
            factory.setConfigLocation(this.properties.getConfigLocation());
        }
        
        // 设置Mapper位置
        if (this.properties.getMapperLocations() != null) {
            factory.setMapperLocations(
                this.resourceLoader.getResources(this.properties.getMapperLocations())
            );
        }
        
        // 设置类型别名包
        if (StringUtils.hasText(this.properties.getTypeAliasesPackage())) {
            factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
        }
        
        return factory.getObject();
    }

    /**
     * 创建SqlSessionTemplate
     */
    @Bean
    @ConditionalOnMissingBean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        ExecutorType executorType = this.properties.getExecutorType();
        if (executorType != null) {
            return new SqlSessionTemplate(sqlSessionFactory, executorType);
        } else {
            return new SqlSessionTemplate(sqlSessionFactory);
        }
    }
}

关键注解深度解析

注解1:@ConditionalOnClass
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})

作用: 只有当这些类在classpath中存在时,才加载这个配置类

原理:

// SpringBoot会检查
Class.forName("org.apache.ibatis.session.SqlSessionFactory");
// 如果抛异常(类不存在),就不加载配置类

使用场景: 避免在没有MyBatis依赖时加载配置


注解2:@ConditionalOnSingleCandidate
@ConditionalOnSingleCandidate(DataSource.class)

作用: 只有当Spring容器中有且仅有一个DataSource(或有Primary标记的DataSource)时才生效

为什么需要? MyBatis需要数据源,没有数据源或多个数据源会出问题


注解3:@AutoConfigureAfter
@AutoConfigureAfter(DataSourceAutoConfiguration.class)

作用:DataSourceAutoConfiguration之后再执行

为什么? MyBatis依赖DataSource,必须先有数据源再配置MyBatis

配置顺序:

1. DataSourceAutoConfiguration    ← 先配置数据源
2. MybatisAutoConfiguration        ← 再配置MyBatis

注解4:@ConditionalOnMissingBean
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) {

作用: 如果容器中没有SqlSessionFactory,才创建

好处: 允许用户自定义SqlSessionFactory覆盖默认实现


spring.factories 注册文件

位置: META-INF/spring.factories

内容:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

注意: 注册的是autoconfigure包中的配置类,不是starter


🎨 第四问:SpringBoot官方的设计

官方Starter结构

以spring-boot-starter-web为例:

📦 spring-boot-starter-web
   └─ pom.xml(只有依赖声明)
       ├─ spring-boot-starter
       ├─ spring-boot-starter-json
       ├─ spring-boot-starter-tomcat
       ├─ spring-web
       └─ spring-webmvc

📦 spring-boot-autoconfigure
   └─ 所有自动配置类
       ├─ web/
       │   ├─ WebMvcAutoConfiguration
       │   ├─ DispatcherServletAutoConfiguration
       │   └─ ErrorMvcAutoConfiguration
       ├─ data/
       │   ├─ redis/RedisAutoConfiguration
       │   └─ jpa/HibernateJpaAutoConfiguration
       └─ ...上百个自动配置类

特点:

  1. 所有官方Starter都只包含依赖
  2. 所有自动配置统一放在spring-boot-autoconfigure模块
  3. 一个spring.factories注册所有自动配置

为什么官方要统一管理?

优势:

  1. 统一版本: 所有自动配置版本一致
  2. 统一测试: 一次测试覆盖所有配置
  3. 统一发布: 一次发布所有Starter
  4. 避免冲突: 不会出现版本不兼容问题

劣势:

  1. 包体积大: spring-boot-autoconfigure包含所有配置
  2. 升级影响大: 改一个配置需要重新发布整个包

结论: 对于SpringBoot这种基础框架,优势远大于劣势!


🏗️ 第五问:如何选择设计方案?

方案对比

方案A:单包设计(我们目前的)
hello-spring-boot-starter
├── src/main/java/
│   ├── HelloService.java
│   ├── HelloProperties.java
│   └── HelloAutoConfiguration.java
├── src/main/resources/
│   └── META-INF/spring.factories
└── pom.xml

适用场景:

  • ✅ 简单的Starter(1-3个配置类)
  • ✅ 个人项目或小团队
  • ✅ 不需要单独复用自动配置

方案B:两包设计(MyBatis的)
hello-spring-boot-starter
└── pom.xml(只有依赖)

hello-spring-boot-autoconfigure
├── src/main/java/
│   ├── HelloService.java
│   ├── HelloProperties.java
│   └── HelloAutoConfiguration.java
├── src/main/resources/
│   └── META-INF/spring.factories
└── pom.xml

适用场景:

  • ✅ 复杂的Starter(5+个配置类)
  • ✅ 企业级项目
  • ✅ 需要给其他Starter复用
  • ✅ 想遵循SpringBoot官方规范

方案C:统一管理(SpringBoot官方的)
company-spring-boot-autoconfigure
├── module-a/
├── module-b/
└── module-c/

company-spring-boot-starter-a
└── pom.xml

company-spring-boot-starter-b
└── pom.xml

适用场景:

  • ✅ 公司统一技术栈
  • ✅ 多个相关Starter
  • ✅ 需要统一版本管理

决策树

你的Starter是什么情况?
│
├─ 个人学习/简单工具
│  └─ 选择:方案A(单包) ⭐
│
├─ 企业内部使用/功能复杂
│  └─ 选择:方案B(两包) ⭐⭐
│
└─ 公司技术中台/多个Starter
   └─ 选择:方案C(统一管理) ⭐⭐⭐

💻 第六问:改造我们的Starter(方案B)

项目结构改造

创建两个Maven模块:

hello-spring-boot/                    ← 父项目
├── pom.xml                           ← 父pom
├── hello-spring-boot-starter/        ← Starter模块
│   └── pom.xml
└── hello-spring-boot-autoconfigure/  ← AutoConfigure模块
    ├── src/main/java/
    ├── src/main/resources/
    └── pom.xml

父pom.xml

<?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.example</groupId>
    <artifactId>hello-spring-boot</artifactId>
    <version>2.0.0</version>
    <packaging>pom</packaging>

    <name>Hello Spring Boot</name>
    <description>Hello Starter Parent</description>

    <!-- 子模块 -->
    <modules>
        <module>hello-spring-boot-autoconfigure</module>
        <module>hello-spring-boot-starter</module>
    </modules>

    <!-- 继承SpringBoot父项目 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.10</version>
        <relativePath/>
    </parent>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <!-- 依赖管理 -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.example</groupId>
                <artifactId>hello-spring-boot-autoconfigure</artifactId>
                <version>${project.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

autoconfigure模块的pom.xml

<?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>

    <!-- 父项目 -->
    <parent>
        <groupId>com.example</groupId>
        <artifactId>hello-spring-boot</artifactId>
        <version>2.0.0</version>
    </parent>

    <artifactId>hello-spring-boot-autoconfigure</artifactId>
    <name>Hello Spring Boot AutoConfigure</name>
    <description>Auto-configuration for Hello</description>

    <dependencies>
        <!-- SpringBoot核心 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <!-- 配置元数据处理器 -->
        <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-validation</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

</project>

starter模块的pom.xml(关键!)

<?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>

    <!-- 父项目 -->
    <parent>
        <groupId>com.example</groupId>
        <artifactId>hello-spring-boot</artifactId>
        <version>2.0.0</version>
    </parent>

    <artifactId>hello-spring-boot-starter</artifactId>
    <name>Hello Spring Boot Starter</name>
    <description>Starter for Hello</description>

    <dependencies>
        <!-- SpringBoot核心 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <!-- 引入AutoConfigure模块 -->
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>hello-spring-boot-autoconfigure</artifactId>
        </dependency>
    </dependencies>

</project>

注意: Starter模块的pom.xml非常简洁,只包含依赖声明!


代码迁移

将所有Java代码移到autoconfigure模块:

hello-spring-boot-autoconfigure/src/main/java/com/example/autoconfigure/
├── HelloService.java
├── HelloProperties.java
├── HelloAutoConfiguration.java
├── HelloMarker.java
└── EnableHello.java

spring.factories也在autoconfigure模块:

hello-spring-boot-autoconfigure/src/main/resources/META-INF/
└── spring.factories

starter模块什么都不放:

hello-spring-boot-starter/
└── pom.xml  ← 只有这一个文件!

打包发布

# 在父项目目录下执行
cd hello-spring-boot
mvn clean install

会同时打包两个模块:

[INFO] --- Installing hello-spring-boot-autoconfigure-2.0.0.jar ---
[INFO] --- Installing hello-spring-boot-starter-2.0.0.jar ---
[INFO] BUILD SUCCESS

使用方式

用户只需引入starter:

<dependency>
    <groupId>com.example</groupId>
    <artifactId>hello-spring-boot-starter</artifactId>
    <version>2.0.0</version>
</dependency>

Maven会自动传递依赖:

hello-spring-boot-starter
  └─ hello-spring-boot-autoconfigure
      └─ spring-boot-starter

效果和之前完全一样,但结构更专业!


🎓 第七问:企业级Starter最佳实践总结

实践1:命名规范

✅ 推荐命名:
{company}-{module}-spring-boot-starter
{company}-{module}-spring-boot-autoconfigure

示例:
alibaba-druid-spring-boot-starter
alibaba-druid-spring-boot-autoconfigure

❌ 不推荐:
my-starter
custom-boot-starter

实践2:版本管理

<!-- 使用统一的父pom管理版本 -->
<parent>
    <groupId>com.example</groupId>
    <artifactId>company-dependencies</artifactId>
    <version>1.0.0</version>
</parent>

<!-- 或使用dependencyManagement -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>company-bom</artifactId>
            <version>1.0.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

实践3:文档完善

必须提供:

  1. README.md: 快速开始指南
  2. 配置说明: 所有配置项详细说明
  3. 使用示例: 常见场景代码示例
  4. 更新日志: CHANGELOG.md
  5. FAQ: 常见问题解答

示例README.md结构:

# Hello Spring Boot Starter

## 快速开始

### 1. 添加依赖
​```xml
<dependency>
    <groupId>com.example</groupId>
    <artifactId>hello-spring-boot-starter</artifactId>
    <version>2.0.0</version>
</dependency>
​```

### 2. 配置
​```yaml
hello:
  enabled: true
  prefix: Hello
​```

### 3. 使用
​```java
@Autowired
private HelloService helloService;
​```

## 配置项说明

| 配置项 | 类型 | 默认值 | 说明 |
|-------|------|--------|------|
| hello.enabled | boolean | true | 是否启用 |
| hello.prefix | String | Hello | 前缀 |

## 更新日志

### 2.0.0 (2024-01-20)
- 拆分为两个模块
- 添加配置校验

### 1.0.0 (2024-01-01)
- 初始版本

实践4:测试覆盖

必须测试:

  1. 自动配置测试: 验证Bean是否正确注册
  2. 条件注解测试: 验证各种条件是否生效
  3. 配置测试: 验证配置是否正确读取
  4. 集成测试: 在真实项目中测试

示例测试:

@SpringBootTest
@TestPropertySource(properties = {
    "hello.enabled=true",
    "hello.prefix=Test"
})
class HelloAutoConfigurationTest {

    @Autowired(required = false)
    private HelloService helloService;

    @Test
    void testAutoConfiguration() {
        assertNotNull(helloService, "HelloService应该被自动注册");
        assertEquals("Test World !", helloService.sayHello("World"));
    }
}

实践5:向后兼容

升级时要考虑:

  1. 配置项不要轻易删除: 标记为@Deprecated
  2. 默认值不要轻易改: 可能影响现有项目
  3. API不要破坏性变更: 提供兼容层
  4. 提供迁移指南: 告诉用户如何升级

示例:

@ConfigurationProperties(prefix = "hello")
public class HelloProperties {

    private String prefix = "Hello";

    /**
     * @deprecated 使用 prefix 替代
     */
    @Deprecated
    private String oldPrefix;

    public String getPrefix() {
        // 兼容旧配置
        if (prefix == null && oldPrefix != null) {
            return oldPrefix;
        }
        return prefix;
    }
}

实践6:监控与日志

添加启动日志:

@Configuration
public class HelloAutoConfiguration {

    private static final Logger log = LoggerFactory.getLogger(HelloAutoConfiguration.class);

    @Bean
    public HelloService helloService(HelloProperties properties) {
        log.info("Hello Starter 自动配置成功");
        log.info("配置信息: prefix={}, suffix={}", 
            properties.getPrefix(), 
            properties.getSuffix());
        return new HelloService(properties.getPrefix(), properties.getSuffix());
    }
}

添加健康检查(可选):

@Component
public class HelloHealthIndicator implements HealthIndicator {

    @Override
    public Health health() {
        // 检查服务状态
        return Health.up()
            .withDetail("status", "Hello service is running")
            .build();
    }
}

🎉 系列总结

我们学到了什么?

第一篇:Starter基础
  • ✅ Starter是SpringBoot的"套餐"机制
  • ✅ 解决配置地狱问题
  • ✅ 理解命名规范
第二篇:手写Starter
  • ✅ 创建配置属性类
  • ✅ 编写自动配置类
  • ✅ 配置spring.factories
  • ✅ 打包测试
第三篇:可插拔实现
  • ✅ 配置文件开关
  • ✅ 自定义注解开关
  • ✅ 学习Zuul设计
第四篇:配置元数据
  • ✅ 自动生成元数据
  • ✅ 实现智能提示
  • ✅ 配置校验
第五篇:设计思想
  • ✅ Starter和AutoConfigure分离
  • ✅ 深入MyBatis源码
  • ✅ 企业级最佳实践

从入门到精通路线图

Level 1: 入门 ⭐
└─ 能看懂Starter,知道怎么用

Level 2: 初级 ⭐⭐
└─ 能写简单的Starter(单包)

Level 3: 中级 ⭐⭐⭐
└─ 能实现可插拔Starter,添加配置元数据

Level 4: 高级 ⭐⭐⭐⭐
└─ 能设计两包结构,遵循最佳实践

Level 5: 专家 ⭐⭐⭐⭐⭐
└─ 能设计企业级技术中台,统一管理多个Starter

恭喜你,看完这个系列,你已经达到Level 4了! 🎉


💡 下一步学习建议

1. 深入SpringBoot源码

推荐阅读:

  • SpringApplication.run() 启动流程
  • AutoConfigurationImportSelector 自动装配原理
  • @Conditional 条件注解实现原理
  • ConfigurationPropertiesBindingPostProcessor 配置绑定原理

2. 研究优秀开源Starter

推荐研究:

  • MyBatis Starter: 经典的两包设计
  • Druid Starter: 阿里出品,配置丰富
  • PageHelper Starter: 简洁实用
  • Swagger Starter: 文档生成
  • Dubbo Starter: 服务治理

3. 实战项目

建议实现:

  1. 日志Starter: 统一日志格式和输出
  2. Redis Starter: 封装常用Redis操作
  3. OSS Starter: 对象存储统一接口
  4. 短信Starter: 多渠道短信发送
  5. 权限Starter: 基于注解的权限控制

4. 参与开源

  • 给优秀的Starter项目提PR
  • 在GitHub上开源自己的Starter
  • 写技术博客分享经验

🤔 最后的思考题

问题1: 如果你要为公司设计一套统一的技术栈Starter,会包含哪些模块?

问题2: SpringBoot 3.x 和 2.x 的Starter有什么区别?如何做兼容?

问题3: 如何监控Starter的使用情况(哪些项目在用、配置了什么参数)?

欢迎在评论区讨论! 💭


💬 写在最后

从第一篇到现在,我们一起:

  • 👨‍💻 手写了完整的Starter
  • 🔧 实现了可插拔功能
  • 💎 添加了配置元数据
  • 🔬 深入分析了源码
  • 📚 总结了最佳实践

这个系列到这里就完结了!

如果这个系列对你有帮助,请:

  • 👍 点赞支持
  • ⭐ 收藏备用
  • 🔄 转发分享
  • 💬 评论交流

感谢一路陪伴,我们下个系列再见! 👋

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值