动态代理实现多数据库的定时刷新链接信息的应用
开始研究动态代理之前 先简要谈下动态代理的概念
在不改变原有类结构的前提下增强类的功能以及对类原有方法操作,注意是方法不是属性(属性一般被设计为private修饰,不可以直接被调用)
动态代理的基本实例不做阐述,网上一大把 不理解的同学可以直接去搜索。
今天说的是自己在项目中遇到的一个实际的动态代理应用--》定时刷新多数据库的连接接属性
项目背景:项目中存在三个数据库 redis PostgreSQL(PT库) oracle
我们做的需求是将oracle和redis的数据库链接存储在 PT库中 然后项目启动后从PT库的自定义配置表中读取oracle和redis的数据库链接
在这里我们使用的是druid连接池 有兴趣的伙伴可以去研究下
废话不多说直接上代码实例
基于CGlib实现
1 package com.manager.aop; 2 3 import java.lang.reflect.Method; 4 import java.sql.SQLException; 5 import java.util.concurrent.ScheduledFuture; 6 import javax.annotation.PostConstruct; 7 import javax.sql.DataSource; 8 import org.springframework.beans.factory.FactoryBean; 9 import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; 10 import com.alibaba.druid.pool.DruidDataSource; 11 import net.sf.cglib.proxy.Enhancer; 12 import net.sf.cglib.proxy.MethodInterceptor; 13 import net.sf.cglib.proxy.MethodProxy; 14 15 16 17 18 public class DynamicProxy implements FactoryBean<DataSource> { 19 20 private DruidDataSource target; 21 private DataSource proxy; 22 23 24 //这两个类是线程池的类用来做定时器任务 25 //需要用注解注入进来 26 private ThreadPoolTaskScheduler scheduler; 27 private ScheduledFuture<?> future; 28 29 30 //存储每次刷新的上一次的数据库的链接属性,以便和最新的数据库链接对比 31 private String key; 32 @PostConstruct 33 private void init() throws SQLException { 34 //项目启动后 第一次实例化连接池 35 target=createDataSource(); 36 //生成Druid链接池的代理对象 37 proxy=(DataSource) Enhancer.create(target.getClass(),//设置代理目标类的字节码对象 38 CallBacks()//设置代理对象的回调对象 39 ); 40 //判断是否创建新的代理对象 41 refresh(); 42 future=scheduler.scheduleWithFixedDelay(new Runnable() { 43 public void run() { 44 // TODO Auto-generated method stub 45 try { 46 refresh(); 47 } catch (SQLException e) { 48 // TODO Auto-generated catch block 49 e.printStackTrace(); 50 } 51 } 52 }, 1000*10); 53 } 54 private void refresh() throws SQLException { 55 //这里的key的值是从PT数据库读取而来的 oracle的链接属性的拼接值 没有全写 56 //写一个service去从pt数据库里面 定时 获取oracle的连接参数 定时任务和此处的定时器一致 57 String key="username+password+url"; 58 //如果最新的连接池属性拼接参数key和上一次的key不相同 则销毁上次的委托对象并创建新的数据库连接 如果相同者不做处理 59 if(this.key.equals(key)) { 60 if(target!=null) { 61 target.close(); 62 } 63 //当数据库链接不同的时候才会重新给key添加新的引用 64 this.key=key; 65 target=createDataSource(); 66 } 67 } 68 private DruidDataSource createDataSource() throws SQLException { 69 70 //在这里设置连接池的属性 同样没有写全 71 DruidDataSource druid=new DruidDataSource(); 72 druid.setUrl("url"); 73 //阿里连接池的自行初始化 如果不初始化 代理的连接池对象的属性为空 74 druid.init(); 75 return druid; 76 } 77 78 private MethodInterceptor CallBacks() { 79 return new MethodInterceptor() { 80 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { 81 // TODO Auto-generated method stub 82 //在这里执行被代理对象的操作 83 Object result=proxy.invokeSuper(target, args); 84 //在这里执行被代理对象的操作 85 return result; 86 } 87 }; 88 } 89 public DataSource getObject() throws Exception { 90 // TODO Auto-generated method stub 91 return proxy; 92 } 93 public Class<?> getObjectType() { 94 // TODO Auto-generated method stub 95 return null; 96 } 97 public boolean isSingleton() { 98 // TODO Auto-generated method stub 99 return true; 100 } 101 102 }
这里实现了 FactoryBean<DataSource> 为什么要实现FactoryBean 主要是为了在xml做的ref引用时获得DataSource代理对象
基于JDK实现
1 package com.manager.aop; 2 3 import java.lang.reflect.InvocationHandler; 4 import java.lang.reflect.Method; 5 import java.lang.reflect.Proxy; 6 import java.sql.SQLException; 7 import java.util.concurrent.ScheduledFuture; 8 9 import javax.annotation.PostConstruct; 10 import javax.sql.DataSource; 11 12 import org.springframework.beans.factory.FactoryBean; 13 import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; 14 import org.springframework.util.ClassUtils; 15 16 import com.alibaba.druid.pool.DruidDataSource; 17 18 19 public class JdkProxy implements FactoryBean<DataSource> { 20 private DruidDataSource target; 21 private DataSource proxy; 22 23 24 //这两个类是线程池的类用来做定时器任务 25 private ThreadPoolTaskScheduler scheduler; 26 private ScheduledFuture<?> future; 27 28 29 //存储每次刷新的上一次的数据库的链接属性,以便和最新的数据库链接对比 30 private String key; 31 @PostConstruct 32 private void init() throws SQLException { 33 //项目启动后 第一次实例化连接池 34 target=createDataSource(); 35 //生成Druid链接池的代理对象 36 proxy=(DataSource) Proxy.newProxyInstance(ClassUtils.getDefaultClassLoader(), //获取类加载器 37 DataSource.class.getInterfaces(), //获取DataSource所实现的接口 38 handler()); 39 //判断是否创建新的代理对象 40 refresh(); 41 future=scheduler.scheduleWithFixedDelay(new Runnable() { 42 public void run() { 43 // TODO Auto-generated method stub 44 try { 45 refresh(); 46 } catch (SQLException e) { 47 // TODO Auto-generated catch block 48 e.printStackTrace(); 49 } 50 } 51 }, 1000*10); 52 } 53 private void refresh() throws SQLException { 54 //这里的key的值是从PT数据库读取而来的 oracle的链接属性的拼接值 没有全写 55 String key="username+password+url"; 56 //如果最新的连接池属性拼接参数key和上一次的key不相同 则销毁上次的委托对象并创建新的数据库连接 如果相同者不做处理 57 if(this.key.equals(key)) { 58 if(target!=null) { 59 target.close(); 60 } 61 //当数据库链接不同的时候才会重新给key添加新的引用 62 this.key=key; 63 target=createDataSource(); 64 } 65 } 66 private DruidDataSource createDataSource() throws SQLException { 67 68 //在这里设置连接池的属性 同样没有写全 69 DruidDataSource druid=new DruidDataSource(); 70 druid.setUrl("url"); 71 //阿里连接池的自行初始化 如果不初始化 代理的连接池对象的属性为空 72 druid.init(); 73 return druid; 74 } 75 76 private InvocationHandler handler() { 77 return new InvocationHandler() { 78 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 79 // TODO Auto-generated method stub 80 Object obj=method.invoke(target, args); 81 return obj; 82 } 83 }; 84 } 85 public DataSource getObject() throws Exception { 86 // TODO Auto-generated method stub 87 return proxy; 88 } 89 public Class<?> getObjectType() { 90 // TODO Auto-generated method stub 91 return null; 92 } 93 public boolean isSingleton() { 94 // TODO Auto-generated method stub 95 return true; 96 } 97 }
再来看xml如何到底是怎么配置的
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" 4 xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 5 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 6 xmlns:util="http://www.springframework.org/schema/util" 7 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd 8 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd 9 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd 10 http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd"> 11 <!-- 配置dao层扫描 --> 12 <context:property-placeholder location="classpath:conf/database.properties" /> 13 <!-- 通过JDBC模版获取数据库连接 --> 14 <bean scope="singleton" id="jdbcTemplate" 15 class="org.springframework.jdbc.core.JdbcTemplate"> 16 <property name="dataSource" ref="dataSource"></property> 17 </bean> 18 <!-- 数据库连接池 --> 19 //这里的class写的是我们所写的代理类的类路径 20 <bean id="dataSource" class="com.manager.aop.JdkProxy"> 21 //这里什么都可以不用写 因为我们在类中已经设置了属性 22 </bean> 23 24 <!-- 让spring管理sqlsessionfactory 使用mybatis和spring整合包中的 --> 25 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> 26 <!-- 数据库连接池 --> 27 //这里的ref引用的时候 会调用类中的getObject()方法拿到代理对象 28 <property name="dataSource" ref="dataSource" /> 29 <!-- 加载mybatis的全局配置文件 --> 30 <property name="mapperLocations" value="classpath:com/mapper/*.xml" /> 31 </bean> 32 //其他的配置没有写 只是示例在xml怎么拿到代理类所返回的代理对象供其他bean使用 33 </beans>
这里只是给出一个动态代理在项目中的应用实际情况,希望给各位同仁一点应用动态的代理的思路和技巧。单独拿出来是无法使用要配合实际的项目背景来调试和使用
如有错误和理解性的错误,请及时指出 大家一起学习。