前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >玩转 Spring Boot 原理篇(自动装配前凑之自定义Stater)

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

作者头像
一猿小讲
发布2022-04-12 14:41:42
2310
发布2022-04-12 14:41:42
举报
文章被收录于专栏:一猿小讲一猿小讲

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 自动装配的前凑篇,至于是如何实现的呢?下次将顺着主线往下捋,感兴趣的可以顺着我画的这个流程图先自己体会体会自动装配的思想。

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-03-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一猿小讲 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档