動態(tài)代理類是實現(xiàn)在運行時指定的接口列表的類,這樣通過類實例上的一個接口的方法調(diào)用將被編碼并通過統(tǒng)一接口分派到另一個對象示惊。
先問一個問題好港?
數(shù)據(jù)庫操作需要以下流程: 獲取數(shù)據(jù)庫連接->執(zhí)行sql->提交事務->異常回滾事務->釋放連接米罚。
但是我們開發(fā)很少自己手動去做獲取數(shù)據(jù)庫連接钧汹、提交事務、回滾事務录择、釋放連接等這些操作拔莱。
為什么我們不需要做這些操作?隘竭?塘秦?
那是有像Spring
這樣的框架,它底層用了動態(tài)代理技術动看,我們只需要寫業(yè)務sql就可以了尊剔,其它像獲取數(shù)據(jù)庫連接、釋放連接菱皆、提交事務须误、回滾事務等操作交給了代理類去完成笔咽,因為這些操作每個方法都是一樣的,如果我們每個方法都去寫那不是要瘋掉霹期。
代理類在執(zhí)行被代理類方法的前后加上自己的操作。
傳統(tǒng)數(shù)據(jù)庫操作方式拯田。
// 加載驅(qū)動類
Class.forName("com.mysql.jdbc.Driver");
Connection conn;
PreparedStatement ps1;
PreparedStatement ps2;
try {
// 獲取連接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/xxx", "root", "password");
ps1 = conn.prepareStatement("update user set age = 1");
ps2 = conn.prepareStatement("update user set money = 2");
// 執(zhí)行sql
ps1.executeUpdate();
ps2.executeUpdate();
// 提交事務
conn.commit();
} catch (SQLException e) {
// 回滾事務
conn.rollback();
} finally {
// 關閉連接
ps1.close();
ps2.close();
conn.close();
}
}
看代碼 動態(tài)代理到底怎么實現(xiàn)的
代理接口 Eat
和Sleep
历造。
// 接口 吃
public interface Eat {
// 吃飯
void eat();
}
// 接口 誰
public interface Sleep {
// 睡覺
void sleep(int hour);
}
被代理類 People
它實現(xiàn)了兩個接口 Eat
、Sleep
船庇。
public class People implements Eat, Sleep {
@Override
public void eat() {
System.out.println("吃飯");
}
@Override
public void sleep(int hour) {
System.out.println("睡覺吭产,時長:" + hour);
if (hour <= 0) {
// 睡眠時長小于0拋出異常
throw new RuntimeException();
}
}
}
統(tǒng)一處理類 ProxyInvocationHandler
處理類需要實現(xiàn)InvocationHandler
接口,獲得invoke
方法鸭轮。
invoke
方法有三個參數(shù):代理類臣淤、被代理類的方法、被代理類的方法參數(shù)窃爷。
// 統(tǒng)一處理類
public class ProxyInvocationHandler implements InvocationHandler {
private Object obj;
public ProxyInvocationHandler(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
Object result;
try {
System.out.println("方法調(diào)用之前");
result = m.invoke(obj, args);
} catch (InvocationTargetException e) {
System.out.println("統(tǒng)一處理異常");
return null;
} finally {
System.out.println("方法調(diào)用之后");
}
return result;
}
}
生成代理類
通過Proxy
類的靜態(tài)方法newProxyInstance
生成代理類邑蒋。
需要傳遞三個參數(shù):類加載器、被代理接口數(shù)組按厘、統(tǒng)一處理類医吊。
public class Demo {
public static void main(String[] args) {
People obj = new People();
Object proxyObj = Proxy.newProxyInstance(
obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),
new ProxyInvocationHandler(obj));
Eat eatObj = (Eat) proxyObj;
eatObj.eat();
System.out.println();
Sleep sleepObj = (Sleep) proxyObj;
sleepObj.sleep(8);
System.out.println();
sleepObj.sleep(-1);
System.out.println();
}
// 運行結(jié)果
// 方法調(diào)用之前
// 吃飯
// 方法調(diào)用之后
// 方法調(diào)用之前
// 睡覺,時長:8
// 方法調(diào)用之后
// 方法調(diào)用之前
// 睡覺逮京,時長:-1
// 統(tǒng)一處理異常
// 方法調(diào)用之后
}
類加載器可以通過class對象的getClassLoader()
方法獲得卿堂。
還記得獲取Class對象的幾種方法嗎?懒棉?草描?
這里的obj.getClass().getInterfaces()
得到結(jié)果就是接口的class數(shù)組。就等同于
new Class[]{Eat.class, Sleep.class}
生成的代理類proxyObj
可以向上轉(zhuǎn)型為Eat
接口和Sleep
接口策严。不管調(diào)用Eat
的eat()
方法還是Sleep
的sleep()
都會統(tǒng)一派發(fā)到InvocationHandler
類的invoke
方法穗慕,invoke
方法中在調(diào)用被代理類的原方法之前和之后都可以進行一些操作,并且可以對被代理類的原方法的異常進行一些處理享钞。
結(jié)語
動態(tài)代理非常重要揍诽,雖然平時可能很少用到,但是很多流行的框架都有用到它栗竖。動態(tài)代理還有一種實現(xiàn)方式CGLIB
,它是一個強大的暑脆,高性能,高質(zhì)量的Code生成類庫狐肢,是一個開源項目添吗,這個后續(xù)會跟大家分享。
關注微信公眾號:小虎哥的技術博客份名,每天一篇文章碟联,讓你我都成為更好的自己妓美。