項目原來已經實現瞭多數據源配置,實現方式為在beans.xml文件中直接配置多個數據源bean,然後在使用數據源時通過HotSwappableTargetSource動態切換數據源(詳細內容請Google)。可領導不滿意,要求隻在屬性文件中配置相應的連接信息,並要求動態數據源除配置的屬性外,其他屬性都繼承系統默認數據源(DataSource)的屬性。然後給出的屬性文件中數據源的格式為:
[plain]
#連接數據庫地址,該地址可動態添加,“link.”之後,“.jdbc”之前的名稱為數據庫連接池的名稱,其餘連接池屬性如果不寫,將自動繼承Hummer的數據庫連接池配置
link.eagle2.business.jdbc.jdbcUrl=jdbc:oracle:thin:@192.168.0.155:1521:orcl
link.eagle2.business.jdbc.user=eagle2
link.eagle2.business.jdbc.password=eagle2_password
link.eagle2.interface.jdbc.jdbcUrl=jdbc:oracle:thin:@192.168.0.155:1521:interface
link.eagle2.interface.jdbc.user=interface
link.eagle2.interface.jdbc.password=interface22
link.eagle2.ods.jdbc.jdbcUrl=jdbc:oracle:thin:@192.168.0.10:1521:sifen
link.eagle2.ods.jdbc.user=honghe
link.eagle2.ods.jdbc.password=honghe_pwd
為實現這個要求,我經過google搜索,寫代碼嘗試,最後終於較好的實現瞭該功能,結果記錄如下:
實現一個實現ApplicationContextAware和ApplicationListener接口的類DynamicDataSourceC3p0,實現ApplicationContextAware是為瞭得到ApplicationContext,實現瞭ApplicationListener是為瞭配置spring的加載事件。
類的實現代碼如下:
[java]
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.ChildBeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.io.support.ResourcePropertySource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class DynamicDataSourceC3p0 implements ApplicationContextAware,ApplicationListener {
private ApplicationContext app;
@Override
public void setApplicationContext(ApplicationContext app)
throws BeansException {
this.app = app;
}
public void onApplicationEvent(ApplicationEvent event) {
//如果是容器刷新事件
if(event instanceof ContextRefreshedEvent ){//如果是容器關閉事件
try {
regDynamicBean();
} catch (IOException e) {
e.printStackTrace();
}
//System.out.println(event.getClass().getSimpleName()+" 事件已發生!");
}
}
private void regDynamicBean() throws IOException{
// 解析屬性文件,得到數據源Map
Map<String, DataSourceInfo> mapCustom = parsePropertiesFile("hummer.properties");
// 把數據源bean註冊到容器中
addSourceBeanToApp(mapCustom);
}
/**
* 功能說明:根據DataSource創建bean並註冊到容器中
* @param acf
* @param mapCustom
*/
private void addSourceBeanToApp(Map<String, DataSourceInfo> mapCustom) {
DefaultListableBeanFactory acf = (DefaultListableBeanFactory) app.getAutowireCapableBeanFactory();
BeanDefinition beanDefinition;
Iterator<String> iter = mapCustom.keySet().iterator();
while(iter.hasNext()){
String beanKey = iter.next();
// 得到Bean定義,並添加到容器中
beanDefinition = new ChildBeanDefinition("dataSource");
// 註意:必須先註冊到容器中,再得到Bean進行修改,否則數據源屬性不能有效修改
acf.registerBeanDefinition(beanKey, beanDefinition);
// 再得到數據源Bean定義,並修改連接相關的屬性
ComboPooledDataSource cpds = (ComboPooledDataSource)app.getBean( beanKey);;
cpds.setJdbcUrl(mapCustom.get(beanKey).connUrl);
cpds.setUser(mapCustom.get(beanKey).userName);
cpds.setPassword(mapCustom.get(beanKey).password);
}
}
/**
* 功能說明:解析屬性文件,得到數據源Map
* @return
* @throws IOException
*/
private Map<String, DataSourceInfo> parsePropertiesFile(String fileName) throws IOException {
// 屬性文件
ResourcePropertySource props = new ResourcePropertySource(fileName);
Matcher matcher;
Pattern pattern = Pattern.compile("^link\\.(eagle2\\.\\w+)\\.jdbc\\.(jdbcUrl|user|password)$");
Map<String, DataSourceInfo> mapDataSource = new HashMap<String,DataSourceInfo>();
// 根據配置文件解析數據源
for(String keyProp : props.getPropertyNames())
{
matcher = pattern.matcher(keyProp);
if(matcher.find()){
String dsName = matcher.group(1);
String dsPropName = matcher.group(2);
DataSourceInfo dsi;
if(mapDataSource.containsKey(dsName)){
dsi = mapDataSource.get(dsName);
}
else{
dsi = new DataSourceInfo();
}
// 根據屬性名給數據源屬性賦值
if("jdbcUrl".equals(dsPropName)){
dsi.connUrl = (String)props.getProperty(keyProp);
}else if("user".equals(dsPropName)){
dsi.userName = (String)props.getProperty(keyProp);
}else if("password".equals(dsPropName)){
dsi.password = (String)props.getProperty(keyProp);
}
mapDataSource.put(dsName, dsi);
}
}
return mapDataSource;
}
private class DataSourceInfo{
public String connUrl;
public String userName;
public String password;
public String toString(){
return "(JcbcUrl:"+connUrl+", user:"+userName+", password:"+password+")";
}
}
}
然後在beans.xml文件中添加配置
"ApplicationEventListener" class="com.dfsoft.hummer.domain.common.DynamicDataSourceC3p0" />
讓spring在加載時調用DynamicDataSourceC3p0中的onApplicationEvent方法實現動態註冊數據源。而原來實現多數據源動態切換的地方完全不用修改。
附加說明:
1、 做完上面的工作之後,在beans.xml中去掉原來的多數據源配置(已經沒有用瞭)。
2、 acf.registerBeanDefinition不用判斷容器中是否已經有相應名字的bean,spring會在該名稱的bean存在時覆蓋,不存在時添加。