程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Spring:動態代理,spring動態代理

Spring:動態代理,spring動態代理

編輯:JAVA綜合教程

Spring:動態代理,spring動態代理


代理模式:動態代理與靜態代理
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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved