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

0. 

36e4ce370d9894e5ad9ff6dacdeca565.png

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 启动依赖作为葫芦来参考。

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.1</version>
</dependency>

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

fb3a769208f95704bf540683e98f1ac9.png

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

edffde8a1119d3e614f6f85f0bf4b79d.png

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

8addfe4a7ac4ad20ed8f0e12dc54f802.png

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

0b4dc0b8b1b45516fda7ede6a19d8840.png

再瞅瞅 MybatisAutoConfiguration 自动配置类。

@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 项目结构

db94c2906e64b8d97695309bd6eff4c7.png

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

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 定义自动配置类(这一处关键)

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 定义配置文件

# -------Game Starter自动装配---------
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.growup.starter.GameAutoConfiguration

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

2.6 编译打成 jar 包

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

7554e860e167f93658b3fb656afa2769.png

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

3.1 项目结构

4938853a77b2e5b1e105a07bf8918351.png

3.2 引入 pom 依赖

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

<?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 文件中开启配置。

game.enabled=true

3.4 定义 Controller

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 定义游戏服务启动入口

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,效果如下

742d2391f5ad760003d5745529651ed6.png

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

5. 例行回顾

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

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

b771d1f5ed31570dcaa34005bcf3e6ad.png

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

40b820d33844bcdfb62aeadac65a6a3c.png

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值