作为公司的技术保障部,一直承担着技术方向的把控,最近公司准备全面转入spring boot的开发。所以我们部门也一直在调研相关的技术知识点;
使用springboot开发应用已经有一段时间了,我们都沉醉于它简洁的配置和平滑的上手曲线。
在springboot的开发中,starter是一个核心的配置,只需要引入对应模块的starter,然后在application.properties中引入对应的配置项,就可以开发业务逻辑了。
这一切都归功于springboot的自动配置的能力。
本文会从一个mybatis与spring boot结合的starter开始学习,该框架是我们提供出来,需要使用mybatis的团队只需要引入这个starter的jar包即可使用
新建原型项目
完整的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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>cn.caijiajia</groupId> <artifactId>springboot-parent</artifactId> <version>1.0.0.RELEASE</version> </parent> <groupId>cn.caijiajia</groupId> <artifactId>mybatis-starter</artifactId> <version>${project.release.version}</version> <packaging>jar</packaging> <dependencies> <dependency> <groupId>cn.caijiajia</groupId> <artifactId>framework-starter</artifactId> <version>${project.release.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> </dependency> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-jdbc</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> </dependencies> </project>
开发阶段
1.配置类 DatabaseAutoConfiguration DbProperties
@EnableConfigurationProperties({DbProperties.class}) public class DatabaseAutoConfiguration implements BeanDefinitionRegistryPostProcessor, EnvironmentAware { private static Logger log = LoggerFactory.getLogger(DatabaseAutoConfiguration.class); private Environment environment; @Override public void setEnvironment(Environment environment) { this.environment = environment; } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { DbProperties dbProperties = ConfigUtil.getDbConfig(environment, DbProperties.PROPERTY_PREFIX, DbProperties.class); // TODO 检查参数 if (dbProperties.getDatabase() == null && dbProperties.getDatabases() != null && dbProperties .getDatabases().size() == 1) { dbProperties.setDatabase(dbProperties.getDatabases().get(0)); } initAllDataAccessBean(dbProperties, registry); } protected void initAllDataAccessBean(DbProperties dbProperties, BeanDefinitionRegistry registry) { // 单数据源 if (dbProperties.getDatabase() != null) { initDataAccessBean(dbProperties.getDatabase(), null, registry); } // 多数据源 else if (dbProperties.getDatabases() != null && dbProperties.getDatabases().size() > 0) { for (int i = 0; i < dbProperties.getDatabases().size(); i++) { initDataAccessBean(dbProperties.getDatabases().get(i), i, registry); } } } /** * 注册一个datasource的所有bean 信息 * * @param databaseConfig * @param index * @param registry */ protected void initDataAccessBean(DatabaseConfig databaseConfig, Integer index, BeanDefinitionRegistry registry) { DataSourceConfig dataSourceConfig = databaseConfig.getDataSource(); TransactionConfig transactionConfig = databaseConfig.getTransaction(); MyBatisConfig myBatisConfig = databaseConfig.getMybatis(); LogConfig logConfig = databaseConfig.getLog(); // 给与默认 dataSource bean id: dataSource${index} if (StringUtils.isEmpty(dataSourceConfig.getName())) { if (index == null) { dataSourceConfig.setName("dataSource"); } else { dataSourceConfig.setName("dataSource" + index); } } // 注册datasource registry.registerBeanDefinition(dataSourceConfig .getName(), getTomcatJdbcDataSourceBeanDefinition(dataSourceConfig, logConfig)); // 注册事务 if (transactionConfig != null && transactionConfig.isEnable()) { if (index == null) { registry.registerBeanDefinition("transactionManager", getTransactionManagerBeanDefinition(dataSourceConfig)); } else { registry.registerBeanDefinition("transactionManager" + index, getTransactionManagerBeanDefinition(dataSourceConfig)); } } // 注册mybatis if (myBatisConfig != null) { final String sqlSessionFactoryBeanName = "sqlSessionFactory" + dataSourceConfig.getName(); registry.registerBeanDefinition(sqlSessionFactoryBeanName, getSqlSessionFactoryBeanDefinition(myBatisConfig, dataSourceConfig)); registry.registerBeanDefinition("mapperScannerConfigurer" + dataSourceConfig .getName(), getMapperScannerConfigurerBeanDefinition(myBatisConfig, sqlSessionFactoryBeanName)); // 给与默认 dataSource bean id: sqlSession${index} if (StringUtils.isEmpty(myBatisConfig.getSqlSessionBeanName())) { if (index == null) { myBatisConfig.setSqlSessionBeanName("sqlSession"); } else { myBatisConfig.setSqlSessionBeanName("sqlSession" + index); } } registry.registerBeanDefinition(myBatisConfig .getSqlSessionBeanName(), getSqlSessionBeanDefinition(sqlSessionFactoryBeanName)); } } /** * 获取 tomcat jdbc datasource bean 定义 * * @param dataSourceConfig * @return */ protected AbstractBeanDefinition getTomcatJdbcDataSourceBeanDefinition(DataSourceConfig dataSourceConfig, LogConfig logConfig) { if (dataSourceConfig.getMinIdle() == null) { dataSourceConfig.setMinIdle(dataSourceConfig.getInitialSize()); } if (dataSourceConfig.getMaxIdle() == null) { dataSourceConfig.setMaxIdle(dataSourceConfig.getMaxActive()); } BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder .rootBeanDefinition("org.apache.tomcat.jdbc.pool.DataSource"); definitionBuilder.setDestroyMethodName("close"); // add filter definitionBuilder .addPropertyValue("jdbcInterceptors", "cn.caijiajia.framework.db.log.TomcatJdbcLogFilter(slowLogOn=" + logConfig .isSlowSqlOn() + ",slowSqlThreshold=" + logConfig.getSlowSqlThreshold() + ")"); // if not set, use default value in jdbc driver if (dataSourceConfig.getDefaultAutoCommit() != null) { definitionBuilder .addPropertyValue("defaultAutoCommit", dataSourceConfig.getDefaultAutoCommit()); } if (dataSourceConfig.getDefaultReadOnly() != null) { definitionBuilder .addPropertyValue("defaultReadOnly", dataSourceConfig.getDefaultReadOnly()); } if (dataSourceConfig.getDefaultTransactionIsolation() != null) { definitionBuilder .addPropertyValue("defaultTransactionIsolation", dataSourceConfig.getDefaultTransactionIsolation()); } definitionBuilder.addPropertyValue("url", dataSourceConfig.getUrl()); definitionBuilder.addPropertyValue("username", dataSourceConfig.getUsername()); definitionBuilder.addPropertyValue("password", dataSourceConfig.getPassword()); definitionBuilder.addPropertyValue("driverClassName", dataSourceConfig.getDriverClassName()); // 配置初始化大小、最小、最大 definitionBuilder.addPropertyValue("initialSize", dataSourceConfig.getInitialSize()); definitionBuilder.addPropertyValue("minIdle", dataSourceConfig.getMinIdle()); definitionBuilder.addPropertyValue("maxActive", dataSourceConfig.getMaxActive()); definitionBuilder.addPropertyValue("maxIdle", dataSourceConfig.getMaxIdle()); // 配置获取连接等待超时的时间 definitionBuilder.addPropertyValue("maxWait", dataSourceConfig.getMaxWait()); // 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫 definitionBuilder .addPropertyValue("timeBetweenEvictionRunsMillis", dataSourceConfig.getTimeBetweenEvictionRunsMillis()); // 配置一个连接在池中最小生存的时间,单位是毫秒 definitionBuilder .addPropertyValue("minEvictableIdleTimeMillis", dataSourceConfig.getMinEvictableIdleTimeMillis()); definitionBuilder.addPropertyValue("validationQuery", "select 1"); definitionBuilder.addPropertyValue("testOnConnect", dataSourceConfig.isTestOnConnect()); definitionBuilder.addPropertyValue("testWhileIdle", dataSourceConfig.isTestWhileIdle()); definitionBuilder.addPropertyValue("testOnBorrow", dataSourceConfig.isTestOnBorrow()); definitionBuilder.addPropertyValue("testOnReturn", dataSourceConfig.isTestOnReturn()); definitionBuilder.addPropertyValue("removeAbandoned", dataSourceConfig.isRemoveAbandoned()); definitionBuilder.addPropertyValue("removeAbandonedTimeout", dataSourceConfig.getRemoveAbandonedTimeout()); AbstractBeanDefinition bd = definitionBuilder.getRawBeanDefinition(); bd.setScope(BeanDefinition.SCOPE_SINGLETON); return bd; } /** * 获取mybaits SqlSessionFactory bean 定义 * * @param dataSourceConfig * @return */ protected AbstractBeanDefinition getSqlSessionFactoryBeanDefinition(MyBatisConfig myBatisConfig, DataSourceConfig dataSourceConfig) { BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder .rootBeanDefinition(org.mybatis.spring.SqlSessionFactoryBean.class); definitionBuilder.addPropertyReference("dataSource", dataSourceConfig.getName()); PageInterceptor pageInterceptor = new PageInterceptor(); if (myBatisConfig.getPage().isSupportMethodsArguments()) { Properties pageInterceptorProperties = new Properties(); pageInterceptorProperties.setProperty("supportMethodsArguments", "true"); pageInterceptorProperties.setProperty("params", "pageNum=pageNum;pageSize=pageSize"); pageInterceptor.setProperties(pageInterceptorProperties); } definitionBuilder .addPropertyValue("plugins", new Interceptor[]{new LogInfoInterceptor(), pageInterceptor}); AbstractBeanDefinition bd = definitionBuilder.getRawBeanDefinition(); bd.setScope(BeanDefinition.SCOPE_SINGLETON); return bd; } /** * 获取mybaits MapperScannerConfigurer bean 定义 * * @param myBatisConfig * @param sqlSessionFactoryBeanName * @return */ protected AbstractBeanDefinition getMapperScannerConfigurerBeanDefinition(MyBatisConfig myBatisConfig, String sqlSessionFactoryBeanName) { BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder .rootBeanDefinition(org.mybatis.spring.mapper.MapperScannerConfigurer.class); definitionBuilder .addPropertyValue("sqlSessionFactoryBeanName", sqlSessionFactoryBeanName); definitionBuilder.addPropertyValue("basePackage", myBatisConfig.getBasePackage()); AbstractBeanDefinition bd = definitionBuilder.getRawBeanDefinition(); bd.setScope(BeanDefinition.SCOPE_SINGLETON); return bd; } /** * 获取mybaits SqlSessionTemplate bean 定义 * * @param sqlSessionFactoryBeanName * @return */ protected AbstractBeanDefinition getSqlSessionBeanDefinition(String sqlSessionFactoryBeanName) { BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder .rootBeanDefinition(org.mybatis.spring.SqlSessionTemplate.class); definitionBuilder.addConstructorArgReference(sqlSessionFactoryBeanName); AbstractBeanDefinition bd = definitionBuilder.getRawBeanDefinition(); bd.setScope(BeanDefinition.SCOPE_SINGLETON); return bd; } /** * 获取 DataSourceTransactionManager bean定义 * * @param dataSourceConfig * @return */ protected AbstractBeanDefinition getTransactionManagerBeanDefinition(DataSourceConfig dataSourceConfig) { BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder .genericBeanDefinition(DataSourceTransactionManager.class); definitionBuilder.addConstructorArgReference(dataSourceConfig.getName()); AbstractBeanDefinition bd = definitionBuilder.getRawBeanDefinition(); bd.setScope(BeanDefinition.SCOPE_SINGLETON); return bd; } }
@ConfigurationProperties(prefix = DbProperties.PROPERTY_PREFIX) public class DbProperties { public final static String PROPERTY_PREFIX = "caijiajia.db"; private DatabaseConfig database; private List<DatabaseConfig> databases = new ArrayList(); public DatabaseConfig getDatabase() { return database; } public void setDatabase(DatabaseConfig database) { this.database = database; } public List<DatabaseConfig> getDatabases() { return databases; } public void setDatabases(List<DatabaseConfig> databases) { this.databases = databases; } }
这是两段核心配置类,其它还有一些pojo类,这里就不写出来了
自定义spring.factories
自定义spring.factories的步骤很简单,只需要在src/main/resource目录下创建META-INF目录,并在该目录下添加文件spring.factories,文件内容为:
org.springframework.boot.autoconfigure.EnableAutoConfiguration= cn.caijiajia.framework.db.autoconfigure.DatabaseAutoConfiguration
到这里通过spring boot的自动装配原理实现了配置类的自动装配
一个简单的starter创造就完成了