创建自定义starter包

创建自定义starter包

一个先行问题

在创建自定义starter包中,让我思考了一个我认为是先行的问题:满足什么特性,实际上才是一个starter包?当然这个问题我思考的不算透彻,但也有了一个简单的答案。我们可以建立一个任意的spring项目,用上所需要的注解,改个starter名字,打成一个包作为依赖引入springboot项目里,依照spring的特性,我们让springboot扫描依赖包的包名,自然就能自动注入依赖里的bean。但我认为这不是一个starter包。starter包是springboot的包,需要体现springboot的自动配置特性。举个例子,springboot的jdcb的starter包,能根据导入的数据库驱动类型来自动配置,这绝不是一个普通的spring应用包会做的。然后还有次要的,当然也是很重要的一点,就是导入的starter包从来都不需要使用者显式配置扫包,这点的实现是利用了springboot的一些特性。因此,会被springboot触发,根据环境来进行自动配置,完成bean的装配与控制反转的包,才是一个starter包。

创建一个基础springboot

先创建一个普通的springboot项目,作为starter,Spring官方的Starter通常命名为spring-boot-starter-{name},所以Spring官方建议非官方Starter项目名称建议遵循{name}-spring-boot-starter格式。这里我创建一个hello-spring-boot-starter项目,功能很简单,就是sayhello。项目包名为com.example

  1. Config会读取配置文件里的name(自己的名字)
  2. PeopleDao根据id从数据库读取name(他人的名字)
  3. HelloService的sayHello方法,会根据自己的名字和他人的名字拼凑sayHello语句
  4. PeopleService的talk方法,会根据id查到他人的名字,通过sayHello方法得到sayHello语句

最后在单元测试里测试一下,没问题,一个简单的,没有controller的springboot项目完成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
@Data
@Configuration
@ConfigurationProperties(prefix = "hello")
public class Config {
private String name;
}


@Repository
public class PeopleDaoImpl implements PeopleDao {

private JdbcTemplate jdbcTemplate;

@Autowired
public PeopleDaoImpl(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

@Override
public String selectNameById(int id) {
return jdbcTemplate.queryForObject("select name from people where id=?", String.class, id);
}
}


@Service
public class HelloServiceImpl implements HelloService {

private Config config;

@Autowired
public HelloServiceImpl(Config config) {
this.config = config;
}

@Override
public String sayHello(String name) {
return "hello " + name + ", my name is " + config.getName();
}
}


@Service
public class PeopleServiceImpl implements PeopleService {

private HelloService helloService;
private PeopleDao peopleDao;

@Autowired
public PeopleServiceImpl(HelloService helloService, PeopleDao peopleDao) {
this.helloService = helloService;
this.peopleDao = peopleDao;
}

@Override
public String talk(int id) {
String name = peopleDao.selectNameById(id);
return helloService.sayHello(name);
}
}


@RunWith(SpringRunner.class)
@SpringBootTest
public class HelloSpringBootStarterApplicationTests {
@Autowired
private PeopleService peopleService;

@Test
public void contextLoads() {
System.out.println(peopleService.talk(1));
}
}
1
2
3
4
5
6
7
8
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
username: root
password: 123456

hello:
name: "aaa"

自动装配

自动装配只是对于使用者,代价就是实现者需要显式声明整个装配过程,并通过各种条件化注解进行控制。所以添加一个bean的装配类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Configuration
@EnableConfigurationProperties(Config.class)
public class HelloAutoConfiguration {

@Bean
@ConditionalOnMissingBean(PeopleService.class)
public PeopleService createPeopleService(HelloService helloService, PeopleDao peopleDao) {
PeopleService service = new PeopleServiceImpl(helloService, peopleDao);
System.out.println("创建-PeopleService");
return service;
}

@Bean
@ConditionalOnMissingBean(HelloService.class)
public HelloService createHelloService(Config config) {
HelloService service = new HelloServiceImpl(config);
System.out.println("创建-HelloService");
return service;
}

@Bean
@ConditionalOnMissingBean(PeopleDao.class)
public PeopleDao createPeopleDao(JdbcTemplate jdbcTemplate) {
PeopleDao service = new PeopleDaoImpl(jdbcTemplate);
System.out.println("创建-PeopleDao");
return service;
}
}

这里解释一下各个注解:
1. @Configuration表明这个是一个装配bean的类,并将改类作为bean注册,因为@Configuration本身包含了@Component
2. @EnableConfigurationProperties开启属性配置,并指定从配置文件里读取配置的类Config,springboot扫描到这个注解,会将配置文件装配到Config类里,然后注册为bean。这也是为什么createHelloService方法能注入Config的原因。至于怎样不给spring指定包名有扫到这个注解和类,下面会讲到。
3. @ConditionalOnMissingBean(clazz)只有当clazz没有被注册为bean时,才进行装配。这个注解可以试试注释掉,然后跑起项目就会报错。因为例如在PeopleServiceImpl类已经有@Service,已经注册了,这里再注册就会报错。自动装配少不了各种条件化注解,但这个demo比较简单,就没去做两种接口实现,根据条件进行二选一进行自动装配的效果了。

spring.factories文件

创建resources/META-INF/spring.factories文件,将上面的配置类HelloAutoConfiguration配置进去。这是springboot的特性,springboot会去读取spring.factories文件,然后加载装配这里面配置的配置类。

1
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.hellospringbootstarter.config.HelloAutoConfiguration

打包

springboot生产的jar包是可执行jar,导入是引入不了依赖的,为了生产普通的jar包,需要包springboot的maven打包插件注释掉再打包,安装到本地。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?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.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>hello-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>hello-spring-boot-starter</name>
...

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

</project>

引入自定义的starter包

来新建一个新的普通项目,为了避免扫描影响,新项目包名为net.example。导入依赖,建个controller用一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?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.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>net.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-starter-test</artifactId>
<scope>test</scope>
</dependency>


<dependency>
<groupId>com.example</groupId>
<artifactId>hello-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>

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

</project>
1
2
3
4
5
6
7
8
9
10
11
@RestController
@RequestMapping("")
public class Controller {
@Autowired
private PeopleService peopleService;

@GetMapping("/")
public String sayHello(int id) {
return peopleService.talk(id);
}
}

新项目配置,hello.name避开starter的默认配置

1
2
3
4
5
6
7
8
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
username: root
password: 123456

hello:
name: "zzz"

完成


创建自定义starter包
https://cellargalaxy.github.io/posts/框架/16.创建自定义starter包/
作者
cellargalaxy
发布于
2019年8月28日
许可协议