什么是動態(tài)代理?
假如我有一個user對象,該對象里面有4個方法,增、刪、改、查,外界能直接調用這4個方法嗎?拿百度來說,你能隨便對百度上的內容進行增、刪、改、查操作嗎?你最多能執(zhí)行查的操作,增、刪、改的操作是不能執(zhí)行的,你必須要加一個權限操作,應該看看你是否有權限執(zhí)行這個操作。同理,誰操作了這個東西,你需要給我留下記錄,免得我不知道是誰做的。所以,我應該在每一個方法的前面加權限校驗,在每一個方法的后面加日志記錄。
該怎么做呢?
有人說,很簡單,直接在user對象的實現(xiàn)類里面去改,在增、刪、改查前面加上權限校驗,在后面加上日志記錄。你能隨便改別人的代碼嗎?你一改,所以用過user對象的地方都要改,這不亂套了嗎?
有人說,可以再重新創(chuàng)建一個user對象,在新對象中加上權限校驗和日志記錄。確實是這樣。但是如果我還有一個學生類,還有一個老師類...等等,你每一個都新創(chuàng)建一個對象的話,太麻煩了,而且沒有必要,因為對我來說,我只關心對象的增、刪、改、查操作,對于權限校驗和日志記錄我并不關心,這個時候,我們可以找中介來做權限校驗和日志記錄的事情,這個中介就是動態(tài)代理對象!
動態(tài)代理
代理:本來應該自己做的事情,卻請了別人來做,被請的人就是代理對象。
舉例:春季回家買票讓人代買
動態(tài)代理:在程序運行過程中產生的這個對象
而程序運行過程中產生對象其實就是反射,所以,動態(tài)代理其實就是通過反射來生成一個代理
在Java中java.lang.reflect包下提供了一個Proxy類和一個InvocationHandler接口,通過使用這個類和接口就可以生成動態(tài)代理對象。JDK提供的代理只能針對接口做代理。我們有更強大的代理cglib
Proxy類中的方法創(chuàng)建動態(tài)代理類對象
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
最終會調用InvocationHandler的方法
InvocationHandler
Object invoke(Object proxy,Method method,Object[] args)
代碼如何來實現(xiàn)呢?
package itcast_06;
/*
* 用戶操作接口
*/
public interface UserDao {
public abstract void add();
public abstract void delete();
public abstract void update();
public abstract void find();
}
package itcast_06;
/**
* 用戶接口實現(xiàn)類
*/
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("添加功能");
}
@Override
public void delete() {
System.out.println("刪除功能");
}
@Override
public void update() {
System.out.println("修改功能");
}
@Override
public void find() {
System.out.println("查找功能");
}
}
package itcast_06;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
//寫一個類實現(xiàn)InvocationHandler接口
public class MyInvocationHandler implements InvocationHandler {
private Object target; // 目標對象
public MyInvocationHandler(Object target) {
this.target = target;
}
//重寫invoke()方法
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("權限校驗");
Object result = method.invoke(target, args);
System.out.println("日志記錄");
return result; // 返回的是代理對象
}
}
package itcast_06;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) {
UserDao ud = new UserDaoImpl();
ud.add();
ud.delete();
ud.update();
ud.find();
System.out.println("-----------");
// 我們要創(chuàng)建一個動態(tài)代理對象
// Proxy類中有一個方法可以創(chuàng)建動態(tài)代理對象
// public static Object newProxyInstance(ClassLoader loader,Class<?>[]
// interfaces,InvocationHandler h)
// 我準備對ud對象做一個代理對象
MyInvocationHandler handler = new MyInvocationHandler(ud);
UserDao proxy = (UserDao) Proxy.newProxyInstance(ud.getClass().getClassLoader(), ud.getClass().getInterfaces(), handler);
proxy.add();
proxy.delete();
proxy.update();
proxy.find();
}
}
以上為JDK動態(tài)代理,只能針對接口做代理。我們有更強大的代理cglib。
JDK動態(tài)代理依賴一個類和一個接口,分別是什么?
答:Proxy類和InvocationHandler接口。
調用Proxy類中的newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)方法可以創(chuàng)建一個動態(tài)代理對象,但是這個方法需要3個參數(shù),前兩個參數(shù)是固定的,但第三個參數(shù)比較麻煩,需要我們創(chuàng)建一個類MyInvocationHandler來實現(xiàn)InvocationHandler接口,這個類里面要重寫invoke()方法。
JDK動態(tài)代理和cglib動態(tài)代理有什么區(qū)別?
答:JDK動態(tài)代理智能對實現(xiàn)了接口的類生成代理對象;
cglib可以對任意類生成代理對象,它的原理是對目標對象進行繼承代理,如果目標對象被final修飾,那么該類無法被cglib代理。
Spring框架的一大特點就是AOP,SpringAOP的本質就是動態(tài)代理,那么Spring到底使用的是JDK代理,還是cglib代理呢?
答:混合使用。如果被代理對象實現(xiàn)了接口,就優(yōu)先使用JDK代理,如果沒有實現(xiàn)接口,就用用cglib代理。
動態(tài)代理的應用
AOP(Aspect-OrientedProgramming,面向切面編程),AOP包括切面(aspect)、通知(advice)、連接點(joinpoint),實現(xiàn)方式就是通過對目標對象的代理在連接點前后加入通知,完成統(tǒng)一的切面操作。
實現(xiàn)AOP的技術,主要分為兩大類:
一是采用動態(tài)代理技術,利用截取消息的方式,對該消息進行裝飾,以取代原有對象行為的執(zhí)行;
二是采用靜態(tài)織入的方式,引入特定的語法創(chuàng)建“方面”,從而使得編譯器可以在編譯期間織入有關“方面”的代碼。
Spring提供了兩種方式來生成代理對象: JDKProxy和Cglib,具體使用哪種方式生成由AopProxyFactory根據(jù)AdvisedSupport對象的配置來決定。
默認的策略是如果目標類是接口,則使用JDK動態(tài)代理技術,如果目標對象沒有實現(xiàn)接口,則默認會采用CGLIB代理。
如果目標對象實現(xiàn)了接口,可以強制使用CGLIB實現(xiàn)代理(添加CGLIB庫,并在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>)。
JDK動態(tài)代理
1、因為利用JDKProxy生成的代理類實現(xiàn)了接口,所以目標類中所有的方法在代理類中都有。
2、生成的代理類的所有的方法都攔截了目標類的所有的方法。而攔截器中invoke方法的內容正好就是代理類的各個方法的組成體。
3、利用JDKProxy方式必須有接口的存在。
4、invoke方法中的三個參數(shù)可以訪問目標類的被調用方法的API、被調用方法的參數(shù)、被調用方法的返回類型。
cglib動態(tài)代理
1、 CGlib是一個強大的,高性能,高質量的Code生成類庫。它可以在運行期擴展Java類與實現(xiàn)Java接口。
2、 用CGlib生成代理類是目標類的子類。
3、 用CGlib生成 代理類不需要接口
4、 用CGLib生成的代理類重寫了父類的各個方法。
5、 攔截器中的intercept方法內容正好就是代理類中的方法體
spring兩種代理方式
若目標對象實現(xiàn)了若干接口,spring使用JDK的java.lang.reflect.Proxy類代理。
優(yōu)點:因為有接口,所以使系統(tǒng)更加松耦合
缺點:為每一個目標類創(chuàng)建接口若目標對象沒有實現(xiàn)任何接口,spring使用CGLIB庫生成目標對象的子類。
優(yōu)點:因為代理類與目標類是繼承關系,所以不需要有接口的存在。
缺點:因為沒有使用接口,所以系統(tǒng)的耦合性沒有使用JDK的動態(tài)代理好。