開始研究動態代理之前 先簡要談下動態代理的概念 在不改變原有類結構的前提下增強類的功能以及對類原有方法操作,註意是方法不是屬性(屬性一般被設計為private修飾,不可以直接被調用) 動態代理的基本實例不做闡述,網上一大把 不理解的同學可以直接去搜索。 今天說的是自己在項目中遇到的一個實際的動態代理 ...
開始研究動態代理之前 先簡要談下動態代理的概念
在不改變原有類結構的前提下增強類的功能以及對類原有方法操作,註意是方法不是屬性(屬性一般被設計為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>
這裡只是給出一個動態代理在項目中的應用實際情況,希望給各位同仁一點應用動態的代理的思路和技巧。單獨拿出來是無法使用要配合實際的項目背景來調試和使用
如有錯誤和理解性的錯誤,請及時指出 大家一起學習。