1. 程式人生 > >28--靜態代理模式和JDK、CGLIB動態代理

28--靜態代理模式和JDK、CGLIB動態代理

在分析AOP的原始碼之前,我們需要了解一下代理模式,代理模式可分為靜態代理和動態代理兩種。

1.靜態代理

代理模式是常用設計模式的一種,我們在軟體設計時常用的代理一般是指靜態代理,也就是在程式碼中顯式指定的代理。

靜態代理由 業務實現類、業務代理類 兩部分組成。業務實現類 負責實現主要的業務方法,業務代理類負責對呼叫的業務方法作攔截、過濾、預處理,主要是在方法中首先進行預處理動作,然後呼叫業務實現類的方法,還可以規定呼叫後的操作。我們在需要呼叫業務時,不是直接通過業務實現類來呼叫的,而是通過業務代理類的同名方法來呼叫被代理類處理過的業務方法。

  • 代理類和被代理類
package com.lyc.
cn.v2.day04.aop.proxy; /** * 賬戶介面 */ public interface Count { // 查詢賬戶 void queryCount(); // 修改賬戶 void updateCount(); }
package com.lyc.cn.v2.day04.aop.proxy;

/**
 * @author: LiYanChao
 * @create: 2018-10-21 12:07
 */
public class CountImpl implements Count {
	@Override
	public void queryCount() {
		System.
out.println("==查詢賬戶"); } @Override public void updateCount() { System.out.println("==更新賬戶"); } }
package com.lyc.cn.v2.day04.aop.proxy;

/**
 * 代理類
 * @author: LiYanChao
 * @create: 2018-10-21 12:08
 */
public class CountProxy implements Count {

	private CountImpl countImpl;

	/**
	 * 覆蓋預設構造器
	 */
public CountProxy(CountImpl countImpl) { this.countImpl = countImpl; } @Override public void queryCount() { System.out.println("==查詢賬戶開始"); // 呼叫真正的查詢賬戶方法 countImpl.queryCount(); System.out.println("==查詢賬戶結束"); } @Override public void updateCount() { System.out.println("==更新賬戶開始"); // 呼叫真正的修改賬戶操作 countImpl.updateCount(); System.out.println("==更新賬戶結束"); } }
  • 測試及結果
@Test
public void test3() {
    // 靜態代理
    CountImpl countImpl = new CountImpl();
    CountProxy countProxy = new CountProxy(countImpl);
    countProxy.updateCount();
    System.out.println("\n*******\n");
    countProxy.queryCount();
}
==更新賬戶開始
==更新賬戶
==更新賬戶結束

*******

==查詢賬戶開始
==查詢賬戶
==查詢賬戶結束
  • 總結 一個代理類只能對一個業務介面的實現類進行包裝,如果有多個業務介面的話就要定義很多實現類和代理類才行。而且,如果代理類對業務方法的預處理、呼叫後操作都是一樣的(比如:呼叫前輸出提示、呼叫後自動關閉連線),則多個代理類就會有很多重複程式碼。這時我們可以定義這樣一個代理類,它能代理所有實現類的方法呼叫:根據傳進來的業務實現類和方法名進行具體呼叫。——那就是動態代理。
2.JKD動態代理

JDK動態代理所用到的代理類在程式呼叫到代理類物件時才由JVM真正建立,JVM根據傳進來的 業務實現類物件 以及 方法名 ,動態地建立了一個代理類的class檔案並被位元組碼引擎執行,然後通過該代理類物件進行方法呼叫。我們需要做的,只需指定代理類的預處理、呼叫後操作即可。JDK的動態代理需要實現InvocationHandler介面,並重寫invoke方法。

  • 代理類和被代理類
package com.lyc.cn.v2.day04.aop.jdk;

public interface Animal {
	void say();
}

package com.lyc.cn.v2.day04.aop.jdk;

public class Dog implements Animal {

	@Override
	public void say() {
		System.out.println("I am a dog...");
	}

}

package com.lyc.cn.v2.day04.aop.jdk;

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

public class MyInvocationHandler implements InvocationHandler {

	// 目標物件
	private Object target;

	/**
	 * 構造方法
	 * @param target 目標物件
	 */
	public MyInvocationHandler(Object target) {
		super();
		this.target = target;
	}

	/**
	 * @param proxy  JDK動態生成的最終代理物件
	 * @param method 呼叫真實物件的某個方法的Method物件
	 * @param args   呼叫真實物件某個方法時接受的引數
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("==代理方法開始執行");
		Object invoke = method.invoke(target, args);
		System.out.println("==代理方法結束執行");
		return invoke;
	}

	/**
	 * 獲取目標物件的代理物件
	 * @return 代理物件
	 */
	public Object getProxy() {
		return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), target.getClass().getInterfaces(), this);
	}

}

  • 測試及結果
@Test
public void test4() {
    // JDK動態代理
    MyInvocationHandler handler = new MyInvocationHandler(new Dog());
    Animal proxy = (Animal) handler.getProxy();
    proxy.say();
}
==代理方法開始執行
I am a dog...
==代理方法結束執行
  • 總結 JDK動態代理的代理物件在建立時,需要使用業務實現類所實現的介面作為引數(因為在後面代理方法時需要根據介面內的方法名進行呼叫)。如果業務實現類是沒有實現介面而是直接定義業務方法的話,就無法使用JDK動態代理了。並且,如果業務實現類中新增了介面中沒有的方法,這些方法是無法被代理的(因為無法被呼叫)。如果沒有介面定義又想使用動態代理的話,那麼可以使用CGLIB動態代理。
3.CGLIB動態代理

CGLIB是針對類來實現代理的,原理是對指定的業務類生成一個子類,並覆蓋其中業務方法實現代理。因為採用的是繼承,所以不能對final修飾的類進行代理。 在使用的時候需要引入cglib和asm的jar包

compile group: 'asm', name: 'asm', version: '3.3.1'
compile group: 'cglib', name: 'cglib', version: '2.2.2'
  • 代理類和被代理類
package com.lyc.cn.v2.day04.aop.cglib;

public class Cat {
	public void say() {
		System.out.println("I am a cat");
	}
}

package com.lyc.cn.v2.day04.aop.cglib;

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

import java.lang.reflect.Method;

public class CglibProxy implements MethodInterceptor {

	private Enhancer enhancer = new Enhancer();

	// 這裡的目標型別為Object,則可以接受任意一種引數作為被代理類,實現了動態代理
	public Object getInstance(Class clazz) {
		enhancer.setSuperclass(clazz);
		enhancer.setCallback(this);
		// 返回代理物件
		return enhancer.create();
	}

	@Override
	public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
		System.out.println("==代理方法開始執行");
		Object result = methodProxy.invokeSuper(proxy, args);
		System.out.println("==代理方法結束執行");
		return result;
	}

}

  • 測試類及結果
@Test
public void test5() {
    // CGLIB動態代理
    Cat cat = (Cat) new CglibProxy().getInstance(Cat.class);
    cat.say();
}
==代理方法開始執行
I am a cat
==代理方法結束執行
  • 總結 1、靜態代理是通過在程式碼中顯式定義一個業務實現類一個代理,在代理類中對同名的業務方法進行包裝,使用者通過代理類呼叫被包裝過的業務方法; 2、JDK動態代理是通過介面中的方法名,在動態生成的代理類中呼叫業務實現類的同名方法; 3、CGlib動態代理是通過繼承業務類,生成的動態代理類是業務類的子類,通過重寫業務方法進行代理;