JDK動態代理實現的兩種方式(代理模式Proxy)


Java領域中,常用的動態代理實現方式有兩種,一種是利用JDK反射機制生成代理,另外一種是使用CGLIB代理。

JDK代理必須要提供接口,而CGLIB則不需要,可以直接代理類。

定義

代理模式是對象的結構模式。代理模式給某一個對象提供代理對象,並由代理對象控制對源對象的引用。

代理模式的結構

所謂的代理,就是一個人或者一個機構代表另外一個人或者另外一個機構采取行動。在一些情況下,一個客戶不想或者不能夠直接引用一個對象,而代理對象可以在客戶端和目標對象中間起到中介的作用。

動態代理

動態代理主要有如下特點:

  • 代理對象不需要實現目標對象的接口。
  • 代理對象的生成,使用的是Java的API,動態的在內存中構件代理對象(這需要我們指定創建代理對象/目標對象的接口的類型)。
  • 動態代理也叫做JDK代理、接口代理。

   JDK中生成代理對象的API

       代理類所在的包為:java.lang.reflect.Proxy

       JDK實現代理只需要使用newProxyInstance方法,但是該方法需要接收三個參數,源碼中的方法定義為:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
{
    //......
}

  

  注意,該方法在Proxy類中是靜態方法,且接收的三個參數依次為:

  • ClassLoader loader:指定當前目標對象使用類加載器,獲取加載器的方法是固定的。
  • Class<?>[] interfaces:目標對象實現的接口類型,使用泛型方式確認類型。
  • InvocationHandler h:事件處理。執行目標對象的方法時,會觸發事件處理器的方法,會把當前執行目標對象的方法作為參數傳入。

示例代碼

  MyService:

public interface MyService {
    
    /**
     * 吃飯
     */
    public void eat();
    
    /**
     * 睡覺
     */
    public void sleep();
    
}

 

  MyServiceImpl:
public class MyServiceImpl implements MyService {

    @Override
    public void eat() {
        System.out.println("一日三餐");
    }

    @Override
    public void sleep() {
        System.out.println("每天八小時睡眠,不然會猝死");
    }

}

  

  MyProxy:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MyProxy {

    //維護一個目標對象
    private Object target;
    
    //對象構造時,提供目標對象
    public MyProxy(Object target) {
        this.target = target;
    }
    //給目標對象生成代理對象
    public Object getProxyInstance() {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(), 
                target.getClass().getInterfaces(), 
                new InvocationHandler() {
                    
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("日志打印: before");
                        //執行目標對象方法
                        Object returnValue = method.invoke(target, args);
                        System.out.println("日志打印: after");
                        return returnValue;
                    }
                });
    }
    
    public static void main(String[] args) {
        MyService myService = new MyServiceImpl();
        MyService myProxy = (MyService)new MyProxy(myService).getProxyInstance();
        myProxy.eat();
        myProxy.sleep();
    }
    
}

 

 

Cglib代理

上面的靜態代理和動態代理模式都需要目標對象是一個實現了接口的目標對象,但是有的時候,目標對象可能只是一個單獨的對象,並沒有實現任何的接口,這個時候,我們就可以使用目標對象子類的方式實現代理,這種代理方式就是:Cglib代理

定義

Cglib代理,也叫做子類代理,它是在內存中構件一個子類對象,從而實現對目標對象的功能拓展。

  • JDK的動態代理有個限制,就是使用動態代理的目標對象必須實現至少一個接口,由此,沒有實現接口但是想要使用代理的目標對象,就可以使用Cglib代理。
  • Cglib是強大的高性能的代碼生成包,它可以在運行期間拓展Java類與實現Java接口。它廣泛的被許多AOP的框架使用,例如Spring AOP和synaop,為他們提供方法的interception(攔截)。
  • Cglib包的底層是通過使用一個小而快的字節碼處理框架ASM來轉換字節碼並生成新的類,不鼓勵直接只使用ASM,因為它要求你必須對JVM內部結構,包括class文件的格式和指令集都很熟悉。

Cglib子類代理的實現方法

  1. 需要引入Cglib的jar文件,在Maven中可以直接在POM.xml中添加下列引用即可。
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.5</version>
        </dependency>

  如果不是maven項目,除了到cglib包還需要導入asm.jar包,不然會報異常:

Exception in thread "main" java.lang.NoClassDefFoundError: org/objectweb/asm/Type
    at net.sf.cglib.core.TypeUtils.parseType(TypeUtils.java:180)
    at net.sf.cglib.core.KeyFactory.<clinit>(KeyFactory.java:66)
    at net.sf.cglib.proxy.Enhancer.<clinit>(Enhancer.java:69)
    at proxy.CGlibProxy.getProxy(CGlibProxy.java:13)
    at proxy.CGlibProxy.main(CGlibProxy.java:36)
Caused by: java.lang.ClassNotFoundException: org.objectweb.asm.Type
    at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
    ... 5 more

代碼示例:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class MyProxyByGclib implements MethodInterceptor {
    
    //維護目標對象
    private Object target;
    
   
    public MyProxyByGclib(Object target) {
        this.target = target;
    }
    
    public Object getProxyInstance() {
        //1. 實例化工具類
        Enhancer en = new Enhancer();
        //2. 設置父類對象
        en.setSuperclass(this.target.getClass());
        //3. 設置回調函數
        en.setCallback(this);
        //4. 創建子類,也就是代理對象
        return en.create();
    }

    @Override
    public Object intercept(Object arg0, Method method, Object[] objects, MethodProxy arg3) throws Throwable {
        System.out.println("Begin Transaction");
        //執行目標對象的方法
        Object returnValue = method.invoke(target, objects);
        System.out.println("End Transaction");
        return returnValue;
    }
    
    public static void main(String[] args) {
        //目標對象
        MyServiceImpl myService = new MyServiceImpl();
        //生成代理對象
        MyServiceImpl myProxy = (MyServiceImpl)new MyProxyByGclib(myService).getProxyInstance();
        //調用對象方法
        myProxy.eat();
        myProxy.sleep();
    }

}

在Spring的AOP編程中:

  • 如果加入容器的目標對象有實現接口,就使用JDK代理
  • 如果目標對象沒有實現接口,就使用Cglib代理。

推薦閱讀:https://www.jianshu.com/p/305c8da4563d

      https://www.cnblogs.com/ygj0930/p/6542259.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM