转载

面试官:同学来帮我实现一个集成c3p0的Starter

前文讲到了SpringBoot如何实现自动配置,SpringBoot的自动配置极大的提升了框架的使用效率。今天我们就来说一说 面试必问 之如何手写starter。

本文以C3P0连接池为基础,实现一个C3P0-Starter。

前言

首先给大家介绍一些SpringBoot默认支持的部分连接池,看看它的内部是怎么实现的,咱们依葫芦画瓢,干他一个Starter。

spring-boot-autoconfigure:SpringBot的自动配置依赖,这一票能不能干成全看这个东西。

咱们直奔主题找到(数据源自动配置类)

org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
复制代码
面试官:同学来帮我实现一个集成c3p0的Starter

看看这些东西是不是感觉到好像有点意思?还没有?那我们接着看。

@Configuration               
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }) 
@EnableConfigurationProperties(DataSourceProperties.class) 
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
		DataSourceInitializationConfiguration.class })   
public class DataSourceAutoConfiguration {   
	@Configuration
	@Conditional(EmbeddedDatabaseCondition.class)
	@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
	@Import(EmbeddedDataSourceConfiguration.class)
	protected static class EmbeddedDatabaseConfiguration {
	}
	@Configuration
	@Conditional(PooledDataSourceCondition.class)
	@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
	@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
			DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class,
			DataSourceJmxConfiguration.class })       
	protected static class PooledDataSourceConfiguration {
	}
复制代码
  • @Configuration Spring配置类等同于.xml

  • @ConditionalOnClass表示只有classpath中能找到DataSource.class和EmbeddedDatabaseType.class时,DataSourceAutoConfiguration这个类才会被spring容器实例化。我们有时候只需要在pom.xml添加某些依赖包,某些功能就自动打开了,正是这个注解帮我们处理了。

  • @EnableConfigurationProperties引入了DataSourceProperties的配置。这是个好东西以后写框架一定能用上,看看代码吧。

@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
	private ClassLoader classLoader;
	private String name;
	private boolean generateUniqueName;
	private Class<? extends DataSource> type;
	private String driverClassName;
	private String url;
	private String username;
	private String password;
	private String jndiName;
	private DataSourceInitializationMode initializationMode = DataSourceInitializationMode.EMBEDDED;
	private String platform = "all";
	private List<String> schema;
	private String schemaUsername;
	private String schemaPassword;
	private List<String> data;
	private String dataUsername;
	private String dataPassword;
	private boolean continueOnError = false;
	private String separator = ";";
	private Charset sqlScriptEncoding;
复制代码

看到这里你应该知道咱们平时在.yml或.properties里面的配置项是怎么来的以及它注入到哪些属性上了吧。

  • @ConfigurationProperties 将配置文件中的配置,以属性的形式自动注入到实体中,要特别说明的一个注属性 ignoreUnknownFields = false 这个超好用,自动检查配置文件中的属性是否存在,不存在则在启动时就报错。

  • @Import 默认引入了DataSourceConfiguration的几个内部类 Hikari、Tomcat、Dbcp2、Generic

/**
	 * Tomcat Pool DataSource configuration.
	 */
	@ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
	@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "org.apache.tomcat.jdbc.pool.DataSource", matchIfMissing = true)
	static class Tomcat extends DataSourceConfiguration {
		@Bean
		@ConfigurationProperties(prefix = "spring.datasource.tomcat")
		public org.apache.tomcat.jdbc.pool.DataSource dataSource(
				DataSourceProperties properties) {
			org.apache.tomcat.jdbc.pool.DataSource dataSource = createDataSource(
					properties, org.apache.tomcat.jdbc.pool.DataSource.class);
			DatabaseDriver databaseDriver = DatabaseDriver
					.fromJdbcUrl(properties.determineUrl());
			String validationQuery = databaseDriver.getValidationQuery();
			if (validationQuery != null) {
				dataSource.setTestOnBorrow(true);
				dataSource.setValidationQuery(validationQuery);
			}
			return dataSource;
		}
	}
	/**
	 * Hikari DataSource configuration.
	 */
	@ConditionalOnClass(HikariDataSource.class)
	@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource", matchIfMissing = true)
	static class Hikari extends DataSourceConfiguration {
		@Bean
		@ConfigurationProperties(prefix = "spring.datasource.hikari")
		public HikariDataSource dataSource(DataSourceProperties properties) {
			HikariDataSource dataSource = createDataSource(properties,
					HikariDataSource.class);
			if (StringUtils.hasText(properties.getName())) {
				dataSource.setPoolName(properties.getName());
			}
			return dataSource;
		}

	}
	/**
	 * DBCP DataSource configuration.
	 */
	@ConditionalOnClass(org.apache.commons.dbcp2.BasicDataSource.class)
	@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "org.apache.commons.dbcp2.BasicDataSource", matchIfMissing = true)
	static class Dbcp2 extends DataSourceConfiguration {
		@Bean
		@ConfigurationProperties(prefix = "spring.datasource.dbcp2")
		public org.apache.commons.dbcp2.BasicDataSource dataSource(
				DataSourceProperties properties) {
			return createDataSource(properties,
					org.apache.commons.dbcp2.BasicDataSource.class);
		}

	}
	/**
	 * Generic DataSource configuration.
	 */
	@ConditionalOnMissingBean(DataSource.class)
	@ConditionalOnProperty(name = "spring.datasource.type")
	static class Generic {
		@Bean
		public DataSource dataSource(DataSourceProperties properties) {
			return properties.initializeDataSourceBuilder().build();
		}

	}
复制代码
  • @ConditionalOnMissingBean(HikariDataSource.class) 这里的Hikari是SpringBoot的亲儿子所以是默认支持的,在此之前还没有实例化HikariDataSource,往下走(如果我们在应用中主动实现了datasource,那默认的hikari就不会再实现了。

  • @ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource", matchIfMissing = true) 当配置 spring.datasource.type=com.zaxxer.hikari.HikariDataSource或该配置不存在时则程序继续执行。

给大家送上一张条件注解归总图,不熟悉的话可以作为参考,无需多言看我操作。

面试官:同学来帮我实现一个集成c3p0的Starter

V-LoggingTool

号外!号外!天啦噜~ 隔壁程序员又写一堆Bug了,大家赶紧去吐槽啊!

面试官:同学来帮我实现一个集成c3p0的Starter

代码已经托管在Github平台: V-LoggingTool 。 欢迎大家 start/fork !:star:

这个项目主要目的是供大家学习如何 手写SpringBoot的StarterAOP日志场景处理 、SpringBoot 异步技术 等。同时该类库也可以快速开发小项目的审计日志功能,提供给需要的同学相关的使用方法,达到 开箱即用 的目的,详情请看Readme。

拉回来,下面咱么接着唠!

目的: 手写C3P0的Starter !别忘了咱们是要实现这个东西。

第一步,引入依赖库

<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <mybatis-plus.version>3.3.2</mybatis-plus.version>
    </properties>
    <dependencies>
         <!--------------------------------------------------------->
         <!--   自动配置,那些条件注解才可以使用,最好设置为 optional 为 true 不往下传递-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
           <!-- C3P0连接池 -->
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.3</version>
        </dependency>
          <!---------------------------可要可不要------------------------------>
         <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
复制代码

其中 spring-boot-autoconfigure 和c3p0是必须引入的,对于其他的依赖我不再强调,我觉得你应该知道是干什么的,哈哈。我的代码里面用到了@Slf4j这个是使用的web的starter里面的log实现,大家可以忽略。

第二步实现配置类

@Slf4j
@Configuration
@ConditionalOnClass(DataSource.class)
@EnableConfigurationProperties(C3P0DataSourceProperties.class)
public class C3p0DataSourceAutoConfiguration {

    @Autowired
    C3P0DataSourceProperties dataSourceProperties;

    @Bean("c3p0Pool")
    @ConditionalOnMissingBean
    public DataSource dataSource() throws Exception {
        log.info("Start initializing c3p0 connection pool......");
        // 创建一个 c3p0 的连接池
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass(dataSourceProperties.getDriver());
        dataSource.setJdbcUrl(dataSourceProperties.getJdbcUrl());
        dataSource.setUser(dataSourceProperties.getUsername());
        dataSource.setPassword(dataSourceProperties.getPassword());
        // 池大小配置
        dataSource.setAcquireIncrement(dataSourceProperties.getAcquireIncrement());
        dataSource.setInitialPoolSize(dataSourceProperties.getInitialPoolSize());
        dataSource.setMinPoolSize(dataSourceProperties.getMinPoolSize());
        dataSource.setMaxPoolSize(dataSourceProperties.getMaxPoolSize());
       
        dataSource.setMaxIdleTime(dataSourceProperties.getMaxIdleTime());
        dataSource.setMaxStatements(dataSourceProperties.getMaxStatements());
        dataSource.setIdleConnectionTestPeriod(dataSourceProperties.getIdleConnectionTestPeriod());
        dataSource.setAcquireRetryDelay(dataSourceProperties.getAcquireRetryDelay());
        dataSource.setAcquireRetryAttempts(dataSourceProperties.getAcquireRetryAttempts());
        dataSource.setBreakAfterAcquireFailure(dataSourceProperties.isBreakAfterAcquireFailure());
        dataSource.setTestConnectionOnCheckout(dataSourceProperties.isTestConnectionOnCheckout());
        log.info("Initialization of c3p0 connection pool completed");
        return dataSource;
    }
}
复制代码

第三步配置注入类

@ConfigurationProperties(prefix = "c3p0")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class C3P0DataSourceProperties {
    /**
     * 默认设置如下配置项并提供默认值
     */
    private String driver = "com.mysql.jdbc.Driver";
    private String jdbcUrl = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=Hongkong";
    private String username = "root";
    private String password = "root";
    private int minPoolSize = 2;
    private int initialPoolSize = 10;
    private int maxPoolSize = 50;
    private int acquireIncrement = 5;
    private int maxIdleTime = 1800000;
    private int maxStatements = 1000;
    private int idleConnectionTestPeriod = 60;
    private int acquireRetryAttempts = 30;
    private int acquireRetryDelay = 1000;
    private boolean breakAfterAcquireFailure = false;
    private boolean testConnectionOnCheckout = false;

}
复制代码

第四步配置文件

请在resource下的META-INF创建 spring.factories

#启动自动配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.xianglei.config.C3p0DataSourceAutoConfiguration
复制代码

spring.factories解释:

面试官:同学来帮我实现一个集成c3p0的Starter

完事!

剩下的大家可以自己测试一下我就不再赘述了。

Spring官方强烈建议非官方Starter命名应遵循{name}-spring-boot-starter的格式。

总结一下,spring-boot是通过spring-boot-autoconfigure体现了"约定优于配置"的原则,而spring-boot-autoconfigure主要用到了spring.factories和几个常用的条件注解来实现自动配置。

手写starter一共四步。

  • 引依赖:主要是 spring-boot-autoconfigure

  • 写自动配置类

  • 配置项注入类:这一步大家也可以用@Value(“xxx:xxx”)注入参数的方式实现。看个人喜好。

  • 添加配置文件

各位小伙伴,更多代码细节请光顾我的 GitHub仓库 ,如有疑问欢迎大家提出 Issue ,想成为 Collaborator 大家可以私信我~ :mailbox:

原文  https://juejin.im/post/5ef8bdcdf265da22cc284ed3
正文到此结束
Loading...