代理模式詳解

blue星空 發表於 2020-11-22
       
        代理模式為物件提供一個代理以控制對這個物件的訪問。所謂代理,是指與代理元(即:被代理的物件)具有相同介面的類,客戶端必須通過代理與代理元進行互動。我們可以將代理理解成另一個物件的代表
        代理(proxy)模式本質上就是通過一個代理物件訪問目標物件,而不直接訪問目標物件;代理模式可以在不修改被代理物件的基礎上,通過擴充套件代理類,進行一些功能的附加與增強。這麼做的好處是,可以讓目標物件只關注業務邏輯,而非業務邏輯(比如日誌輸出等)可以在代理物件中實現,實現業務分離。
代理模式詳解

 

代理模式要點:
  • 代理模式包含三個要素:1)介面/Interface; 2)代理元/Target:即被代理的物件; 3)代理類/Proxy:代理元的代表,對外提供訪問;
  • 代理元與代理類都是介面的實現類;
  • 客戶端只能訪問代理類,無法直接訪問代理元;

 

代理分為靜態代理和動態代理。

 


靜態代理

 
        靜態代理就是在編譯時生成代理類。即:通過編碼手動建立代理類,並在代理類中呼叫代理元。
 
編碼實現
 
1.介面
package effectiveJava.proxy;

public interface HelloService {
    void sayHello();
}

2,代理元

package effectiveJava.proxy;

public class HelloServiceImpl implements HelloService{
    @Override
    public void sayHello() {
        System.out.println("Hello Proxy.");
    }
}

3,代理類

package effectiveJava.proxy.staticProxy;

import effectiveJava.proxy.HelloService;

public class LogProxy implements HelloService {

    private HelloService service;

    public LogProxy(HelloService service) {
        this.service = service;
    }

    @Override
    public void sayHello() {
        System.out.println("Static Proxy : Before Hello....");
        service.sayHello();
        System.out.println("Static Proxy : After Hello....");
    }
}

4,測試類

package effectiveJava.proxy.staticProxy;

import effectiveJava.proxy.HelloServiceImpl;

public class LogProxyDemo {
    public static void main(String[] args) {
        LogProxy logProxy = new LogProxy(new HelloServiceImpl());
        logProxy.sayHello();
    }
}

5,測試結果

Static Proxy : Before Hello....
Hello Proxy.
Static Proxy : After Hello....

 


動態代理


 

        動態代理地實現有兩種方式:1)基於JDK的動態代理;2)基於CGLIB的動態代理;

 

1)基於JDK的動態代理

 

API

public class Proxy implements java.io.Serializable {
     /**
   * 建立代理例項
      *  @param loader 代理元的類載入器
      *  @param interfaces 代理元的介面
      *  h 一個 InvocationHandler 物件
      */
     
   public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);

}
/**
 *
 * 每個代理的例項都有一個與之關聯的 InvocationHandler 實現類,
 * 如果代理的方法被呼叫,那麼代理便會通知和轉發給內部的 InvocationHandler 實現類,由它決定處理。
 */
public interface InvocationHandler {

    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

 

編碼實現

1,介面、代理元(編碼與靜態代理一致,不再贅述)
2,代理類(必須實現InvocationHandler介面)
package effectiveJava.proxy.v0;

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

public class HelloInvocation implements InvocationHandler {
    /**
     * 代理元
     */
    private Object target;

    public HelloInvocation(Object target) {
        this.target = target;
    }

    /**
     *
     * @param proxy 代理類例項
     * @param method 實際要呼叫的方法
     * @param args  實際要呼叫方法的引數型別
     * @return 結果值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("HelloInvocation : Before Hello....");
        Object reslut = method.invoke(target, args);
        System.out.println("HelloInvocation : After Hello....");
        return reslut;
    }

}

3,測試類

package effectiveJava.proxy.v0;

import effectiveJava.proxy.HelloService;
import effectiveJava.proxy.HelloServiceImpl;

import java.lang.reflect.Proxy;

/**
* 通過Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)建立代理物件的例項
*/
public class HelloInvocationDemo {
    public static void main(String[] args) {
        HelloServiceImpl helloService = new HelloServiceImpl();
        HelloInvocation helloInvocation = new HelloInvocation(helloService);
        HelloService impl = (HelloService)Proxy.newProxyInstance(
                helloService.getClass().getClassLoader(),
                helloService.getClass().getInterfaces(),
                helloInvocation);
        impl.sayHello();
    }
}

4,測試結果

HelloInvocation : Before Hello....
Hello Proxy.
HelloInvocation : After Hello....

 

2)基於CGLIB的動態代理

   Cglib是基於繼承的方式進行代理,代理類去繼承目標類,每次呼叫代理類的方法都會被方法攔截器攔截,在攔截器中才是呼叫目標類的該方法的邏輯。因此,基於CGLIB的動態代理不需要介面。

代理模式詳解

 

 

編碼實現

1,引入架包

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>
2,代理類(HelloServiceImpl.java,編碼與靜態代理一致)
 3, 方法攔截器
package effectiveJava.proxy.cglib;

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

import java.lang.reflect.Method;

public class LogInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("Cglib : Before hello..."); 
        //呼叫父委方法
        Object result = methodProxy.invokeSuper(object, args);
        System.out.println("Cglib : After hello...");
        return result;
    }
}

4,測試類

package effectiveJava.proxy.cglib;

import effectiveJava.proxy.HelloService;
import effectiveJava.proxy.HelloServiceImpl;
import net.sf.cglib.proxy.Enhancer;

public class CglibDemo {
    public static void main(String[] args) {
        //建立Enhancer物件,類似於JDK動態代理的Proxy類
        Enhancer enhancer = new Enhancer();
        //設定父類
        enhancer.setSuperclass(HelloServiceImpl.class);
        //設定回撥函式(攔截器)
        enhancer.setCallback(new LogInterceptor());
        //建立代理類例項
        HelloService service = (HelloService)enhancer.create();
        service.sayHello();
    }
}

5,測試結果

Cglib : Before hello...
Hello Proxy.
Cglib : After hello...
總結:
  • 靜態代理是在編譯時建立代理,動態代理是在執行時建立代理;
  • JDK動態代理是基於介面的方式,CGLib動態代理是基於繼承;