代理模式爲常用的設計模式之一,代理模式爲實際對象提供了一個代理類,用代理類來控制實際對象的訪問(本地或是遠程訪問、起到權限或是對資源的控制),同時也起到隔離和屏蔽實際對象的具體實現細節。其UML圖如下:
接口
public interface Subject {
public void add();
}
實現類
public class RealSubject implements Subject {
@Override
public void add() {
System.out.println("add method");
}
}
代理類
public class SubjectAgent implements Subject {
private Subject sell ;
public SubjectAgent(Subject sell) {
this.sell = sell;
}
@Override
public void add() {
sell.add();
}
}
代理類中依賴了接口的一個具體實現,來實現Subject接口的方法,屏蔽了Subject接口的具體實現細節。同時,代理類還具有很好的拓展性,可以在代理類裏面添加其他一些跟具體業務無關的邏輯,例如:操作日誌、權限的控制等。靜態代理特點:
1、代理類與實現類需要共同實現同一個接口
2、不修改實現對象功能的前提下,可以對目標功能做拓展
3、可能存在多個實現類,也會有多個代理類,一旦增加新的接口或方法,目標對象和代理對象都很難維護
使用動態代理就可以很方便的維護代理對象和目標方法。
1.代理對象,不需要實現目標接口
2.代理對象的生成,是利用JDK的API,動態的在內存中構建代理對象(需要我們指定創建代理對象/目標對象實現的接口的類型)
3.動態代理也叫做:JDK代理,接口代理
動態代理
public class DynamicProxy implements InvocationHandler {
private Object targt;
public Object GetInstance(Object target){
this.targt =target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(proxy.getClass().toString());
Field [] fields = proxy.getClass().getDeclaredFields();
for(Field f:fields){
System.out.println(f.getName());
}
Method[] methods = proxy.getClass().getDeclaredMethods();
for(Method m :methods){
System.out.println(m.getName());
}
System.out.println("before");
Object result = method.invoke(targt,args);
System.out.println("after");
return result;
}
}
public class Test {
public static void main(String[] args) {
Subject proxy = (Subject) new DynamicProxy().GetInstance(new SubjectAgent(new RealSubject()));
proxy.add();
}
}
缺點是目標類需要實現接口,原因是JDK在生成代理類時需要傳入接口。
Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
那麼動態代理是怎麼把代理對象跟目標對象聯繫起來的呢?換句話說,在調用目標對象的相關方法時是怎樣跟代理對象的invoke方法聯繫起來的?
invoke方法中的參數proxy對象看似沒有起到作用,實際上是通過該對象把代理對象和目標對象關聯的。在編譯動態代理類時會生成內部類class $Proxy0 ,也就是此處的invoke方法參數中的proxy類,該類繼承自Proxy類並實現了Subject接口,其部分代碼如下:
public final void add() {
try {
super.h.invoke(this, m3, null);
return;
} catch (Error e) {
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
可以看到此處的目標對象的add()方法跟代理對象關聯起來了,實際調用的是Proxy中InvocationHandler的invoke方法。再看Proxy類中的關鍵代碼class Proxy{
InvocationHandler h=null;
protected Proxy(InvocationHandler h) {
this.h = h;
}
...
}
動態代理的不足之處就是目標對象必須實現接口,如果目標對象沒有實現接口,那麼就需要使用Cglib代理
Cglib代理
上面的靜態代理和動態代理模式都是要求目標對象是實現一個接口的目標對象,但是有時候目標對象只是一個單獨的對象,並沒有實現任何的接口,這個時候就可以使用以目標對象子類的方式類實現代理,這種方法就叫做:Cglib代理
Cglib代理,也叫作子類代理,它是在內存中構建一個子類對象從而實現對目標對象功能的擴展.
- JDK的動態代理有一個限制,就是使用動態代理的對象必須實現一個或多個接口,如果想代理沒有實現接口的類,就可以使用Cglib實現.
- Cglib是一個強大的高性能的代碼生成包,它可以在運行期擴展java類與實現java接口.它廣泛的被許多AOP的框架使用,例如Spring AOP和synaop,爲他們提供方法的interception(攔截)
- Cglib包的底層是通過使用一個小而塊的字節碼處理框架ASM來轉換字節碼並生成新的類.不鼓勵直接使用ASM,因爲它要求你必須對JVM內部結構包括class文件的格式和指令集都很熟悉.
Cglib子類代理實現方法:
1.需要引入cglib的jar文件,但是Spring的核心包中已經包括了Cglib功能,所以直接引入pring-core-3.2.5.jar
即可.
2.引入功能包後,就可以在內存中動態構建子類
3.代理的類不能爲final,否則報錯
4.目標對象的方法如果爲final/static,那麼就不會被攔截,即不會執行目標對象額外的業務方法
目標對象
public class CglibSubject {
public void add(){
System.out.println("add");
}
}
動態代理類
public class CglibProxy implements MethodInterceptor {
private Object target;
public Object getProxyInstance(Object target){
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2,
MethodProxy arg3) throws Throwable {
// TODO Auto-generated method stub
System.out.println("start");
Object result = arg1.invoke(target, arg2);
System.out.println("end");
return result;
}
}
測試類:
public class Test {
public static void main(String[] args) {
new Test().testCglibProxy();
}
public void testCglibProxy(){
CglibSubject proxy = (CglibSubject) new CglibProxy().getProxyInstance(new CglibSubject());
proxy.add();
}
}
如果目標對象有實現接口,用JDK代理
如果目標對象沒有實現接口,用Cglib代理
@姍姍來遲