java幾種代理模式的實現方式

熬夜总冠军發表於2024-04-01

1. 代理模式

代理模式是常用的java設計模式,他的特徵是代理類與委託類有同樣的介面,代理類主要負責為委託類預處理訊息、過濾訊息、把訊息轉發給委託類,以及事後處理訊息等。代理類與委託類之間通常會存在關聯關係,一個代理類的物件與一個委託類的物件關聯,代理類的物件本身並不真正實現服務,而是透過呼叫委託類的物件的相關方法,來提供特定的服務。簡單的說就是,我們在訪問實際物件時,是透過代理物件來訪問的,代理模式就是在訪問實際物件時引入一定程度的間接性,因為這種間接性,可以附加多種用途。

2. 靜態代理

簡單來說代理模式就是將被代理類包裝起來然後重新實現相同的方法,並且呼叫原來方法的同時可以在方法前後新增一個新的處理。而這種包裝可以使用繼承或者組合來使用。當我們呼叫的時候需要使用的是代理類的物件來呼叫而不是原來的被代理物件。

靜態代理有兩種實現方式:

1、基於繼承的方式實現

2、基於介面方式實現

基於繼承實現靜態代理

透過繼承被代理物件,重寫被代理方法,可以對其進行代理。
優點:被代理類無需實現介面
缺點:只能代理這個類,要想代理其他類,要想代理其他類需要寫新的代理方法。
cglib動態代理就是採用這種方式對類進行代理。不過類是由cglib幫我們在記憶體中動態生成的。

/**
 * 被代理物件
 */
class UserDao{
    public int insert(){
        System.out.println("執行insert......");
        return 1;
    }
}

/**
 * 代理物件
 */
class UserDaoProxy extends UserDao{

    @Override
    public int insert() {
        System.out.println("代理執行方法前......");
        int result = super.insert();
        System.out.println("代理執行方法後......");
        return result;
    }
}

public class StaticProxyWithExtendsDemo {
    public static void main(String[] args) {
        UserDao userDao = new UserDaoProxy();
        userDao.insert();
    }
}

基於介面方式實現

  • 被代理類與代理類一定要實現同一介面。
  • 代理類需要將該介面作為屬性,例項化時需要傳入該介面的物件。
  • 優點:可以代理所有實現介面的類。
  • 缺點:被代理的類必須實現介面。
  • JDK動態代理就是採用的這種方式實現的。同樣的代理類是由JDK自動幫我們在記憶體生成的。
interface IUserDao{
    void insert();
}

/**
 * 被代理物件
 */
class UserDaoImpl implements IUserDao{
    @Override
    public void insert() {
        System.out.println("這裡執行了insert方法......");
    }
}

/**
 * 代理物件
 */
class UserDaoImplProxy implements IUserDao{
    private IUserDao userDao;
    public UserDaoImplProxy(IUserDao userDao){
        this.userDao = userDao;
    }

    @Override
    public void insert() {
        System.out.println("代理執行方法前.....");
        userDao.insert();
        System.out.println("代理執行方法後.....");
    }
}

public class StaticProxyWithInterfaceDemo {
    public static void main(String[] args) {
        //需要被代理的物件
        IUserDao userDao = new UserDaoImpl();
        //獲取代理物件
        IUserDao userDaoProxy = new UserDaoImplProxy(userDao);
        //代理物件執行方法
        userDaoProxy.insert();
    }
}

3.動態代理

動態代理其實本質還是 將被代理類包裝一層,生成一個具有新的相同功能的代理類。

但是與靜態代理不同的是,這個代理類我們自己定義的。而動態代理這個代理類是根據我們的提示動態生成的。

相比於靜態代理,動態代理的優勢在於可以很方便的對代理類的函式進行統一的處理,而不用修改每個代理類中的方法。

實現動態代理有幾種方案

  • JDK動態代理
  • CGLIB動態代理

3.1 JDK動態代理

透過java提供的Proxy類幫我們建立代理物件。
優點:可以生成所有實現介面的代理物件
缺點:JDK反射生成代理必須面向介面, 這是由Proxy的內部實現決定的。生成代理的方法中必須指定實現類的介面,它根據這個介面來實現代理類生成的所實現的介面。

interface IStudentDao{
    void insert();
}

//被代理物件
class StudentDaoImpl implements IStudentDao{
    @Override
    public void insert() {
        System.out.println("執行了insert方法......");
    }
}

class ProxyInvoke implements InvocationHandler{
    //需要被代理的物件
    Object target;
    public ProxyInvoke(Object target){
        this.target = target;
    }
    /**
     * nvoke方法的三個引數
     *     Object proxy:這個就是代理物件
     *     Method method:執行的方法的反射物件
     *     Object[] args:執行方法的引數
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理執行方法前......");
        method.invoke(target,args);
        System.out.println("代理執行方法後......");
        return null;
    }
}
public class DynamicProxyWithJDKDemo {
    public static void main(String[] args) {
        //需要被代理的物件
        IStudentDao studentDao = new StudentDaoImpl();
        //透過JDC的Proxy.newProxyInstance進行生成代理物件
        // 引數一: 被代理類物件
        // 引數二:介面類物件  被代理物件所實現的介面
        // 引數三:呼叫處理器。 被呼叫物件的那個方法被呼叫後該如何處理
        IStudentDao studentDaoProxy = (IStudentDao) Proxy.newProxyInstance(studentDao.getClass().getClassLoader(),studentDao.getClass().getInterfaces(),new ProxyInvoke(studentDao));
        studentDaoProxy.insert();
    }
}

如果業務邏輯不復雜,也可以進行簡寫:

public class DynamicProxyWithJDKDemo {
    public static void main(String[] args) {
        //需要被代理的物件
        IStudentDao studentDao = new StudentDaoImpl();
        IStudentDao studentDaoProxy = (IStudentDao)Proxy.newProxyInstance(studentDao.getClass().getClassLoader(), studentDao.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("代理執行方法前......");
                Object invoke = method.invoke(studentDao, args);
                System.out.println("代理執行方法後......");
                return invoke;
            }
        });
        studentDaoProxy.insert();
    }
}

interface IStudentDao{
    void insert();
}

class StudentDaoImpl implements IStudentDao{
    @Override
    public void insert() {
        System.out.println("執行了insert方法......");
    }
}

3.2 CGLib動態代理

CGLib(Code Generate Library) 與JDK動態代理不同的是,cglib生成代理是被代理物件的子類。因此它擁有繼承方法實現靜態代理的優點:不需要被代理物件實現某個介面。
缺點:不能給final類生成代理,因為final類無法擁有子類。

使用cglib生成代理類也很簡單,只要指定父類和回撥方法即可
首先需要引入依賴

<dependency>
     <groupId>cglib</groupId>
     <artifactId>cglib</artifactId>
     <version>3.2.12</version>
 </dependency>
public class DynamicProxyWithCgLibDemo {
    public static void main(String[] args) {
        TeacherDao teacherDao = new TeacherDao();
        TeacherDao teacherProxy = (TeacherDao) Enhancer.create(teacherDao.getClass(), new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                System.out.println("執行方法之前");
                proxy.invokeSuper(obj,args);
                System.out.println("執行方法之後");
                return null;
            }
        });
        teacherProxy.insert();
    }
}

class TeacherDao{
    public void insert(){
        System.out.println("執行了insert方法");
    }
}

相關文章