JDK、CGLib、Javassist實現動態代理
一、類載入
1.類載入過程模擬(先明白類載入過程,方可模擬類執行期間載入-建立代理類,呼叫目標方法)
public class Programmer {
public void code() {
System.out.println("I'm a Programmer,Just Coding.....");
}
}
/** * 自定義一個類載入器,用於將位元組碼轉換為class物件 */ public class MyClassLoader extends ClassLoader { public Class<?> defineMyClass(byte[] b, int off, int len) { //TODO SOURCE CODE return super.defineClass(null,b, off, len); } }
public class MyTest { public static void main(String[] args) throws IOException { //讀取本地的class檔案內的位元組碼,轉換成位元組碼陣列 File file = new File("."); InputStream input = new FileInputStream(file.getCanonicalPath() + "\\target\\classes\\com\\max\\dproxy\\loadseq\\Programmer.class"); byte[] result = new byte[1024];//位元組型 int count = input.read(result); // 使用自定義的類載入器將 byte位元組碼陣列轉換為對應的class物件 MyClassLoader loader = new MyClassLoader(); Class clazz = loader.defineMyClass(result, 0, count); //測試載入是否成功,列印class 物件的名稱 System.out.println(clazz.getCanonicalName()); try { //例項化一個Programmer物件 Object o = clazz.newInstance(); //呼叫Programmer的code方法 clazz.getMethod("code", null).invoke(o, null); } catch (IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } } }
2.類載入過程
注:綠色橢圓內即JVM虛擬機器狀態,具體不做贅述。
二、動態代理
代理模式:
靜態代理:手動編寫代理類代理目標類方法。缺點:手動建立;代理類越來越多,系統規模增大,不易維護;
動態代理:由於JVM通過位元組碼的二進位制(byte-code)資訊載入類的,那麼,如果我們在執行期系統中,1.遵循Java編譯系統組織.class檔案的格式和結構,2.生成相應的二進位制資料,3.然後再把這個二進位制資料載入轉換成對應的類,這樣,就完成了在程式碼中,動態建立一個類的能力了。
JDK實現動態代理
public interface Vehicle {
void drive();
}
public interface Rechargable { void recharge(); }
public class ElectricCar implements Rechargable, Vehicle {
@Override
public void drive() {
System.out.println("Electric Car is Moving silently...");
}
@Override
public void recharge() {
System.out.println("Electric Car is Recharging...");
}
}
public class InvocationHandlerImpl implements InvocationHandler {
private ElectricCar car;
public InvocationHandlerImpl(ElectricCar car) {
this.car = car;
}
@Override
public Object invoke(Object paramObject, Method paramMethod, Object[] paramArrayOfObject) throws Throwable {
System.out.println("You are going to invoke " + paramMethod.getName() + " ...");
paramMethod.invoke(car, null);
System.out.println(paramMethod.getName() + " invocation Has Been finished...");
return null;
}
}
public class Test {
public static void main(String[] args) {
ElectricCar car = new ElectricCar();
// 1.獲取對應的ClassLoader
ClassLoader classLoader = car.getClass().getClassLoader();
// 2.獲取ElectricCar 所實現的所有介面
Class[] interfaces = car.getClass().getInterfaces();
// 3.設定一個來自代理傳過來的方法呼叫請求處理器,處理所有的代理物件上的方法呼叫
InvocationHandler handler = new InvocationHandlerImpl(car);
/*
4.根據上面提供的資訊,建立代理物件 在這個過程中,
a.JDK會通過根據傳入的引數資訊動態地在記憶體中建立和.class檔案等同的位元組碼
b.然後根據相應的位元組碼轉換成對應的class,
c.然後呼叫newInstance()建立例項
*/
Object o = Proxy.newProxyInstance(classLoader, interfaces, handler);
Vehicle vehicle = (Vehicle) o;
vehicle.drive();
Rechargable rechargeable = (Rechargable) o;
rechargeable.recharge();
}
}
newProxyInstance過程做了3件事:
1.通過傳入的類資訊(載入器、介面、處理器-增強方法)動態的在記憶體中建立和.class檔案同等的位元組碼(代理類位元組碼)
2.將該位元組碼轉換成對應類(生成代理類的步驟)
3.通過newInstance建立類,而後呼叫1中傳入的所有介面方法。
對比類的載入過程,123步驟。
cglib實現動態代理
public class Programmer {
public void code() {
System.out.println("I'm a Programmer,Just Coding.....");
}
}
/*
* 實現了方法攔截器介面 Spring AOP實現方式
*/
public class Hacker implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("**** I am a hacker,Let's see what the poor programmer is doing Now...");
proxy.invokeSuper(obj, args);
System.out.println("**** Oh,what a poor programmer.....");
return null;
}
}
public class Test {
public static void main(String[] args) {
Programmer progammer = new Programmer();
Hacker hacker = new Hacker();
//cglib 中加強器,用來建立動態代理
Enhancer enhancer = new Enhancer();
//設定要建立動態代理的類
enhancer.setSuperclass(progammer.getClass());
// 設定回撥,這裡相當於是對於代理類上所有方法的呼叫,都會呼叫CallBack,而Callback則需要實行intercept()方法進行攔截
enhancer.setCallback(hacker);
Programmer proxy = (Programmer) enhancer.create();
proxy.code();
}
}
javassist實現動態代理
public class Programmer {
public void code() {System.out.println("I'm a Programmer,Just Coding.....");
}
}
public class MyGenerator {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
//建立Programmer類
CtClass cc = pool.makeClass("com.max.dproxy.javassist.Programmer");
//定義code方法
CtMethod method = CtNewMethod.make("public void code(){}", cc);
//插入方法程式碼 增強
method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");
cc.addMethod(method);
//儲存生成的位元組碼
cc.writeFile("d://temp");
}
}
以上先簡單顯示一下javassist的用法。(其實insertBefore就已經是在針對code方法的增強,上述程式碼補充上cc位元組碼的load和建立過程【如下下例中的】及完整的顯示了javassist建立代理的步驟)。
//獲取動態生成的class
Class c = cc.toClass();
//獲取構造器
Constructor constructor = c.getConstructor(TicketServiceImpl.class);
//通過構造器例項化
TicketService o = (TicketService) constructor.newInstance(new TicketServiceImpl());
o.sellTicket();
以下顯示使用javassist實現動態代理的完整步驟
public interface TicketService {
//售票
void sellTicket();
//問詢
void inquire();
//退票
void withdraw();
}
public class TicketServiceImpl implements TicketService {
@Override
public void sellTicket() {
System.out.println("\n\t售票.....\n");
}
@Override
public void inquire() {
System.out.println("\n\t問詢。。。。\n");
}
@Override
public void withdraw() {
System.out.println("\n\t退票......\n");
}
}
public class Test {
public static void main(String[] args) throws Exception {
createProxy();
}
/*
* 手動建立位元組碼
*/
private static void createProxy() throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("com.max.dproxy.staticproxy.StationProxy");
//設定介面
CtClass interface1 = pool.get("com.max.dproxy.staticproxy.TicketService");
cc.setInterfaces(new CtClass[] { interface1 });
//設定Field
CtField field = CtField.make("private com.max.dproxy.staticproxy.TicketServiceImpl station;", cc);
cc.addField(field);
CtClass stationClass = pool.get("com.max.dproxy.staticproxy.TicketServiceImpl");
CtClass[] arrays = new CtClass[] { stationClass };
CtConstructor ctc = CtNewConstructor.make(arrays, null, CtNewConstructor.PASS_NONE, null, null, cc);
//設定建構函式內部資訊
ctc.setBody("{this.station=$1;}");
cc.addConstructor(ctc);
//建立5個方法
//建立收取手續方法
CtMethod takeHandlingFee = CtMethod.make("private void takeHandlingFee() {}", cc);
takeHandlingFee.setBody("System.out.println(\"收取手續費,打印發票。。。。。\");");
cc.addMethod(takeHandlingFee);
//建立showAlertInfo方法
CtMethod showInfo = CtMethod.make("private void showAlertInfo(String info) {}", cc);
showInfo.setBody("System.out.println($1);");
cc.addMethod(showInfo);
//建立sellTicket
CtMethod sellTicket = CtMethod.make("public void sellTicket(){}", cc);
//"{this.showAlertInfo 都是在呼叫這些方法,括號中傳入需print的string
//但proxy物件中,sellTicket方法並未執行,而是執行的動態代理中method的流程
sellTicket.setBody("{this.showAlertInfo(\"showAlertInfo********\");" + "station.sellTicket();"
+ "this.takeHandlingFee();" + "this.showAlertInfo(\"××××BYBY!××××\");}");
cc.addMethod(sellTicket);
//建立inquire方法
CtMethod inquire = CtMethod.make("public void inquire() {}", cc);
inquire.setBody("{this.showAlertInfo(\"××××歡迎光臨本代售點,問詢服務不會收取任何費用,本問詢資訊僅供參考,具體資訊以車站真實資料為準!××××\");"
+ "station.inquire();" + "this.showAlertInfo(\"××××歡迎您的光臨,再見!××××\");}");
cc.addMethod(inquire);
//建立widthraw方法
CtMethod withdraw = CtMethod.make("public void withdraw() {}", cc);
withdraw.setBody("{this.showAlertInfo(\"××××歡迎光臨本代售點,退票除了扣除票額的20%外,本代理處額外加收2元手續費!××××\");"
+ "station.withdraw();" + "this.takeHandlingFee();}");
cc.addMethod(withdraw);
//獲取動態生成的class
Class c = cc.toClass();
//獲取構造器
Constructor constructor = c.getConstructor(TicketServiceImpl.class);
//通過構造器例項化
TicketService o = (TicketService) constructor.newInstance(new TicketServiceImpl());
o.sellTicket();
}
}
這裡的Proxy代理類即可刪除,動態生成代理,取代以下的代理類建立。
public class StationProxy implements TicketService {
private TicketServiceImpl station;
public StationProxy(TicketServiceImpl station) {
this.station = station;
}
@Override
public void sellTicket() {
// 1.做真正業務前,提示資訊
this.showAlertInfo("××××showAlertInfo before sellTicket××××");
// 2.呼叫真實業務邏輯
station.sellTicket();
// 3.後處理
this.takeHandlingFee();
this.showAlertInfo("××××sellTicket over××××\n");
}
@Override
public void inquire() {
// 1做真正業務前,提示資訊
this.showAlertInfo("××××showAlertInfo before inquire××××");
// 2.呼叫真實邏輯
station.inquire();
// 3。後處理
this.showAlertInfo("××××inquire over××××\n");
}
@Override
public void withdraw() {
// 1。真正業務前處理
this.showAlertInfo("××××howAlertInfo before withdraw××××");
// 2.呼叫真正業務邏輯
station.withdraw();
// 3.後處理
this.takeHandlingFee();
}
// TODO 除了實現public介面,代理類新增private方法,在test中仍新增進method中
/*
* 展示額外資訊
*/
private void showAlertInfo(String info) {
System.out.println(info);
}
/*
* 收取手續費
*/
private void takeHandlingFee() {
System.out.println("takeHandlingFee......\n");
}
}
三、總結
1.類的載入:
.java編譯形成.class bytecode;而後將byte code通過類載入期load到執行期中進行解釋、編譯、執行
2.動態代理
實現原理:在執行期模擬類的載入過程,將增強類生成位元組碼,載入轉成類(代理類)
jdk:基於介面?從何說起?因為在newPorxy建立代理類時,傳入被代理類的所有介面。
InvocationHandler作用?
1)自定義的方法處理器,在invoke方法中加入被代理類的增強邏輯。
2)通過InvocationHandler統一管理器,在呼叫介面方法(drive、recharge時)進行攔截,統一呼叫invoke方法,根據invoke中傳入的method引數,確定呼叫被代理類的具體方法。
缺點:被代理類與代理類都繼承自同一介面,無法實現隨機自由組合增強。
cglib:兩個無關類,增強類只需實現MethodInterceptor,通過enhancer的setCallback,呼叫intercept增強方法。intercept寫法與jdk類似。
javassist:採用類的包名載入類,編譯形成位元組碼,設定介面、欄位資訊,新增構造方法、介面方法,最後使用構造器例項化類,呼叫介面方法。