spring注解 @PropertySource配置数据源全流程


Posted in Java/Android onMarch 25, 2022

@PropertySource数据源配置

一般在配置数据源是都会使用xml的方式注入,key-value在properties中管理;spring4.X已有着比较完善的注解来替换xml的配置方式。

使用xml配置数据源

通常我们使用xml配置数据源,使用SpEL获取properties中的配置。

applicationContext.xml 中配置 dataSource 及 PreferencesPlaceholderConfigurer,使用 PropertyPlaceholderConfigurer进行Bean属性替换

<bean id="configProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
        <property name="locations">
            <list>
                <value>classpath:/jdbc.properties</value>
            </list>
        </property>
        <property name="fileEncoding" value="utf-8"/>
    </bean>
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer">
    <property name="properties" ref="configProperties" />
</bean>
<!-- 使用proxool连接池的数据源, -->
<bean id="dataSource" class="org.logicalcobwebs.proxool.ProxoolDataSource">
    <!-- 数据源的别名 -->
    <property name="alias" value="${proxool.alias}" /> 
    <!-- 驱动 -->
    <property name="driver" value="${proxool.driver}" /> 
    <!-- 链接URL  -->
    <property name="driverUrl" value="${proxool.driverUrl}" /> 
    <!-- 用户名-->
    <property name="user" value="${proxool.user}" />
    <!-- 密码 -->
    <property name="password" value="${proxool.password}" /> 
    <!-- 最大链接数-->
    <property name="maximumConnectionCount" value="${proxool.maximumConnectionCount}" /> 
    <!-- 最小链接数 -->
    <property name="minimumConnectionCount" value="${proxool.minimumConnectionCount}" /> 
    <!-- ...(略) -->
</bean> 

jdbc.properties

proxool.alias=mySql
proxool.driver=com.mysql.jdbc.Driver
proxool.driverUrl=jdbc:mysql://localhost:3306/test?characterEncoding=utf8
proxool.user=root
proxool.password=root
proxool.maximumActiveTime=1200
proxool.maximumConnectionCount=50
#...

使用javaBean配置数据源

DataSourceConfiguration类是数据源的javaBean配置方式,@Configuratio注解当前类,

spring启动时会扫描被@Configuratio注解的类,注入当前类中配置的方法bean;

当然别忘了启用注解扫描:

<context:annotation-config/>  
<context:component-scan base-package="com.XXX.test.dateSource"></context:component-scan>

@value注解读取配置

@value中可以直接使用SpEL,获取properties配置,成员变量也不需要getter、setter,不过还是有一个前提,需要配置xml:

<bean id="configProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
        <property name="locations">
            <list>
                <value>classpath:/jdbc.properties</value>
            </list>
        </property>
        <property name="fileEncoding" value="utf-8"/>
    </bean>
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer">
    <property name="properties" ref="configProperties" />
</bean>

@Bean注解:spring扫面当前类时,注入每个有@Bean注解的方法的返回值Bean, name属性默认为返回值类类名首字母小写,这里自己设置name。

package com.XXX.test.dateSource;
import org.logicalcobwebs.proxool.ProxoolDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuratio
public class DataSourceConfiguration{
    @Value("${proxool.alias}")
    private String alias;
    @Value("${proxool.driver}")
    private String driver;
    @Value("${proxool.driverUrl}")
    private String driverUrl;
    @Value("${proxool.user}")
    private String user;
    @Value("${proxool.password}")
    private String password;
    //...
    @Bean(name="dataSource")
    public ProxoolDataSource dataSource(){
         ProxoolDataSource proxoolDataSource = new ProxoolDataSource();
         proxoolDataSource.setDriver(driver);
         proxoolDataSource.setDriverUrl(driverUrl);
         proxoolDataSource.setUser(user);
         proxoolDataSource.setPassword(password);
         //...
         return proxoolDataSource;
     }
 }

这时dataSource已被注入,使用时可注解注入,如下:

    @Autowired
    private ProxoolDataSource dataSource;

@PropertySource注解读取配置

@PropertySource注解当前类,参数为对应的配置文件路径,这种方式加载配置文件,可不用在xml中配置PropertiesFactoryBean引入jdbc.properties,使用时方便得多,DataSourceConfiguration不再需要成员变量,取而代之的是需要注入一个Environment环境配置,使用env.getProperty(key)获取数据:

package com.XXX.test.dateSource;
import org.logicalcobwebs.proxool.ProxoolDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
@Configuratio
@PropertySource("classpath:/jdbc.properties")
public class DataSourceConfiguration{
    @Autowired
    private Environment env;
    @Bean(name="dataSource")
    public ProxoolDataSource dataSource(){
         ProxoolDataSource proxoolDataSource = new ProxoolDataSource();
         proxoolDataSource.setDriver(env.getProperty("proxool.alias"));
         proxoolDataSource.setDriverUrl(env.getProperty("proxool.driver"));
         proxoolDataSource.setUser(env.getProperty("proxool.user"));
         proxoolDataSource.setPassword(env.getProperty("proxool.password"));
         //...
         return proxoolDataSource;
     }
 }

这里主要是说明注解的用法,所以没有具体体现数据源全部参数的配置。对于有强迫症的来说若项目中所有bean都使用注解,几乎不太希望仅dataSource用xml类配置,换成类的方式类配置强迫感就消失了! 

注解的spring多数据源配置及使用

前一段时间研究了一下spring多数据源的配置和使用,为了后期从多个数据源拉取数据定时进行数据分析和报表统计做准备。由于之前做过的项目都是单数据源的,没有遇到这种场景,所以也一直没有去了解过如何配置多数据源。

后来发现其实基于spring来配置和使用多数据源还是比较简单的,因为spring框架已经预留了这样的接口可以方便数据源的切换。

先看一下spring获取数据源的源码

spring注解 @PropertySource配置数据源全流程

可以看到AbstractRoutingDataSource获取数据源之前会先调用determineCurrentLookupKey方法查找当前的lookupKey,这个lookupKey就是数据源标识。

因此通过重写这个查找数据源标识的方法就可以让spring切换到指定的数据源了。

第一步:创建一个DynamicDataSource的类

继承AbstractRoutingDataSource并重写determineCurrentLookupKey方法,代码如下:

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        // 从自定义的位置获取数据源标识
        return DynamicDataSourceHolder.getDataSource();
    }
}

第二步:创建DynamicDataSourceHolder

用于持有当前线程中使用的数据源标识,代码如下:

public class DynamicDataSourceHolder {
    /**
     * 注意:数据源标识保存在线程变量中,避免多线程操作数据源时互相干扰
     */
    private static final ThreadLocal<String> THREAD_DATA_SOURCE = new ThreadLocal<String>();
    public static String getDataSource() {
        return THREAD_DATA_SOURCE.get();
    }
    public static void setDataSource(String dataSource) {
        THREAD_DATA_SOURCE.set(dataSource);
    }
    public static void clearDataSource() {
        THREAD_DATA_SOURCE.remove();
    }
}

第三步:配置多个数据源

和第一步里创建的DynamicDataSource的bean,简化的配置如下:

<!--创建数据源1,连接数据库db1 -->
<bean id="dataSource1" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${db1.driver}" />
    <property name="url" value="${db1.url}" />
    <property name="username" value="${db1.username}" />
    <property name="password" value="${db1.password}" />
</bean>
<!--创建数据源2,连接数据库db2 -->
<bean id="dataSource2" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${db2.driver}" />
    <property name="url" value="${db2.url}" />
    <property name="username" value="${db2.username}" />
    <property name="password" value="${db2.password}" />
</bean>
<!--创建数据源3,连接数据库db3 -->
<bean id="dataSource3" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${db3.driver}" />
    <property name="url" value="${db3.url}" />
    <property name="username" value="${db3.username}" />
    <property name="password" value="${db3.password}" />
</bean>
<bean id="dynamicDataSource" class="com.test.context.datasource.DynamicDataSource">
    <property name="targetDataSources">
        <map key-type="java.lang.String">
            <!-- 指定lookupKey和与之对应的数据源 -->
            <entry key="dataSource1" value-ref="dataSource1"></entry>
            <entry key="dataSource2" value-ref="dataSource2"></entry>
            <entry key="dataSource3 " value-ref="dataSource3"></entry>
        </map>
    </property>
    <!-- 这里可以指定默认的数据源 -->
    <property name="defaultTargetDataSource" ref="dataSource1" />
</bean>

到这里已经可以使用多数据源了,在操作数据库之前只要DynamicDataSourceHolder.setDataSource("dataSource2")即可切换到数据源2并对数据库db2进行操作了。

示例代码如下:

@Service
public class DataServiceImpl implements DataService {
    @Autowired
    private DataMapper dataMapper;
    @Override
    public List<Map<String, Object>> getList1() {
        // 没有指定,则默认使用数据源1
        return dataMapper.getList1();
    }
    @Override
    public List<Map<String, Object>> getList2() {
        // 指定切换到数据源2
        DynamicDataSourceHolder.setDataSource("dataSource2");
        return dataMapper.getList2();
    }
    @Override
    public List<Map<String, Object>> getList3() {
        // 指定切换到数据源3
        DynamicDataSourceHolder.setDataSource("dataSource3");
        return dataMapper.getList3();
    }
}

----------------------------华丽的分割线----------------------------

但是问题来了,如果每次切换数据源时都调用DynamicDataSourceHolder.setDataSource("xxx")就显得十分繁琐了,而且代码量大了很容易会遗漏,后期维护起来也比较麻烦。能不能直接通过注解的方式指定需要访问的数据源呢,比如在dao层使用@DataSource("xxx")就指定访问数据源xxx?当然可以!前提是,再加一点额外的配置^_^。

首先,我们得定义一个名为DataSource的注解,代码如下:

@Target({ TYPE, METHOD })
@Retention(RUNTIME)
public @interface DataSource {
    String value();
}

然后,定义AOP切面以便拦截所有带有注解@DataSource的方法,取出注解的值作为数据源标识放到DynamicDataSourceHolder的线程变量中:

public class DataSourceAspect {
    /**
     * 拦截目标方法,获取由@DataSource指定的数据源标识,设置到线程存储中以便切换数据源
     *
     * @param point
     * @throws Exception
     */
    public void intercept(JoinPoint point) throws Exception {
        Class<?> target = point.getTarget().getClass();
        MethodSignature signature = (MethodSignature) point.getSignature();
        // 默认使用目标类型的注解,如果没有则使用其实现接口的注解
        for (Class<?> clazz : target.getInterfaces()) {
            resolveDataSource(clazz, signature.getMethod());
        }
        resolveDataSource(target, signature.getMethod());
    }
    /**
     * 提取目标对象方法注解和类型注解中的数据源标识
     *
     * @param clazz
     * @param method
     */
    private void resolveDataSource(Class<?> clazz, Method method) {
        try {
            Class<?>[] types = method.getParameterTypes();
            // 默认使用类型注解
            if (clazz.isAnnotationPresent(DataSource.class)) {
                DataSource source = clazz.getAnnotation(DataSource.class);
                DynamicDataSourceHolder.setDataSource(source.value());
            }
            // 方法注解可以覆盖类型注解
            Method m = clazz.getMethod(method.getName(), types);
            if (m != null && m.isAnnotationPresent(DataSource.class)) {
                DataSource source = m.getAnnotation(DataSource.class);
                DynamicDataSourceHolder.setDataSource(source.value());
            }
        } catch (Exception e) {
            System.out.println(clazz + ":" + e.getMessage());
        }
    }
}

最后在spring配置文件中配置拦截规则就可以了,比如拦截service层或者dao层的所有方法:

<bean id="dataSourceAspect" class="com.test.context.datasource.DataSourceAspect" />
    <aop:config>
        <aop:aspect ref="dataSourceAspect">
            <!-- 拦截所有service方法 -->
            <aop:pointcut id="dataSourcePointcut" expression="execution(* com.test.*.dao.*.*(..))"/>
            <aop:before pointcut-ref="dataSourcePointcut" method="intercept" />
        </aop:aspect>
    </aop:config>
</bean>

OK,这样就可以直接在类或者方法上使用注解@DataSource来指定数据源,不需要每次都手动设置了。

示例代码如下:

@Service
// 默认DataServiceImpl下的所有方法均访问数据源1
@DataSource("dataSource1")
public class DataServiceImpl implements DataService {
    @Autowired
    private DataMapper dataMapper;
    @Override
    public List<Map<String, Object>> getList1() {
        // 不指定,则默认使用数据源1
        return dataMapper.getList1();
    }
    @Override
    // 覆盖类上指定的,使用数据源2
    @DataSource("dataSource2")
    public List<Map<String, Object>> getList2() {
        return dataMapper.getList2();
    }
    @Override
    // 覆盖类上指定的,使用数据源3
    @DataSource("dataSource3")
    public List<Map<String, Object>> getList3() {
        return dataMapper.getList3();
    }
}

提示:注解@DataSource既可以加在方法上,也可以加在接口或者接口的实现类上,优先级别:方法>实现类>接口。也就是说如果接口、接口实现类以及方法上分别加了@DataSource注解来指定数据源,则优先以方法上指定的为准。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Java/Android 相关文章推荐
Feign调用传输文件异常的解决
Jun 24 Java/Android
总结Java对象被序列化的两种方法
Jun 30 Java/Android
Mybatis-plus在项目中的简单应用
Jul 01 Java/Android
Java 语言中Object 类和System 类详解
Jul 07 Java/Android
浅谈Java父子类加载顺序
Aug 04 Java/Android
Java异常处理try catch的基本用法
Dec 06 Java/Android
SpringBoot中使用Redis作为全局锁示例过程
Mar 24 Java/Android
MyBatis配置文件解析与MyBatis实例演示
Apr 07 Java/Android
Spring Boot配合PageHelper优化大表查询数据分页
Apr 20 Java/Android
SpringBoot 集成短信和邮件 以阿里云短信服务为例
Apr 22 Java/Android
Java 多线程并发FutureTask
Jun 28 Java/Android
一文了解Java动态代理的原理及实现
Jul 07 Java/Android
Netty客户端接入流程NioSocketChannel创建解析
Mar 25 #Java/Android
Java 超详细讲解设计模式之中的抽象工厂模式
Netty分布式客户端处理接入事件handle源码解析
Java 超详细讲解IO操作字节流与字符流
Netty分布式客户端接入流程初始化源码分析
Mar 25 #Java/Android
java后台调用接口及处理跨域问题的解决
Mar 24 #Java/Android
SpringBoot中使用Redis作为全局锁示例过程
Mar 24 #Java/Android
You might like
MySQL数据库转移,access,sql server 转 MySQL 的图文教程
2007/09/02 PHP
ThinkPHP、ZF2、Yaf、Laravel框架路由大比拼
2015/03/25 PHP
解读PHP的Yii框架中请求与响应的处理流程
2016/03/17 PHP
Laravel访问出错提示:`Warning: require(/vendor/autoload.php): failed to open stream: No such file or di解决方法
2019/04/02 PHP
jQuery客户端分页实例代码
2013/11/18 Javascript
jQuery避免$符和其他JS库冲突的方法对比
2014/02/20 Javascript
javascript实现点击商品列表checkbox实时统计金额的方法
2015/05/15 Javascript
JavaScript模块规范之AMD规范和CMD规范
2015/10/27 Javascript
JavaScript学习笔记之数组的增、删、改、查
2016/03/23 Javascript
js获取鼠标点击的对象,点击另一个按钮删除该对象的实现代码
2016/05/13 Javascript
封装的dialog插件 基于bootstrap模态对话框的简单扩展
2016/08/10 Javascript
JavaScript排序算法动画演示效果的实现方法
2016/10/18 Javascript
vue.js开发环境安装教程
2017/03/17 Javascript
angular+webpack2实战例子
2017/05/23 Javascript
Angular 封装并发布组件的方法示例
2018/04/19 Javascript
node中使用es6/7/8(支持性与性能)
2019/03/28 Javascript
利用weixin-java-miniapp生成小程序码并直接返回图片文件流的方法
2019/03/29 Javascript
Vue Render函数原理及代码实例解析
2020/07/30 Javascript
Python的垃圾回收机制深入分析
2014/07/16 Python
详解Django中的权限和组以及消息
2015/07/23 Python
Python线性回归实战分析
2018/02/01 Python
python通过Windows下远程控制Linux系统
2018/06/20 Python
用python实现k近邻算法的示例代码
2018/09/06 Python
python多线程调用exit无法退出的解决方法
2019/02/18 Python
pytorch的batch normalize使用详解
2020/01/15 Python
PyQt5实现仿QQ贴边隐藏功能的实例代码
2020/05/24 Python
Python 如何创建一个线程池
2020/07/28 Python
利用canvas实现图片压缩的示例代码
2018/07/17 HTML / CSS
Under Armour安德玛德国官网:美国高端运动科技品牌
2019/03/09 全球购物
MAC Cosmetics官方网站:魅可专业艺术彩妆
2019/04/10 全球购物
远程学习的教学用品和家庭学习资源:Really Good Stuff
2020/04/27 全球购物
租房协议书范本
2014/04/09 职场文书
公共艺术专业自荐信
2014/09/01 职场文书
合同权益转让协议书模板
2014/11/18 职场文书
2014年采购部工作总结
2014/11/20 职场文书
二十年同学聚会感言
2015/07/30 职场文书