設計模式(一)三種代理模式以及什麼時候使用

1.爲什麼要使用動態代理?

每個解決方案都是爲了去解決一個問題,那麼爲什麼使用動態代理呢,那就要看他解決了什麼問題
日誌:在程序執行期間追蹤正在發生的活動
什麼是代理模式
先說動態代理之jdk代理

1.1 實現

Spring AOP實現方式有兩種

1:使用JDK動態代理,如果被代理的目標實現了至少一個接口,則會使用JDK動態代理,所有該目標類型實現的接口都將被代理。

2:通過CGLIB來爲目標對象創建代理,若該目標對象沒有實現任何接口,則創建一個CGLIB代理,創建的代理類是目標類的子類。

鏈接:https://www.jianshu.com/p/a0c349e813eb

2.動態代理使用前提:

標記爲final的方法,不能被代理,因爲無法進行覆蓋
代理對象不需要實現接口,但是目標對象一定要實現接口,否則不能用動態代理。
實現與解釋:

 代理類所在包:java.lang.reflect.Proxy
  JDK實現代理只需要使用newProxyInstance方法,但是該方法需要接收三個參數,完整的寫法是:

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
注意該方法是在Proxy類中是靜態方法,且接收的三個參數依次爲:

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

在這裏插入圖片描述

2.1動態代理實現(代碼)

JrmkApplyService jrmkApplyproxy = (JrmkApplyService) Proxy.newProxyInstance(JrmkApplyService.class.getClassLoader(), new
        Class[]{JrmkApplyService.class}, new DynamicProxyHandler(JrmkApplyServiceImpl));
package com.sdecloud.efpx.manager.handler;

import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

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

/**
 *
 *    * @className: DynamicProxyHandler
 *   * @description:TODO
 *   * @param:
 *   * @return:
 *   * @throws:
 *   * @author: lizz
 *   * @date: 2020/06/05 11:07
 *
 */
@Transactional
public class DynamicProxyHandler  implements InvocationHandler {
            private Object object;
            public DynamicProxyHandler(final Object object) {
                 this.object = object;
            }
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                 System.out.println("before");

                 Object result = method.invoke(object, args);
                 if("1".equals("1")){
                     throw new Throwable();
                 }
                 System.out.println("after");
                 return result;
             }
}
public interface ArithmeticCalculator {

int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
**然後寫接口的實現類**

public class ArithmeticCalculatorImpl implements ArithmeticCalculator {

    @Override
    public int add(int i, int j) {
        int result = i + j;
        return result;
    }

    @Override
    public int sub(int i, int j) {
        int result = i - j;
        return result;
    }

    @Override
    public int mul(int i, int j) {
        int result = i * j;
        return result;
    }

    @Override
    public int div(int i, int j) {
        int result = i / j;
        return result;
    }

}

**然後寫實現動態代理的類**
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class ArithmeticCalculatorLoggingProxy {

    //要代理的對象
    private ArithmeticCalculator target;

    public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target) {
        super();
        this.target = target;
    }

    //返回代理對象
    public ArithmeticCalculator getLoggingProxy(){
        ArithmeticCalculator proxy = null;

        ClassLoader loader = target.getClass().getClassLoader();
        Class [] interfaces = new Class[]{ArithmeticCalculator.class};//這個class是代理對象實現的接口
        InvocationHandler h = new InvocationHandler() {
            /**
             * proxy: 代理對象。 一般不使用該對象
             * method: 正在被調用的方法
             * args: 調用方法傳入的參數
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                String methodName = method.getName();
                //打印日誌
                System.out.println("[before] The method " + methodName + " begins with " + Arrays.asList(args));

                //調用目標方法
                Object result = null;

                try {
                    //前置通知
                    result = method.invoke(target, args);
                    //返回通知, 可以訪問到方法的返回值
                } catch (NullPointerException e) {
                    e.printStackTrace();
                    //異常通知, 可以訪問到方法出現的異常
                }

                //後置通知. 因爲方法可以能會出異常, 所以訪問不到方法的返回值

                //打印日誌
                System.out.println("[after] The method ends with " + result);

                return result;
            }
        };

        /**
         * loader: 代理對象使用的類加載器。 
         * interfaces: 指定代理對象的類型. 即代理代理對象中可以有哪些方法. 
         * h: 當具體調用代理對象的方法時, 應該如何進行響應, 實際上就是調用 InvocationHandler 的 invoke 方法
         */
        proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader, interfaces, h);

        return proxy;
    }
}

public static void main(String[] args) {
    ArithmeticCalculator arithmeticCalculator = new ArithmeticCalculatorImpl();
                  arithmeticCalculator = ArithmeticCalculatorLoggingProxy(arithmeticCalculator).getLoggingProxy();
    int result = arithmeticCalculator.add(11, 12);
    System.out.println("result:" + result);
    result = arithmeticCalculator.div(21, 3);
    System.out.println("result:" + result);

在這裏插入圖片描述

3.Cglib代理(基於繼承的方式實現)

靜態代理和動態代理模式都是要求目標對象是實現一個接口的目標對象,但是有時候目標對象只是一個單獨的對象,並沒有實現任何的接口,這個時候就可以使用以目標對象子類的方式類實現代理,這種方法就叫做:Cglib代理
ps:靜態代理 代理類 與目標對象實現同樣接口

Cglib代理,也叫作子類代理,它是在內存中構建一個子類對象從而實現對目標對象功能的擴展.

JDK的動態代理有一個限制,就是使用動態代理的對象必須實現一個或多個接口,如果想代理沒有實現接口的類,就可以使用Cglib實現.
1.需要引入cglib的jar文件,但是Spring的核心包中已經包括了Cglib功能,所以直接引入pring-core-3.2.5.jar即可.

2.引入功能包後,就可以在內存中動態構建子類
3.代理的類不能爲final,否則報錯
4.目標對象的方法如果爲final/static,那麼就不會被攔截,即不會執行目標對象額外的業務方法.
5.如果方法爲static,private則無法進行代理。
CGlib採用非常底層的字節碼技術ASM,可以爲一個類創建子類,並在子類中採用方法攔截技術攔截父類方法的調用,並順勢進行增強,即是織入橫切邏輯

3.1 cglib代理實現(代碼)

/**
 * 目標對象,沒有實現任何接口
 */
public class UserDao {

    public void save() {
        System.out.println("----已經保存數據!----");
    }
}

Cglib代理工廠:ProxyFactory.java

/**
 * Cglib子類代理工廠
 * 對UserDao在內存中動態構建一個子類對象
 */
public class ProxyFactory implements MethodInterceptor{
    //維護目標對象
    private Object target;

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

    //給目標對象創建一個代理對象
    public Object getProxyInstance(){
        //1.工具類
        Enhancer en = new Enhancer();
        //2.設置父類
        en.setSuperclass(target.getClass());
        //3.設置回調函數
        en.setCallback(this);
        //4.創建子類(代理對象)
        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 returnValue;
    }
}

或者::

/**
 * Cglib子類代理工廠
 * 對UserDao在內存中動態構建一個子類對象
 */
public class ProxyFactory {
    //維護目標對象
    private Object target;

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

    //給目標對象創建一個代理對象
    public Object getProxyInstance(){
        //1.工具類
        Enhancer en = new Enhancer();
        //2.設置父類
        en.setSuperclass(target.getClass());
        //3.設置回調函數
        en.setCallback(new MethodInterceptor(){
        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 returnValue;
        }
        });
        //4.創建子類(代理對象)
        return en.create();

    }
}
/**
 * 測試類
 */
public class App {

    @Test
    public void test(){
        //目標對象
        UserDao target = new UserDao();

        //代理對象
        UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance();

        //執行代理對象的方法
        proxy.save();
    }
}

4.總結

如果加入容器的目標對象有實現接口,用JDK代理
如果目標對象沒有實現接口,用Cglib代理   
如果目標對象實現了接口,且強制使用cglib代理,則會使用cglib代理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章