Spring AOP根据JdbcTemplate方法名动态设置数据源

说明:现在的场景是,采用数据库(Mysql)复制(binlog)的方式在两台不同服务器部署并配置主从(Master-Slave)关系;
并需要程序上的数据操作方法来访问不同的数据库,比如,update方法访问主数据库服务器,query方法访问从数据库服务器。
即把“增删改”和“查”分开访问两台服务器,当然两台服务器的数据库同步事先已经配置好。
然而程序是早已完成的使用Spring JdbcTemplate的架构,如何在不修改任何源代码的情况下达到<本文标题>的功能呢?
分析:
1.目前有两个数据源需要配置到Spring框架中,如何统一管理这两个数据源?
JdbcTemplate有很多数据库操作方法,关键的可以分为以下几类(使用简明通配符):execute(args..)、update(args..)、batchUpdate(args..)、query*(args..)

args.. 表示可为任意个参数或无参数。

2.如何根据这些方法名来使用不同的数据源呢?
3.多数据源的事务管理(非本文关注点)。
实现:
Spring配置文件applicationContext.xml(包含相关bean类的代码)
1.数据源配置(省略了更为详细的连接参数设置):
01 02 class=“org.apache.commons.dbcp.BasicDataSource”
03 destroy-method=“close”>
04 05 value=“${jdbc.driverClassName}” />
06
07
08
09
10
11
12 13 class=“org.apache.commons.dbcp.BasicDataSource”
14 destroy-method=“close”>
15 16 value=“${jdbc.driverClassName}” />
17
18
19
20
21
22
23 24 class=“test.my.serivce.ds.DynamicDataSource”>
25
26
27
28
29
30
31
32
首先定义两个数据源(连接地址及用户名等数据存放在properties属性文件中),Spring可以设置多个数据源,究其根本也不过是一个普通bean罢了。
关键是ID为“dataSource”的这个bean的设置,它是这个类“test.my.serivce.ds.DynamicDataSource”的一个实例:
1 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
2
3 public class DynamicDataSource extends AbstractRoutingDataSource {
4 @Override
5 protected Object determineCurrentLookupKey() {
6 return CustomerContextHolder.getCustomerType();
7 }
8 }

DynamicDataSource类继承了Spring的抽象类AbstractRoutingDataSource,而AbstractRoutingDataSource本身实现了javax.sql.DataSource接口(由其父类抽象类AbstractDataSource实现),因此其实际上也是一个标准数据源的实现类。该类是Spring专为多数据源管理而增加的一个接口层,参见Spring-api-doc可知:
Abstract DataSource implementation that routes getConnection() calls to one of various target DataSources based on a lookup key. The latter is usually (but not necessarily) determined through some thread-bound transaction context.
它根据一个数据源唯一标识key来寻找已经配置好的数据源队列,它通常是与当前线程绑定在一起的。
查看其源码,知道它还实现了Spring的初始化方法类InitializingBean,这个类只有一个方法:afterPropertiesSet(),由Spring在初始化bean完成之后调用(根据该方法名联想应该是设置完所有属性后再调用,实际也是如此):
01 public void afterPropertiesSet() {
02 if (this.targetDataSources == null) {
03 throw new IllegalArgumentException(“targetDataSources is required”);
04 }
05 this.resolvedDataSources = new HashMap(this.targetDataSources.size());
06 for (Iterator it = this.targetDataSources.entrySet().iterator(); it.hasNext(); ) {
07 Map.Entry entry = (Map.Entry)it.next();
08 Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
09 DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
10 this.resolvedDataSources.put(lookupKey, dataSource);
11 }
12 if (this.defaultTargetDataSource != null)
13 this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
14 }
查看其具体实现可知,Spring将所有已经配置好的数据源存放到一个名为targetDataSources的hashMap对象中(targetDataSources属性必须设置,否则异常;defaultTargetDataSource属性可以不必设置)。只是把数据源统一存到一个map中并不能做什么,关键是它还重写了javax.sql.DataSource的getConnection()方法,该方法无论你在何时使用数据库操作相关的方法时都会使用到,即使ibatis、hibernate、JPA等进行多层封装的框架底层还是使用最普通的JDBC来实现。
01 public Connection getConnection() throws SQLException {
02 return determineTargetDataSource().getConnection();
03 }
04 protected DataSource determineTargetDataSource() {
05 Object lookupKey = determineCurrentLookupKey();
06 DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
07 if (dataSource == null)
08 dataSource = this.resolvedDefaultDataSource;
09 if (dataSource == null)
10 throw new IllegalStateException(“Cannot determine target DataSource for lookup key [” + lookupKey + “]“);
11 return dataSource;
12 }
13 protected Object resolveSpecifiedLookupKey(Object lookupKey) {
14 return lookupKey;
15 }
16 protected abstract Object determineCurrentLookupKey();
省略部分校验代码,这里有一个必须的关键方法:determineCurrentLookupKey,也是一个抽象的有你自己实现的方法,从这个方法返回实际要使用的数据源的key(也即在前面配置的数据源bean的ID)。从Spring-api-doc中可以看到详细说明:
Determine the current lookup key. This will typically be implemented to check a thread-bound transaction context. Allows for arbitrary keys. The returned key needs to match the stored lookup key type.
它允许任意类型的key,但必须是跟保存到数据源hashMap中的key类型一致。我们可以在Spring配置文件中指定该类型,网上看到有仁兄使用枚举类型的,是一个有不错约束性的主意。
我们的“test.my.serivce.ds.DynamicDataSource”实现了这个方法,可见具体的数据源key是从CustomerContextHolder类中获得的,并且也是使用key与当前线程绑定的方式:
01 public class CustomerContextHolder {
02 private static final ThreadLocal contextHolder = new ThreadLocal();
03 public static void setCustomerType(String customerType) {
04 contextHolder.set(customerType);
05 }
06 public static String getCustomerType() {
07 return (String) contextHolder.get();
08 }
09 public static void clearCustomerType() {
10 contextHolder.remove();
11 }
12 }
我们也可以使用全局变量的方式来存储这个key。我之前并不知道java.lang.ThreadLocal,那就充点电吧:http://java.dzone.com/articles/java-thread-local-–-how-use
甚至有一位评论者一针见血的指出问题来:
Why is userThreadLocal declared public? AFAIK, ThreadLocal instances are typically private static fields. Also, ThreadLocal is a generic type, it is ThreadLocal. An important benefit of ThreadLocal worth mentioning (from 1.4 JVMs forward), is as an alternative to synchronization, to improve scalability in transaction-intensive environments. Classes encapsulated in ThreadLocal are automatically thread-safe in a pretty simple way, since it‘s clear that anything stored in ThreadLocal is not shared between threads.
ThreadLocal是线程安全的,并且不能在多线程之间共享。根据这个原理,我写了下面的小例子以便进一步理解:
01 public class Test {
02 private static ThreadLocal tl = new ThreadLocal();
03 public static void main(String[] args) {
04 tl.set(“abc”);
05 System.out.println(tl.get());
06 new Thread(new Runnable() {
07 public void run() {
08 System.out.println(tl.get());
09 }
10 }).start();
11 }
12 }
做到这里,我们已经解决了第一个问题,但似乎还没有进入正题,如何根据JdbcTemplate方法名动态设置数据源呢?
2.Spring AOP切入JdbcTemplate方法配置:
01
02
03
04 05 expression=“execution( org.springframework.jdbc.core.JdbcTemplate.update(..)) || execution( org.springframework.jdbc.core.JdbcTemplate.batchUpdate(..))” />
06 07 pointcut-ref=“update” />
08

09
10 11 pointcut=“execution(
org.springframework.jdbc.core.JdbcTemplate.query(..)) || execution( org.springframework.jdbc.core.JdbcTemplate.execute(..))” />
12
13
可以看到我已经使用将JdbcTemplate的4类方法进行拦截,并使用前置通知的方式()在执行这些方法之前调用其他方法,具体的AOP表达式语言的含义我就不细说了。
根据最开始的说明,分别对update操作和select操作进行拦截并调用不同的方法,这个方法到底是什么呢?
其实就是给ThreadLocal设置数据源的名字(key),以便DynamicDataSource知道到底是使用哪一个数据源。

前置方法就是调用“test.my.serivce.ds.BeforeAdvice”类的某个set方法:
1 public class BeforeAdvice {
2 public void setMasterDataSource() {
3 CustomerContextHolder.setCustomerType(“master”);
4 }
5 public void setSlaveDataSource() {
6 CustomerContextHolder.setCustomerType(“slave”);
7 }
8 }
当前线程就会保存下设置进去的key名称并随时可以调用。
最后再配置一个JdbcTemplate bean即可。
1 2 class=“org.springframework.jdbc.core.JdbcTemplate”>
3
4
附注:
1.在解决过程中遇到的一个问题:
Spring异常:no matching editors or conversion strategy found
参考:http://blog.csdn.net/zzycgfans/article/details/6316081
引用:Spring注入的是接口,关联的是实现类。 这里注入了实现类,所以报异常了。
2.本文主要参考的文章有:
该文还包含事务管理的配置:http://hi.baidu.com/litianyi520/blog/item/71279e3e180db6f1838b1327.html
该文与多数据源的设置对我有一定的启发(此外还包含测试用例):http://oiote.blog.sohu.com/74596942.html
之前做过ibatis采用ehCache和osCache做缓存的配置,这篇有点类似:http://lihaiyan.iteye.com/blog/127818
多数据源的一些实际场景分析,理论重于实际:http://hi.baidu.com/freeway2000/blog/item/ba0906f4946fa8eb7709d716.html
此外,javaeye(现为iteye)的一些文章也是有参考价值的:http://www.iteye.com/wiki/blog/1229655
EOF.最初的设想到这里变成了现实。本文讲述了“Spring AOP根据JdbcTemplate方法名动态设置数据源”的整个实现过程和一些浅显的分析。
使用这样配置后在实际使用中发现仍然有问题。比如,调用jdbcTemplate的update方法后立即调用query方法查询该条记录,或者使用以下方法:
this.jdbcTemplate.update(new PreparedStatementCreator(), keyHolder)
因为数据库复制有同步间隙,这个时间晚于程序的调用,就会出现查询不到数据的情况,实际上是数据还未同步到从服务器。期待更好的解决方案!

时间: 2024-10-15 16:24:29

Spring AOP根据JdbcTemplate方法名动态设置数据源的相关文章

Spring AOP获取拦截方法的参数名称跟参数值

Spring AOP获取拦截方法的参数名称跟参数值 注意:这种方式需要JDK1.8版本支持 开始: 1.aop配置: <aop:aspectj-autoproxy expose-proxy="true" /> 注意该配置需要配置在spring mvc的配置文件中,因为需要拦截controller层方法 或者在必须要配置在spring配置文件中的情况下,同时需要拦截controller层的方法,可以在spring配置文件中加入controller层的包扫描 2.具体代码: i

Spring AOP详解 、 JDK动态代理、CGLib动态代理

AOP是Aspect Oriented Programing的简称,面向切面编程.AOP适合于那些具有横切逻辑的应用:如性能监测,访问控制,事务管理以及日志记录.AOP将这些分散在各个业务逻辑中的代码通过横向切割的方式抽取到一个独立的模块中. 一.AOP术语 1.连接点(Joinpoint) 程序执行的某个特定位置:如类开始初始化之前.类初始化之后.类某个方法调用前.调用后等:一个类或一段程序代码拥有一些具有边界性质的特定点,这些代码中的特定点就成为“连接点”,Spring仅支持方法的连接点,即

【转载】Spring AOP详解 、 JDK动态代理、CGLib动态代理

原文地址:https://www.cnblogs.com/kukudelaomao/p/5897893.html AOP是Aspect Oriented Programing的简称,面向切面编程.AOP适合于那些具有横切逻辑的应用:如性能监测,访问控制,事务管理以及日志记录.AOP将这些分散在各个业务逻辑中的代码通过横向切割的方式抽取到一个独立的模块中. 一.AOP术语 1.连接点(Joinpoint) 程序执行的某个特定位置:如类开始初始化之前.类初始化之后.类某个方法调用前.调用后等:一个类

spring aop拦截controller方法

背景 开发的web应用程序涉及到校验采用的spring校验框架,在controller的方法中到处都要写校验处理,异常处理,能否减少这部分冗余代码. 问题: 这是表单提交的处理 1 @RequestMapping(value = "/edit", method = RequestMethod.POST) 2 public String edit(@Valid FormBean formBean, BindingResult bindingResult, Model model) { 3

spring data jpa 创建方法名进行简单查询

Spring Data JPA框架在进行方法名解析时,会先把方法名多余的前缀截取掉,比如 find.findBy.read.readBy.get.getBy,然后对剩下部分进行解析. 假如创建如下的查询:findByTaskProjectName(),框架在解析该方法时,首先剔除 findBy,然后对剩下的属性进行解析,假设查询实体为Doc 1.先判断 taskProjectName (根据 POJO 规范,首字母变为小写)是否为查询实体的一个属性,如果是,则表示根据该属性进行查询:如果没有该属

Spring AOP—注解配置方法的使用

Spring除了支持Schema方式配置AOP,还支持注解方式:使用@AspectJ风格的切面声明. 1 启用对@AspectJ的支持 Spring默认不支持@AspectJ风格的切面声明,为了支持需要使用如下配置: 这样Spring就能发现@AspectJ风格的切面并且将切面应用到目标对象. 2 声明切面 @AspectJ风格的声明切面非常简单,使用@Aspect注解进行声明: 然后将该切面在配置文件中声明为Bean后,Spring就能自动识别并进行AOP方面的配置: 该切面就是一个POJO,

Java反射:根据方法名动态调用方法,解决商品动态属性取值问题。

public class Goods{ private String goodsName; private String attr1; private String attr2; private String attr3; private String attr4; ......... private String attr20; setter/getter方法 } 在数据库表goods中,已知该商品的属性个数N(满足:N=5,attr1—attr5有值,attr6-attr20为空).问如何取

spring AOP 代理(静态与动态)

一.没有代理模式 缺点: 1.工作量特别大,如果项目中有多个类,多个方法,则要修改多次. 2.违背了设计原则:开闭原则(OCP),对扩展开放,对修改关闭,而为了增加功能把每个方法都修改了,也不便于维护. 3.违背了设计原则:单一职责(SRP),每个方法除了要完成自己本身的功能,还要计算耗时.延时:每一个方法引起它变化的原因就有多种. 4.违背了设计原则:依赖倒转(DIP),抽象不应该依赖细节,两者都应该依赖抽象.而在Test类中,Test与Math都是细节. 假设需实现一个计算的类Math.完成

spring aop 拦截业务方法,实现权限控制

难点:aop类是普通的java类,session是无法注入的,那么在有状态的系统中如何获取用户相关信息呢,session是必经之路啊,获取session就变的很重要.思索很久没有办法,后来在网上看到了解决办法. 思路是:      i. SysContext  成员变量 request,session,response     ii. Filter 目的是给 SysContext 中的成员赋值     iii.然后在AOP中使用这个SysContext的值   要用好,需要理解  ThreadL