Java:自定义实现SpringBoot Starter
  ndh0xMjNGcR6 2023年11月02日 30 0



目录

  • 1、自定义Starter
  • 1.1、项目结构
  • 1.2、代码实现
  • 1.3、测试
  • 1.4、打包
  • 2、引用Starter
  • 2.1、项目结构
  • 2.2、引入starter
  • 3、重写默认配置
  • 4、重写默认实现
  • 5、实现`@Enable`注解
  • 5.1、starter项目创建注解
  • 5.2、业务工程使用注解
  • 6、元数据
  • 7、参考文章


1、自定义Starter

1.1、项目结构

$ tree
.
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── example
    │   │           └── demo
    │   │               ├── ReadingConfiguration.java
    │   │               ├── config
    │   │               │   └── ReadingConfig.java
    │   │               └── service
    │   │                   ├── ReadingService.java
    │   │                   └── impl
    │   │                       └── ReadingServiceImpl.java
    │   └── resources
    │       ├── META-INF
    │       │   └── spring.factories
    │       └── application.properties
    └── test
        ├── java
        │   └── com
        │       └── example
        │           └── demo
        │               └── ReadingServiceTests.java
        └── resources
            └── application.yml

1.2、代码实现

pom.xml maven依赖

<?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.7.7</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.example</groupId>
    <artifactId>thinking-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <name>demo</name>
    <description>Demo project for Spring Boot</description>
    <packaging>jar</packaging>

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

    <dependencies>
        <!-- lombok依赖 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </dependency>

        <!-- 自动配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
                <configuration>
                    <!-- 不会编译测试 -->
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <skip>true</skip>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

application.properties 默认值

# 注意:该文件名只能是application.properties
# 实践发现:该文件不能出现,否则业务工程的application.yml文件无法覆盖默认值
# 设置默认的值
reading.type=txt

ReadingConfig.java 配置映射

package com.example.demo.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * 读取application.properties中reading相关的配置
 */
@Data
@ConfigurationProperties(prefix = "reading")
public class ReadingConfig {
    // 类型
    private String type;
}

ReadingService.java 接口

package com.example.demo.service;

public interface ReadingService {
    void reading();
}

ReadingServiceImpl.java 接口实现类

package com.example.demo.service.impl;

import com.example.demo.config.ReadingConfig;
import com.example.demo.service.ReadingService;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ReadingServiceImpl implements ReadingService {
    /**
     * reading相关配置类
     */
    private ReadingConfig readingConfig;

    public ReadingServiceImpl(ReadingConfig readingConfig) {
        this.readingConfig = readingConfig;
    }

    @Override
    public void reading() {
        log.info("reading type: {}", this.readingConfig.getType());
    }
}

ReadingConfiguration.java 自动装配的核心

package com.example.demo;

import com.example.demo.config.ReadingConfig;
import com.example.demo.service.ReadingService;
import com.example.demo.service.impl.ReadingServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableConfigurationProperties(ReadingConfig.class)
// 当存在reading.enable属性,且其值为true时,才初始化该Bean
@ConditionalOnProperty(name = "reading.enable", havingValue = "true")
public class ReadingConfiguration {

    @Autowired
    private ReadingConfig readingConfig;

    // 若当前IOC容器中没有Reading接口实现时,提供一个默认的Reading实现
    @Bean
    @ConditionalOnMissingBean(ReadingService.class)
    public ReadingService readingService() {
        return new ReadingServiceImpl(this.readingConfig);
    }
}

spring.factories 自动装配注册

# 提醒SpringBoot在启动时,自动装配ReadingConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.demo.ReadingConfiguration

1.3、测试

ReadingServiceTests.java

package com.example.demo;


import com.example.demo.service.ReadingService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;


@SpringBootTest(classes = ReadingConfiguration.class)
class ReadingServiceTests {

    @Autowired
    private ReadingService readingService;

    @Test
    void testReadingService() {
        readingService.reading();
    }
}

application.yml

reading:
  enable: true

1.4、打包

将项目安装到本地maven仓库

mvn install

2、引用Starter

新建一个业务工程

2.1、项目结构

$ tree
.
├── pom.xml
└── src
    └── main
        ├── java
        │   └── com
        │       └── example
        │           └── demo
        │               ├── Application.java
        │               └── controller
        │                   └── IndexController.java
        └── resources
            └── application.yml

2.2、引入starter

<!-- 引入自定义starter -->
<dependency>
    <groupId>com.example</groupId>
    <artifactId>thinking-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

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 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.7.7</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>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-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- 引入自定义starter -->
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>thinking-starter</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

        <!-- test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

application.yml

reading:
  type: json
  enable: true

IndexController.java

package com.example.demo.controller;

import com.example.demo.service.ReadingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class IndexController {

    @Autowired(required = false)
    private ReadingService readingService;

    @GetMapping("/")
    public String index(){
        if(this.readingService != null){
            this.readingService.reading();
        }

        return "Hello";
    }
}

Application.java

package com.example.demo;

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

@SpringBootApplication
public class Application {

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

}

3、重写默认配置

修改项目配置文件application.yml

reading:  
    enable: true  
    type: json # 重写starter默认配置

4、重写默认实现

默认提供的实现

public class ReadingConfiguration {
    @Bean
    @ConditionalOnMissingBean(ReadingService.class)
    public ReadingService readingService() {
        return new ReadingServiceImpl(this.readingConfig);
    }
}

若当前IOC容器中没有Reading接口实现时,提供一个默认的Reading实现

@ConditionalOnMissingBean(ReadingService.class)

可以自行实现ReadingService

package com.example.web.service;

import com.example.demo.config.ReadingConfig;
import com.example.demo.service.ReadingService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class MyReadingService implements ReadingService {

    @Autowired
    private ReadingConfig readingConfig;

    @Override
    public void reading() {
        log.info("my reading service start reading... type is {}", this.readingConfig.getType());
    }

}

5、实现@Enable注解

类似的注解

@EnableScheduling
@EnableAsync
@EnableCaching

5.1、starter项目创建注解

创建注解

package com.example.demo.annotation;


import com.example.demo.ReadingSelector;
import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
// @Import作用是往SpringIOC中导入一个类,这里即导入ReadingSelector
@Import(ReadingSelector.class)
public @interface EnableReading {

}

创建 ReadingSelector类取代 ReadingConfiguration

package com.example.demo;
import com.example.demo.config.ReadingConfig;
import com.example.demo.service.ReadingService;
import com.example.demo.service.impl.ReadingServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableConfigurationProperties(ReadingConfig.class)
public class ReadingSelector {

    @Autowired
    private ReadingConfig readingConfig;

    // 当SpringIOC容器中不存在Reading实现时,才往Spring中初始化ReadingService作为Reading接口的实现
    @Bean
    @ConditionalOnMissingBean(ReadingService.class)
    public ReadingService readingService() {
        return new ReadingServiceImpl(this.readingConfig);
    }

}

5.2、业务工程使用注解

修改配置文件 application.yml

reading:
  # enable: true
  type: json

启动类增加注解@EnableReading

package com.example.web;

import com.example.demo.annotation.EnableReading;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@EnableReading
@SpringBootApplication
public class Application {

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

}

6、元数据

元数据描述后,开发者便能通过IDE提示看到此配置属性的默认值/值类型等信息

引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
</dependency>

重新打包,会自动生成spring-configuration-metadata.json

7、参考文章

  1. 分分钟!手写一个自己的springboot starter
  2. 好好地说一次,自定义springboot-starter开发(从配置到单元测试)

项目代码:https://github.com/mouday/spring-boot-demo


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
ndh0xMjNGcR6