代理模式:動態代理與靜態代理
In the Spring Framework, an AOP proxy will be a JDK dynamic proxy or a CGLIB proxy。
動態代理的兩種方式:JDK動態代理與CGLIB代理
默認情況下,Spring AOP的實現對於接口來說就是使用的JDK的動態代理來實現的,而對於類的代理使用CGLIB來實現。
1.JDK動態代理
如何使用JDK動態代理。JDK提供了java.lang.reflect.Proxy類來實現動態代理的,可通過它的newProxyInstance來獲得代理實現類。同時對於代理的接口的實際處理,是一個java.lang.reflect.InvocationHandler,它提供了一個invoke方法供實現者提供相應的代理邏輯的實現。可以對實際的實現進行一些特殊的處理,像Spring AOP中的各種advice。下面來看看如何使用。
//被代理的接口
package com.mikan.proxy; public interface HelloWorld { void sayHello(String name); }
接口的實現類:
package com.mikan.proxy; public class HelloWorldImpl implements HelloWorld { @Override public void sayHello(String name) { System.out.println("Hello " + name); } }
實現一個java.lang.reflect.InvocationHandler:
package com.mikan.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class CustomInvocationHandler implements InvocationHandler { private Object target; public CustomInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Before invocation"); Object retVal = method.invoke(target, args); System.out.println("After invocation"); return retVal; } }
使用代理:
package com.mikan.proxy; import java.lang.reflect.Proxy; public class ProxyTest { public static void main(String[] args) throws Exception { System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); CustomInvocationHandler handler = new CustomInvocationHandler(new HelloWorldImpl()); HelloWorld proxy = (HelloWorld) Proxy.newProxyInstance(ProxyTest.class.getClassLoader(),new Class[]{HelloWorld.class}, handler); proxy.sayHello("Mikan"); } }
運行的輸出結果:
localhost:classes mikan$ java com/mikan/proxy/ProxyTest Before invocation Hello Mikan After invocation
從上面可以看出,JDK的動態代理使用起來非常簡單,但是只知道如何使用是不夠的,知其然,還需知其所以然。
所以要想搞清楚它的實現,那麼得從源碼入手。這裡的源碼是1.7.0_79。首先來看看它是如何生成代理類的:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException { if (h == null) { throw new NullPointerException(); } final Class<?>[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } // 這裡是生成class的地方 Class<?> cl = getProxyClass0(loader, intfs); // 使用我們實現的InvocationHandler作為參數調用構造方法來獲得代理類的實例 try { final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) { return AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { return newInstance(cons, ih); } }); } else { return newInstance(cons, ih); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); } } 其中newInstance只是調用Constructor.newInstance來構造相應的代理類實例,這裡重點是看getProxyClass0這個方法的實現: private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { // 代理的接口數量不能超過65535(沒有這種變態吧) if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } // JDK對代理進行了緩存,如果已經存在相應的代理類,則直接返回,否則才會通過ProxyClassFactory來創建代理 return proxyClassCache.get(loader, interfaces); }
可以看到,動態生成的代理類有如下特性:
1) 繼承了Proxy類,實現了代理的接口,由於java不能多繼承,這裡已經繼承了Proxy類了,不能再繼承其他的類,所以JDK的動態代理不支持對實現類的代理,只支持接口的代理。
2) 提供了一個使用InvocationHandler作為參數的構造方法。
3) 生成靜態代碼塊來初始化接口中方法的Method對象,以及Object類的equals、hashCode、toString方法。
4) 重寫了Object類的equals、hashCode、toString,它們都只是簡單的調用了InvocationHandler的invoke方法,即可以對其進行特殊的操作,也就是說JDK的動態代理還可以代理上述三個方法。
5) 代理類實現代理接口的sayHello方法中,只是簡單的調用了InvocationHandler的invoke方法,我們可以在invoke方法中進行一些特殊操作,甚至不調用實現的方法,直接返回。
2.JDK實現動態代理需要實現類通過接口定義業務方法,對於沒有接口的類,如何實現動態代理呢,這就需要CGLib了。CGLib采用了非常底層的字節碼技術,其原理是通過字節碼技術為一個類創建子類,並在子類中采用方法攔截的技術攔截所有父類方法的調用,順勢織入橫切邏輯。JDK動態代理與CGLib動態代理均是實現Spring AOP的基礎。
簡單的實現舉例:
這是一個需要被代理的類,也就是父類,通過字節碼技術創建這個類的子類,實現動態代理。
public class SayHello { public void say(){ System.out.println("hello everyone"); } }
該類實現了創建子類的方法與代理的方法。getProxy(SuperClass.class)方法通過入參即父類的字節碼,通過擴展父類的class來創建代理對象。intercept()方法攔截所有目標類方法的調用,obj表示目標類的實例,method為目標類方法的反射對象,args為方法的動態入參,proxy為代理類實例。proxy.invokeSuper(obj, args)通過代理類調用父類中的方法。
public class CglibProxy implements MethodInterceptor{ private Enhancer enhancer = new Enhancer(); public Object getProxy(Class clazz){ //設置需要創建子類的類 enhancer.setSuperclass(clazz); enhancer.setCallback(this); //通過字節碼技術動態創建子類實例 return enhancer.create(); } //實現MethodInterceptor接口方法 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("前置代理"); //通過代理類調用父類中的方法 Object result = proxy.invokeSuper(obj, args); System.out.println("後置代理"); return result; } }
具體實現類:
public class DoCGLib { public static void main(String[] args) { CglibProxy proxy = new CglibProxy(); //通過生成子類的方式創建代理類 SayHello proxyImp = (SayHello)proxy.getProxy(SayHello.class); proxyImp.say(); } }
輸出結果:
前置代理 hello everyone 後置代理
3.CGLIB與JDK動態代理之間的比較:
JDK代理需要實現某個接口,而CGLIB卻沒有此限制。
CGLib創建的動態代理對象性能比JDK創建的動態代理對象的性能高不少,但是CGLib在創建代理對象時所花費的時間卻比JDK多得多,所以對於單例的對象,因為無需頻繁創建對象,用CGLib合適,反之,使用JDK方式要更為合適一些。同時,由於CGLib由於是采用動態創建子類的方法,對於final方法,無法進行代理。
————————————————————————————————————————————
備注:aop pointcut表達式(*)
execution(方法修飾符+返回值 完整類名+方法名(方法參數))
例如:
A、execution(public void *(..)):所有返回值是public void的方法都會被攔截到
B、execution(public void day6.com.beans.PersonService.*(..)):表示day6.com.beans.PersonService下所有返回值是public void的方法都會被攔截到
C、execution(public void day6.com.beans.PersonService.save(java.lang.String...)):表示day6.com.beans.PersonService類中的第一個形參類型是String的save方法會被攔截到
D、execution(public void save(..)):表示所有類中的save方法會被攔截到
E、execution(public void day6.com.service..*(..)):表示day6.com.service包下的類以及子包的類所有public void方法都會被攔截到
F、execution(public !void day6.com.service..*(..)):表示day6.com.service包下的類以及子包的類所有public 不是void返回類型的方法都會被攔截到
////end