上篇文章写了spring boot自动配置原理,现在尝试自己开发一个starter,供给spring boot完成自动配置。
在这里我们就用c3p0连接池为例,c3p0是一个比较老的连接池,在远程仓库也没有对应的starter。所以在这里的目的就是开发一个简单的c3p0的starter,达到的效果就是像使用过的driud连接池一样,在yml文件指定一些配置信息,数据源就可以自动装配好了。
在创建项目之前,先说明一下spring官方对自定义starter命名的规范和约定,spring boot官方的starter的命名规范是“spring-boot-starter-模块名”,如tomcat:spring-boot-starter-tomcat。那么自定义的starter命名规范是“模块名-spring-boot-starter”,如mybatis:mybatis-spring-boot-starter。自定义跟官方的区别就是模块名放在了后面,这样做的目的就是有区别于官方的starter。
打开idea新建一个普通的maven项目,命名为c3p0-spring-boot-starter。
1、pom.xml添加依赖:
<properties> <java.version>1.8</java.version> <spring.boot.version>2.2.4.RELEASE</spring.boot.version> </properties> <dependencies> <!-- 添加c3p0连接池依赖 --> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <!-- 添加自动配置的依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> <version>${spring.boot.version}</version> </dependency> <!-- 将@ConrigurationProperties注解的类属性注入到元数据, 再idea中如果有元数据则可以提供良好的代码智能提示功能 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <version>${spring.boot.version}</version> </dependency> </dependencies>
根据spring boot的约定,创建一个org.c3p0.spring.boot.autoconfigure的package:
在包中创建C3p0DatasourceAutoConfigure类,这个也是c3p0的核心自动配置类。在类之上,也要添加一些注解,首先添加@Configuration标识这是一个配置类,然后就是添加条件注解,如下代码:
@Configuration /** * c3p0里面的数据源的类就是ComboPooledDataSource * 当ComboPooledDataSource存在classpath时,则初始化当前配置类 */ @ConditionalOnClass(ComboPooledDataSource.class) public class C3p0DatasourceAutoConfigure { }
3、读取yml配置文件的配置信息并松散绑定
spring boot是在properties或者yml文件中进行配置的,所以自动配置就要把配置文件中的配置信息绑定到类中,在这里新建一个C3p0DatasourceProperties用于yml的松散绑定。这个类中的属性就是连接池所需要的属性,这里简单列举几条。
/** * 松散绑定,prefix表明在yml的前缀 */ @ConfigurationProperties(prefix = "spring.datasource.c3p0") public class C3p0DatasourceProperties { private String driverClassName; private String url; private String username; private String password; private Integer minPoolSize; private Integer maxPoolSize; private Integer initialPoolSize; public String getDriverClassName() { return driverClassName; } public void setDriverClassName(String driverClassName) { this.driverClassName = driverClassName; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Integer getMinPoolSize() { return minPoolSize; } public void setMinPoolSize(Integer minPoolSize) { this.minPoolSize = minPoolSize; } public Integer getMaxPoolSize() { return maxPoolSize; } public void setMaxPoolSize(Integer maxPoolSize) { this.maxPoolSize = maxPoolSize; } public Integer getInitialPoolSize() { return initialPoolSize; } public void setInitialPoolSize(Integer initialPoolSize) { this.initialPoolSize = initialPoolSize; } }
写完这些后,会发现在C3p0DatasourceProperties上的@ConfigurationProperties注解中有报红,原因是当我们使用这个注解标注一个类的时候,spring默认是不会将这个类纳入ioc容器管理的,除非在类上加入@Component注解,当然还有另外一个方式,就是在配置类上加@EnableConfigurationProperties注解:
@Configuration /** * 当ComboPooledDataSource存在classpath时,则初始化当前配置类 */ @ConditionalOnClass(ComboPooledDataSource.class) /** * 将带有@ConfigurationProperties注解的类纳入spring容器管理 */ @EnableConfigurationProperties(C3p0DatasourceProperties.class) public class C3p0DatasourceAutoConfigure { }
4、编写核心自动配置类
接下来就是要配置数据源了:
@Configuration /** * 当ComboPooledDataSource存在classpath时,则初始化当前配置类 */ @ConditionalOnClass(ComboPooledDataSource.class) /** * 将带有@ConfigurationProperties注解的类纳入spring容器管理 */ @EnableConfigurationProperties(C3p0DatasourceProperties.class) public class C3p0DatasourceAutoConfigure { @Autowired private C3p0DatasourceProperties properties; @Bean /** * 如果容器中不存在Datasource时,则创建Bean实例 */ @ConditionalOnMissingBean public DataSource dataSource(){ try { ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setDriverClass(properties.getDriverClassName()); dataSource.setJdbcUrl(properties.getUrl()); dataSource.setUser(properties.getUsername()); dataSource.setPassword(properties.getPassword()); dataSource.setMinPoolSize(properties.getMinPoolSize()); dataSource.setMaxPoolSize(properties.getMaxPoolSize()); dataSource.setInitialPoolSize(properties.getInitialPoolSize()); return dataSource; } catch (PropertyVetoException e) { e.printStackTrace(); throw new RuntimeException("error. " + e); } } }
spring boot的自动配置原理就是扫描META-INF目录下的spring.factories文件,拿到里面的完整类名并交由spring管理,所以在自定义starter也要有这样一个目录和文件。
在resources目录下新建META-INF目录,里面新建一个spring.factories文件。
spring.factories:
# 斜杠表示换行符
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
org.c3p0.spring.boot.autoconfigure.C3p0DatasourceAutoConfigure
这样,c3p0的starter就算开发完成。
6、打包
starter最终是要打成jar包使用的,如果要在本地使用,那么最简单的方法就是打成jar包后安装在本地仓库中。
在idea中,打开maven视图层,运行项目的install命令,这样前面的所有命令都会执行:
当控制台报时,说明打包并安装成功,可以在其他项目中使用了。
7、测试
新建一个spring boot项目,添加依赖:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- 依赖c3p0的starter --> <dependency> <groupId>edu.nf</groupId> <artifactId>c3p0-spring-boot-starter</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies>
在yml文件中添加一些基本配置
spring: datasource: c3p0: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT&useUnicode=true&characterEncoding=utf-8 username: root password: root min-pool-size: 5 max-pool-size: 20 initial-pool-size: 5
测试数据源:
@SpringBootTest class Ch08ApplicationTests { @Autowired private JdbcTemplate jdbcTemplate; @Test void testDatasource() { ComboPooledDataSource dataSource = (ComboPooledDataSource) jdbcTemplate.getDataSource(); System.out.println("min-pool-size: " + dataSource.getMinPoolSize()); List<Map<String, Object>> list = jdbcTemplate.queryForList("select city_name from city_info limit 0,5"); for (Map<String, Object> map : list) { for (String key : map.keySet()){ System.out.println(map.get(key)); } } } }