https://github.com/monkeychen/xspring
xspring是个组件集,后续会不断增加新的通用组件,本文所介绍的动态多数据源组件位于xspring项目的xspring-data模块中,其maven坐标如下(尚未上传至maven中央库):
<dependency>
<groupId>org.xspringgroupId>
<artifactId>xspring-dataartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
本框架是在spring框架提供的各种特性的基础上进行开发的,具体如下:
AbstractRoutingDataSource
core组件是所有其他组件的基础,其提供了通用的事件总线、日志管理等功能。其中XspringApplication
是整个框架的启动类,通过调用这个启动类的startup静态方法,并提供您打上@org.springframework.context.annotation.Configuration
注解的启动类来启动您的应用。
框架启动时按如下顺序加载配置文件
* file:./config/env.properties
* classpath:/config/env.properties
* file:./config/xspring.properties
* file:./xspring.properties
* classpath:./config/xspring.properties
* classpath:./xspring.properties
上述配置文件中env.properties
文件中的属性信息支持热加载(即每隔指定时间框架都会读取该文件并更新至SystemProperties),相关源码请参考类EnvironmentInitializer
.
基于Xspring框架的应用启动示例代码如下:
@Configuration
@ImportResource("classpath:/config/customized-beans.xml") // 可以通过ImportResource方式加载定义在XML中的bean信息。
public class DemoApplication {
private ApplicationContext applicationContext;
private DemoService demoService;
public static void main(String[] args) throws Exception {
applicationContext = XspringApplication.startup(DemoApplication.class, args);
demoService = applicationContext.getBean("demoService", DemoService.class);
}
}
XspringApplication
类的startup
方法会创建XspringApplication
实例并调用其run
方法:
public static ConfigurableApplicationContext startup(Class> annotatedClass, String[] args) {
return new XspringApplication().run(annotatedClass, args);
}
在run
方法中,框架会通过SPI方式加载其他组件提供的标注有@Configuration
注解的org.xspring.core.extension.ModuleConfiguration
接口实现类,从而启动其他组件。
SPI声明统一存放在各个组件的classpath:/META-INF/spring.factories
文件中,整个加载过程源码如下:
public ConfigurableApplicationContext run(Class> annotatedClass, String[] args) {
logger.debug("The input arguments is:{}, {}", annotatedClass, args);
AnnotationConfigApplicationContext context = null;
// Load other configurations in spring.factories file
ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
List factoryNames = SpringFactoriesLoader.loadFactoryNames(ModuleConfiguration.class, classLoader);
List moduleConfigClasses = Lists.newArrayList();
moduleConfigClasses.add(XspringConfiguration.class);
if (CollectionUtils.isNotEmpty(factoryNames)) {
for (String factoryName : factoryNames) {
try {
Class factoryClass = ClassUtils.forName(factoryName, classLoader);
moduleConfigClasses.add(factoryClass);
} catch (ClassNotFoundException e) {
logger.warn("Can not find the matched class[{}] in classpath!", factoryName);
}
}
}
if (annotatedClass != null) {
moduleConfigClasses.add(annotatedClass);
}
Class[] configurations = moduleConfigClasses.toArray(new Class[moduleConfigClasses.size()]);
context = new AnnotationConfigApplicationContext(configurations);
context.start();
return context;
}
xspring-core组件还提供了一个基于Google Guava库的事件总线模型,有兴趣的朋友可直接参考
org.xspring.core.eventbus
包下的相关源码。
xspring-data组件的模块定义(启动)类为:XspringDataConfiguration
,其会通过@Import(DataSourceInitializer.class)
方式加载动态数据源初始化配置类。DataSourceInitializer
也是一个@Configurable
注解类,其通过@Bean
的方式定义了datasource
这个动态数据源。
在动态数据源bean创建过程中,组件会通过SPI方式加载DataSourceFactory
这个接口的实现类来获取具体的数据库连接池提供方,框架默认提供了DruidDataSourceFactory
。
您也可以自己提供数据库连接池的实现类(同样定义在各个组件的classpath:/META-INF/spring.factories
文件中),并通过添加org.springframework.core.annotation.Order
注解来指定加载优先级。
DataSourceInitializer
会从属性文件datasource.properties
中加载JDBC配置信息,然后通过DataSourceFactory
实现类创建相关的数据库连接池(真正的数据源)实例。
datasource.properties
文件加载位置如下(先后顺序):
- file:./config/datasource.properties
- classpath:/config/datasource.properties
框架默认提供的Druid数据库连接池所使用的datasource.properties
文件内容大致如下(不同的数据库连接池提供方则会有所差别):
# 数据源个数
xspring.datasource.jdbc.max=2
# 默认数据源编号
xspring.datasource.jdbc.default=1
# 第一个数据源
xspring.datasource.jdbc.1.name=ds_mysql
xspring.datasource.jdbc.1.driverClassName=com.mysql.jdbc.Driver
xspring.datasource.jdbc.1.url=jdbc:mysql://localhost:3306/mooc?useUnicode=true&characterEncoding=UTF8
xspring.datasource.jdbc.1.username=root
xspring.datasource.jdbc.1.password=*****
xspring.datasource.jdbc.1.initialSize=10
xspring.datasource.jdbc.1.minPoolSize=5
xspring.datasource.jdbc.1.maxPoolSize=10
xspring.datasource.jdbc.1.maxWait=60000
xspring.datasource.jdbc.1.poolFilters=stat
# 第二个数据源
xspring.datasource.jdbc.2.name=ds_postgresql
xspring.datasource.jdbc.2.driverClassName=org.postgresql.Driver
xspring.datasource.jdbc.2.url=jdbc:postgresql://localhost:5432/blog
xspring.datasource.jdbc.2.username=postgres
xspring.datasource.jdbc.2.password=*****
xspring.datasource.jdbc.2.initialSize=10
xspring.datasource.jdbc.2.minPoolSize=5
xspring.datasource.jdbc.2.maxPoolSize=10
xspring.datasource.jdbc.2.maxWait=60000
xspring.datasource.jdbc.2.poolFilters=stat
注意:xspring-data组件的动态多数据源目前只支持相同数据库连接池提供方,即只会使用order值最小的
DataSourceFactory
实现类来创建真正的数据源对象。
源码如下:
@Bean
public DataSource dataSource() {
// 加载DataSourceFactory实现类列表,返回的实例已根据@order注解进行升序排序
ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
// 通过SPI方式加载DataSourceFactory这个接口的实现类来获取具体的数据库连接池提供方,框架默认提供了DruidDataSourceFactory。
List dataSourceFactories = SpringFactoriesLoader.loadFactories(DataSourceFactory.class, classLoader);
if (CollectionUtils.isEmpty(dataSourceFactories)) {
throw new BeanCreationException("Not found any DataSourceFactory implementer in class path!");
}
DataSourceFactory dataSourceFactory = dataSourceFactories.get(0);
Map dataSourceMap = dataSourceFactory.loadOriginalDataSources(environment);
if (CollectionUtils.isEmpty(dataSourceMap)) {
throw new BeanCreationException("Fail to load any original DataSource instance!");
}
Map
使用示例见单元测试类:DynamicDataSourceTest, DemoServiceImpl
// DemoServiceImpl源码:
@Component("demoService")
public class DemoServiceImpl implements DemoService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
@TargetDataSource("ds_mysql")
public void printClassroomList() {
List list = jdbcTemplate.queryForList("SELECT * FROM classroom");
System.out.println(list);
}
@Override
@TargetDataSource("ds_postgresql")
public void printUserList() {
List list = jdbcTemplate.queryForList("SELECT * FROM t_user");
System.out.println(list);
}
}
// DynamicDataSourceTest源码(启动配置类):
@Configuration
@ImportResource("classpath:/config/xspring-context-test.xml")
public class DynamicDataSourceTest {
private ApplicationContext applicationContext;
private DemoService demoService;
@Before
public void setUp() throws Exception {
applicationContext = XspringApplication.startup(DynamicDataSourceTest.class, null);
demoService = applicationContext.getBean("demoService", DemoService.class);
}
@Test
public void testDynamicDataSource() throws Exception {
demoService.printClassroomList();
demoService.printUserList();
}
}
自定义的XML配置文件xspring-context-test.xml
内容如下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<context:component-scan base-package="org.xspring" />
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
bean>
beans>
待补充
参考:http://www.importnew.com/17673.html
https://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#resources
file:./config/ -> file:./ -> classpath:/config/ -> classpath:/
如果各个目录下都有相同的配置文件(如application.properties
),则都会被加载进来(即不互斥),但如果多个jar包的相同路径下都存在一样的配置文件,则只会加载第一个匹配的文件(具体由classloader的加载顺序决定);如果每个文件中都包含相同的key,则最左边文件中的key具有最高的优先级,从源码注释也可证明这一点:
// ConfigFileApplicationListener中的描述:
// Note the order is from least to most specific (last one wins)
private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";
// ConfigFileApplicationListener.Loader类获取配置文件加载位置:
private Set getSearchLocations() {
Set locations = new LinkedHashSet();
// User-configured settings take precedence, so we do them first
if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
for (String path : asResolvedSet(
this.environment.getProperty(CONFIG_LOCATION_PROPERTY), null)) {
if (!path.contains("$")) {
path = StringUtils.cleanPath(path);
if (!ResourceUtils.isUrl(path)) {
path = ResourceUtils.FILE_URL_PREFIX + path;
}
}
locations.add(path);
}
}
locations.addAll(
asResolvedSet(ConfigFileApplicationListener.this.searchLocations,
DEFAULT_SEARCH_LOCATIONS));
return locations;
}
转载请注明出处:cloudnoter.com