动态代理实现多数据库的定时刷新链接信息的应用

开始研究动态代理之前 先简要谈下动态代理的概念

在不改变原有类结构的前提下增强类的功能以及对类原有方法操作,注意是方法不是属性(属性一般被设计为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>

这里只是给出一个动态代理在项目中的应用实际情况,希望给各位同仁一点应用动态的代理的思路和技巧。单独拿出来是无法使用要配合实际的项目背景来调试和使用

如有错误和理解性的错误,请及时指出 大家一起学习。

 

posted @ 2019-01-19 18:27  StepByUs  阅读(505)  评论(0编辑  收藏  举报