1. 程式人生 > >動態代理及其兩種實現方式(JDK、CGLIB)

動態代理及其兩種實現方式(JDK、CGLIB)

什麼是代理模式

為某物件提供一個代理,從而通過代理來訪問這個物件。

代理模式的角色組成

代理模式有三種角色組成:

抽象角色:通過介面或抽象類宣告真實角色實現的業務方法。
代理角色:實現抽象角色,是真實角色的代理,通過真實角色的業務邏輯方法來實現抽象方法,並可以附加自己的操作。
真實角色:實現抽象角色,定義真實角色所要實現的業務邏輯,供代理角色呼叫。

我的總結:

抽象角色就是一個介面或抽象類,定義一些方法;
真實角色就是對抽象角色以及其中的方法進行實現;
代理角色也要實現抽象角色,並且注入真實角色,提供與真實角色同名的方法,再在代理角色的同名方法中通過注入的真實角色呼叫真實角色的方法。

代理模式的作用

高擴充套件性:
比如一個介面A中的方法需要被兩個真實角色(B、C)呼叫,現在有新的需求,需要修改B中實現介面A的某方法m,直接修改B會影響系統的穩定性,建立代理ProxyB實現介面A,並將真實物件B注入進來。ProxyB實現介面方法m,另外還可以增加附加行為,然後呼叫真實物件B的m。從而達到了“對修改關閉,對擴充套件開放”,保證了系統的穩定性。

職責清晰:典型的例子就是AOP的應用,在一個繼承了某介面的業務類呼叫時,為了記錄日誌,建立這個業務類的代理類,並在代理類中注入這個業務類,並實現這個介面的方法,在方法中可以加入日誌記錄的程式碼,然後再通過注入的業務類呼叫業務類中的方法,從而實現了日誌功能的新增。

靜態代理及實現

概念:由程式設計師建立或工具生成代理類的原始碼,再編譯代理類。

實現:

來個例子看看
1.抽象角色(介面):

package com.proxy.test;

public interface UserService {
    public void addUser();

}

2.真實角色(實現類):

package com.proxy.test;

public class UserServiceImpl implements UserService{

    @Override
    public void addUser() {
        System.out.println("增加使用者中..."
); } }

3.代理角色(實現代理類)

package com.proxy.test;

public class UserServiceProxy implements UserService{

          private UserService userService;

          public UserServiceProxy(UserService userService){
            this.setUserService(userService);
          }

        public UserService getUserService() {
            return userService;
        }

        public void setUserService(UserService userService) {
            this.userService = userService;
        }



        @Override
        public void addUser() {
        System.out.println("增加使用者準備...");
        userService.addUser();
        System.out.println("增加使用者結束...");            
        }
}

4.執行結果:

增加使用者準備...
增加使用者中...
增加使用者結束...

有沒有很像日誌記錄功能的實現呢?

但是靜態代理也有一些缺點:
1.一旦介面增加一個方法後,所以實現該介面的類都要實現這個方法,當然也包括代理類,使程式碼維護困難。
2.代理物件只能服務於某一物件,如果需要代理的物件很多,則程式碼量很大。
此時我們需要動態代理。

JDK動態代理及實現

原理:

Java 反射機制可以生成任意型別的動態代理類。java.lang.reflect 包下面的Proxy類和InvocationHandler 介面提供了生成動態代理類的能力。

實踐:

1.抽象角色(建立一個介面):

package com.jdkproxy.test;

public interface UserService {
    public void addUser();
}

2.真實角色(實現介面):

package com.jdkproxy.test;

import com.proxy.test.UserService;

public class UserServiceImpl implements UserService{

    @Override
    public void addUser() {
        System.out.println("增加使用者中...");

    }
}

3.代理角色:

package com.jdkproxy.test;

public class addLogs {
    public void addLog(){
        System.out.println("增加使用者日誌啟動");
    }
}

4.動態代理類(必須實現InvocationHandler介面)

package com.jdkproxy.test;

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

public class JDKProxy implements InvocationHandler {
    //目標物件
    private Object target; 
    private addLogs logger = new addLogs();

    public JDKProxy() {
    }

    public Object getTarget() {
        return target;
    }

    public void setTarget(Object target) {
        this.target = target;
    }

    public addLogs getLogger() {
        return logger;
    }

    public void setLogger(addLogs logger) {
        this.logger = logger;
    }

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

    // 引數1 將來所產生的代理物件 Proxy
    // 引數2 將來需要呼叫到的目標物件裡面真正的那個方法的映象
    // 引數3 將來呼叫方法的時候所傳的引數
    public Object invoke(Object proxy, Method m, Object[] args)
            throws Throwable {
        // 獲得將來所呼叫方法的名字
        String methodName = m.getName();
        // 用日誌記錄輸出一下
        System.out.println(methodName+"is invocation");
        logger.addLog();
        // 用反射的方式去呼叫將來需要真正呼叫的方法.
        Object o = m.invoke(target, args);

        return o;
    }

   }

5.測試

package com.jdkproxy.test;

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

import com.proxy.test.UserService;
import com.proxy.test.UserServiceImpl;

public class TestJDKProxy {

    public static void main(String[] args){
        UserService userService=new UserServiceImpl();



            Class c = userService.getClass();
            //獲得目標物件的類載入器物件
            ClassLoader classLoader = c.getClassLoader();

            //獲得目標物件所實現的所有介面
            Class[] interfaces = c.getInterfaces();

            //獲得一個InvocationHandler介面的實現類物件,並把目標物件傳進去
            InvocationHandler h = 
                    new JDKProxy(userService);

            //引數1 目標物件的類載入器物件
            //引數2 目標物件所實現的所有介面. Class型別陣列
            //引數3 InvocationHandler介面的實現類物件
            UserService proxy = 
                (UserService)Proxy.newProxyInstance
                (classLoader, interfaces, h);
            //這裡的proxy是一個實現了IStudentService介面動態生成的代理類的物件
            proxy.addUser();
    }
}

6.執行結果:

addUseris invocation
增加使用者日誌啟動
增加使用者中...

7.解析:
我們來看下動態代理類:

public Object invoke(Object proxy, Method m, Object[] args)
            throws Throwable {
        // 獲得將來所呼叫方法的名字
        String methodName = m.getName();
        // 用日誌記錄輸出一下
        System.out.println(methodName+"is invocation");
        logger.addLog();
        // 用反射的方式去呼叫將來需要真正呼叫的方法.
        Object o = m.invoke(target, args);

        return o;
    }

每一個動態代理類都必須要實現InvocationHandler這個介面,並且每個代理類的例項都關聯到了一個handler。

當我們通過代理物件呼叫一個方法的時候,這個方法的呼叫就會被轉發為由InvocationHandler這個介面的 invoke 方法來進行呼叫

public Object invoke(Object proxy, Method m, Object[] args)
            throws Throwable {}

proxy:  指代我們所代理的那個真實物件
method:  指代的是我們所要呼叫真實物件的某個方法的Method物件
args:  指代的是呼叫真實物件某個方法時接受的引數
程式碼中通過反射的方法
Object o = m.invoke(target, args);
去呼叫真實角色的方法,在這個方法前後我們可以加入日誌的記錄等操作。

測試類中:

    public static void main(String[] args){
        UserService userService=new UserServiceImpl();



            Class c = userService.getClass();
            //獲得目標物件的類載入器物件
            ClassLoader classLoader = c.getClassLoader();

            //獲得目標物件所實現的所有介面
            Class[] interfaces = c.getInterfaces();

            //獲得一個InvocationHandler介面的實現類物件,並把目標物件傳進去
            InvocationHandler h = 
                    new JDKProxy(userService);

            //引數1 目標物件的類載入器物件
            //引數2 目標物件所實現的所有介面. Class型別陣列
            //引數3 InvocationHandler介面的實現類物件
            UserService proxy = 
                (UserService)Proxy.newProxyInstance
                (classLoader, interfaces, h);
            //這裡的proxy是一個實現了IStudentService介面動態生成的代理類的物件
            proxy.addUser();
    }

核心程式碼為:

 UserService proxy = 
                (UserService)Proxy.newProxyInstance
                (classLoader, interfaces, h);

Proxy這個類的作用就是用來動態建立一個代理物件的類

而Proxy的newProxyInstance便得到一個動態的代理物件,傳入三個引數:

classLoader:  一個ClassLoader物件,定義了由哪個ClassLoader物件來對生成的代理物件進行載入

interfaces:  一個Interface物件的陣列,表示的是我將要給我需要代理的物件提供一組什麼介面,如果我提供了一組介面給它,那麼這個代理物件就宣稱實現了該介面(多型),這樣我就能呼叫這組介面中的方法了

h:  一個InvocationHandler物件,表示的是當我這個動態代理物件在呼叫方法的時候,會關聯到哪一個InvocationHandler物件上

再看這一句:

InvocationHandler h = 
                    new JDKProxy(userService);

CGLIB動態代理及實現

一個問題:

以上的JDK代理和靜態代理所代理的類都是實現了某介面的,對於沒有實現介面的類,我們使用動態代理時就可以將CGLIB拿來了~

原理:

通過位元組碼技術為需要代理的目標物件建立一個子類物件,並在子類物件中攔截所有父類(即需要代理的類)方法的呼叫,然後在方法呼叫前後呼叫後都可以加入自己想要執行的程式碼。

但因為採用的是繼承,所以不能對final修飾的類和final方法進行代理。

實現

1、建立方法類(需要被代理的類)

package com.cglib.test;

public class RunMethod {
    public void Run(){
        System.out.println("執行被代理類的方法");
    }
}

2、建立cglib代理類

package com.cglib.test;

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 CglibProxy implements MethodInterceptor{

    public Object getProxy(Class clazz){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
         return enhancer.create();
    }

    @Override
    public Object intercept(Object arg0, Method arg1, Object[] arg2,
            MethodProxy arg3) throws Throwable {
        System.out.println("執行被代理類之前要做的事情");
        Object result = arg3.invokeSuper(arg0, arg2);
        System.out.println("執行被代理類之後要做的事情");

        return result;
    }
}

我們來看下這個代理類,首先要實現MethodInterceptor介面,並重寫intercept方法。

getProxy(Class clazz){}方法通過傳入需要代理的類RunMethod 的Class物件來建立一個RunMethod 的子類的物件。

intercept()方法負責攔截所有需要代理的類RunMethod 中方法的呼叫。

intercept方法的引數:
arg0:生成的代理物件。
arg1:需要代理的類RunMethod (父類)中方法的反射物件。
arg2:方法入參。
arg3:代理類(子類)中方法的反射物件。

3、測試

package com.cglib.test;

public class TestCglib {
    public static void main(String[] args){
        CglibProxy proxy = new CglibProxy();
          //通過生成子類的方式建立代理類
          RunMethod proxyImp = (RunMethod)proxy.getProxy(RunMethod.class);
          proxyImp.Run();
    }
}

4、執行結果

執行被代理類之前要做的事情
執行被代理類的方法
執行被代理類之後要做的事情

附:

2、動態代理是實現AOP(面向切面程式設計)的技術支援

get then deeper~