Java三種代理模式:靜態代理、動態代理和cglib代理

1、代理模式介紹

代理模式是一種設計模式,提供了對目標對象額外的訪問方式,即經過代理對象訪問目標對象,這樣能夠在不修改原目標對象的前提下,提供額外的功能操做,擴展目標對象的功能。html

簡言之,代理模式就是設置一箇中間代理來控制訪問原目標對象,以達到加強原對象的功能和簡化訪問方式。java

代理模式UML類圖git

代理模式UML類圖

舉個例子,咱們生活中常常到火車站去買車票,可是人一多的話,就會很是擁擠,因而就有了代售點,咱們能從代售點買車票了。這其中就是代理模式的體現,代售點代理了火車站對象,提供購買車票的方法。github

2、靜態代理

這種代理方式須要代理對象和目標對象實現同樣的接口。spring

優勢:能夠在不修改目標對象的前提下擴展目標對象的功能。設計模式

缺點:api

  1. 冗餘。因爲代理對象要實現與目標對象一致的接口,會產生過多的代理類。
  2. 不易維護。一旦接口增長方法,目標對象與代理對象都要進行修改。

舉例:保存用戶功能的靜態代理實現bash

  • 接口類: IUserDao
package com.proxy;

public interface IUserDao {
    public void save();
}
  • 目標對象:UserDao
package com.proxy;

public class UserDao implements IUserDao{

    @Override
    public void save() {
        System.out.println("保存數據");
    }
}
  • 靜態代理對象:UserDapProxy 須要實現IUserDao接口!
package com.proxy;

public class UserDaoProxy implements IUserDao{

    private IUserDao target;
    public UserDaoProxy(IUserDao target) {
        this.target = target;
    }
    
    @Override
    public void save() {
        System.out.println("開啓事務");//擴展了額外功能
        target.save();
        System.out.println("提交事務");
    }
}
  • 測試類:TestProxy
package com.proxy;

import org.junit.Test;

public class StaticUserProxy {
    @Test
    public void testStaticProxy(){
        //目標對象
        IUserDao target = new UserDao();
        //代理對象
        UserDaoProxy proxy = new UserDaoProxy(target);
        proxy.save();
    }
}
  • 輸出結果
開啓事務
保存數據
提交事務

3、動態代理

動態代理利用了JDK API,動態地在內存中構建代理對象,從而實現對目標對象的代理功能。動態代理又被稱爲JDK代理或接口代理。框架

靜態代理與動態代理的區別主要在:maven

  • 靜態代理在編譯時就已經實現,編譯完成後代理類是一個實際的class文件
  • 動態代理是在運行時動態生成的,即編譯完成後沒有實際的class文件,而是在運行時動態生成類字節碼,並加載到JVM中

特色:
動態代理對象不須要實現接口,可是要求目標對象必須實現接口,不然不能使用動態代理。

JDK中生成代理對象主要涉及的類有

static Object    newProxyInstance(ClassLoader loader,  //指定當前目標對象使用類加載器

 Class<?>[] interfaces,    //目標對象實現的接口的類型
 InvocationHandler h      //事件處理器
) 
//返回一個指定接口的代理類實例,該接口能夠將方法調用指派到指定的調用處理程序。
Object    invoke(Object proxy, Method method, Object[] args) 
// 在代理實例上處理方法調用並返回結果。

舉例:保存用戶功能的動態代理實現

  • 接口類: IUserDao
package com.proxy;

public interface IUserDao {
    public void save();
}
  • 目標對象:UserDao
package com.proxy;

public class UserDao implements IUserDao{

    @Override
    public void save() {
        System.out.println("保存數據");
    }
}
  • 動態代理對象:UserProxyFactory
package com.proxy;

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

public class ProxyFactory {

    private Object target;// 維護一個目標對象

    public ProxyFactory(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("開啓事務");

                        // 執行目標對象方法
                        Object returnValue = method.invoke(target, args);

                        System.out.println("提交事務");
                        return null;
                    }
                });
    }
}
  • 測試類:TestProxy
package com.proxy;

import org.junit.Test;

public class TestProxy {

    @Test
    public void testDynamicProxy (){
        IUserDao target = new UserDao();
        System.out.println(target.getClass());  //輸出目標對象信息
        IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
        System.out.println(proxy.getClass());  //輸出代理對象信息
        proxy.save();  //執行代理方法
    }
}
  • 輸出結果
class com.proxy.UserDao
class com.sun.proxy.$Proxy4
開啓事務
保存數據
提交事務

4、cglib代理

cglib is a powerful, high performance and quality Code Generation Library. It can extend JAVA classes and implement interfaces at runtime.

cglib (Code Generation Library )是一個第三方代碼生成類庫,運行時在內存中動態生成一個子類對象從而實現對目標對象功能的擴展。

cglib特色

  • JDK的動態代理有一個限制,就是使用動態代理的對象必須實現一個或多個接口。
    若是想代理沒有實現接口的類,就可使用CGLIB實現。
  • CGLIB是一個強大的高性能的代碼生成包,它能夠在運行期擴展Java類與實現Java接口。
    它普遍的被許多AOP的框架使用,例如Spring AOP和dynaop,爲他們提供方法的interception(攔截)。
  • CGLIB包的底層是經過使用一個小而快的字節碼處理框架ASM,來轉換字節碼並生成新的類。
    不鼓勵直接使用ASM,由於它須要你對JVM內部結構包括class文件的格式和指令集都很熟悉。

cglib與動態代理最大的區別就是

  • 使用動態代理的對象必須實現一個或多個接口
  • 使用cglib代理的對象則無需實現接口,達到代理類無侵入。

使用cglib須要引入cglib的jar包,若是你已經有spring-core的jar包,則無需引入,由於spring中包含了cglib。

  • cglib的Maven座標
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.5</version>
</dependency>

舉例:保存用戶功能的動態代理實現

  • 目標對象:UserDao
package com.cglib;

public class UserDao{

    public void save() {
        System.out.println("保存數據");
    }
}
  • 代理對象:ProxyFactory
package com.cglib;

import java.lang.reflect.Method;

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

public class ProxyFactory implements MethodInterceptor{

    private Object target;//維護一個目標對象
    public ProxyFactory(Object target) {
        this.target = target;
    }
    
    //爲目標對象生成代理對象
    public Object getProxyInstance() {
        //工具類
        Enhancer en = new Enhancer();
        //設置父類
        en.setSuperclass(target.getClass());
        //設置回調函數
        en.setCallback(this);
        //建立子類對象代理
        return en.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("開啓事務");
        // 執行目標對象的方法
        Object returnValue = method.invoke(target, args);
        System.out.println("關閉事務");
        return null;
    }
}
  • 測試類:TestProxy
package com.cglib;

import org.junit.Test;

public class TestProxy {

    @Test
    public void testCglibProxy(){
        //目標對象
        UserDao target = new UserDao();
        System.out.println(target.getClass());
        //代理對象
        UserDao proxy = (UserDao) new ProxyFactory(target).getProxyInstance();
        System.out.println(proxy.getClass());
        //執行代理對象方法
        proxy.save();
    }
}
  • 輸出結果
class com.cglib.UserDao
class com.cglib.UserDao$$EnhancerByCGLIB$$552188b6
開啓事務
保存數據
關閉事務

5、總結

  1. 靜態代理實現較簡單,只要代理對象對目標對象進行包裝,便可實現加強功能,但靜態代理只能爲一個目標對象服務,若是目標對象過多,則會產生不少代理類。
  2. JDK動態代理須要目標對象實現業務接口,代理類只需實現InvocationHandler接口。
  3. 動態代理生成的類爲 lass com.sun.proxy.\$Proxy4,cglib代理生成的類爲class com.cglib.UserDao\$\$EnhancerByCGLIB\$\$552188b6。
  4. 靜態代理在編譯時產生class字節碼文件,能夠直接使用,效率高。
  5. 動態代理必須實現InvocationHandler接口,經過反射代理方法,比較消耗系統性能,但能夠減小代理類的數量,使用更靈活。
  6. cglib代理無需實現接口,經過生成類字節碼實現代理,比反射稍快,不存在性能問題,但cglib會繼承目標對象,須要重寫方法,因此目標對象不能爲final類。

6、相關資料

代理模式相關知識

UML相關知識

相關文章
相關標籤/搜索