動态代理由來
Java程式員應該都知道,靜态代理就是使用一個代理類(Proxy)來完成想要完成的事情,Proxy類通過編譯器編譯成class檔案,當系統運作時,此class已經存在。這種靜态的代理模式在增強現有的接口業務功能方面有很大的優點,但是大量使用靜态代理,會使系統内的類大規模爆發,不易維護。
為了解決這個問題,就有了動态建立Proxy的想法,在運作狀态中,動态的建立一個Proxy代理類,使用完之後就會銷毀,這樣就不用維護大量的代理類。
靜态代理案例
建立一個車票接口,定義一個賣票的方法。
package com.doaredo.test.proxy;
public interface Ticket {
public void sell();
}
建立一個車站類,實作賣票的接口,讓其擁有賣票的功能。
package com.doaredo.test.proxy;
public class Station implements Ticket {
@Override
public void sell() {
System.out.println("車站售票");
}
}
建立一個售票代理類,實作車票接口,通過構造函數注入車站類,讓其可以代理車站售票。
package com.doaredo.test.proxy;
public class StationProxy implements Ticket {
private Station station;
// 構造函數注入車站類,使其擁有售票功能
public StationProxy(Station station){
this.station = station;
}
@Override
public void sell() {
System.out.println("代理點開始售票");
station.buy();
System.out.println("代理點售票完成");
}
}
建立測試類,使用售票代理類來完成售票。
package com.doaredo.test.proxy;
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) throws Exception {
// 通過構造函數傳入車站對象,使代理對象擁有售票功能
StationProxy stationProxy = new StationProxy(new Station());
stationProxy.sell();
}
}
靜态代理比較簡單,代理類隻需要實作目标類的接口,然後通過構造函數注入目标類,這樣便可以完成目标類方法的調用,代理類可以在目标方法執行的前後做一些額外的功能,進而實作代理功能。
InvocationHandler的由來
代理就是在調用目标方法之前或者之後做一些額外的功能,但是我們在寫完代理類後,還需要再去調用指定的目标方法,上面靜态代理的目标方法為sell方法,這個目标方法是由我們自己定義的,不能統一。
為了構造出具有通用性和簡單性的代理類,于是将調用目标方法統一交給一個管理器,讓這個管理器統一的調用目标方法。這個管理器就是InvocationHandler。由于目标方法名稱不同,怎麼才能統一調用呢?在Java中使用反射就可以來實作統一的調用目标方法,将目标方法統一為反射中的Method。
JDK動态代理
使用JDK動态代理來實作本站售票的代理類。
建立一個車票接口,定義一個賣票的方法。
package com.doaredo.test.proxy;
public interface Ticket {
public void sell();
}
建立一個車站類,實作賣票的接口,讓其擁有賣票的功能。
package com.doaredo.test.proxy;
public class Station implements Ticket {
@Override
public void sell() {
System.out.println("車站售票");
}
}
建立InvocationHandler的實作類,通過構造函數注入目标類(即被代理的類,這裡Station車站類将被代理)。
實作invoke方法,Method參數就是要執行的目标方法,通過method.invoke進而完成目标方法的調用,在目标方法調用的前後來完成代理功能。
package com.doaredo.test.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class InvocationHandlerImpl implements InvocationHandler {
private Station station;
public InvocationHandlerImpl(Station station){
this.station = station;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("jdk動态代理開始");
method.invoke(station, args);
System.out.println("jdk動态代理結束");
return null;
}
}
建立測試類,完成JDK動态代理的調用,同時将JDK動态生成的代理類的class檔案輸出。
- 既然JDK要動态的幫我們生成代理類,動态的生成代理類後需要進行加載,是以需要一個類加載器ClassLoader。
- 需要目标類實作的接口。(為什麼要接口?)
- 執行目标方法,完成代理功能的管理類,InvocationHandler的實作。
package com.doaredo.test.proxy;
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) throws Exception {
Station station = new Station();
// 類加載器
ClassLoader classLoader = station.getClass().getClassLoader();
// 接口資訊
Class[] interfaces = station.getClass().getInterfaces();
// InvocationHandler的實作,完成代理功能,執行目标方法
InvocationHandler handler = new InvocationHandlerImpl(station);
// Jdk動态生成代理對象
Object o = Proxy.newProxyInstance(classLoader,interfaces,handler);
Ticket ticket = (Ticket)o;
ticket.sell();
generateClassFile(station.getClass(), "JdkProxyStation");
}
// 生成class檔案
public static void generateClassFile(Class clazz, String proxyName) throws Exception {
byte[] bytes = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
String path = clazz.getResource(".").getPath();
FileOutputStream fos = new FileOutputStream(path + proxyName + ".class");
fos.write(bytes);
fos.flush();
fos.close();
}
}
使用Jdk動态代理,并生成動态産生的代理類的class檔案,通過反編譯檢視生成的代碼如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import com.doaredo.test.proxy.Ticket;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class JdkProxyStation extends Proxy implements Ticket {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public JdkProxyStation(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void sell() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.doaredo.test.proxy.Ticket").getMethod("sell");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
我們可以看到,Jdk生成的代理類都繼承了Proxy,同時實作了我們目标類的接口,其中m3就是要執行的目标方法。其它的都是Object類中的方法。我們看一下是怎麼執行目标方法的:
super.h其實就是指的Proxy中的InvocationHandler,也就是在這裡将m3傳遞給了InvocationHandler的invoke方法,進而可以通過m3來完成目标方法的調用。
平時我們看的資料都說Jdk動态代理隻能是接口,這下大家都知道原因了吧,因為Java是單繼承的,而Jdk動态代理中,Jdk自己已經繼承了Proxy,是以我們不能再使用繼承了,而隻能使用接口的形式。
代理類特點:
- 繼承自Proxy,實作了定義的接口
- 類中的所有方法都是final的
- 所有的方法實作都統一調用了InvocationHandler的invoke()方法
Cglib動态代理
Jdk提供的動态代理有個特點:某個類必須有實作的接口,而生成的代理類也隻能代理接口定義的方法。也就是說,如果某個類沒有實作接口,那麼這個類就不能使用Jdk産生動态代理了。
Cglib可以在沒有接口的情況下完成動态代理,原理差不多,後面我們自己來生成代理類實作代理功能。
下面是Cglib的使用:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>idea-demo</groupId>
<artifactId>idea-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!--sm包 -->
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>8.0.1</version>
</dependency>
<!-- cglib包 -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
package com.doaredo.test.proxy;
public class CglibStation {
public void sell() {
System.out.println("車站售票");
}
}
package com.doaredo.test.proxy;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxy implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib代理開始");
methodProxy.invokeSuper(o, objects);
System.out.println("cglib代理結束");
return null;
}
}
package com.doaredo.test.proxy;
import net.sf.cglib.proxy.Enhancer;
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
public class Test {
public static void main(String[] args) throws Exception {
CglibStation cglibStation = new CglibStation();
CglibProxy cglibProxy = new CglibProxy();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(cglibStation.getClass());
enhancer.setCallback(cglibProxy);
CglibStation proxy = (CglibStation) enhancer.create();
proxy.sell();
}
}
更深層次了解動态代理
要明白動态代理,不能隻是表面的怎麼使用動态代理,記住一點,動态代理最重要的就是:動态的建立一個類,加入需要添加的邏輯代碼,并且執行被代理對象的邏輯。
既然是動态的建立類,據我所知,操作Java常用的有Javassist和ASM,Javassist相對ASM使用上比較簡單,下面就使用Javassist來學習動态代理模式是怎麼實作的。
加入javassist依賴
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.21.0-GA</version>
</dependency>
定義接口
// 先定義一個接口
public interface Service {
public void hello(String msg);
public void bye(String name);
}
使用javassist動态建立類,注意:并非是代理類,是用javassist動态建立Service接口的實作類:
- 建立一個ServiceImpl類,實作Service接口
- 在實作類的hello方法中,輸出hello+msg參數,在javassist中,$1表示第一個參數
package com.doaredo.test.proxy;
import javassist.*;
import org.junit.Test;
public class JavassistProxy {
@Test
public void test() throws Exception {
ClassPool classPool = ClassPool.getDefault();
// 建立一個類
CtClass clazz = classPool.makeClass("ServiceImpl");
// 擷取定義的接口,轉換成CtClass
CtClass interfaceCtClass = classPool.get(Service.class.getName());
// 給類添加接口
clazz.addInterface(interfaceCtClass);
// 邏輯代碼
String code = "{System.out.println(\"hello:\"+$1);}";
// String類型的CtClass
CtClass stringCtClass = classPool.get(String.class.getName());
// 實作接口中的hello方法
CtMethod hello = CtNewMethod.make(CtClass.voidType, "hello",
new CtClass[]{stringCtClass},// 參數
new CtClass[0],// 異常
code,// 邏輯代碼
clazz);// 實作方法的類
clazz.addMethod(hello);
// 将CtClass轉換成Java的Class
Class cla = classPool.toClass(clazz);
// 執行個體化Class執行對應的方法
Service service = (Service) cla.newInstance();
service.hello("doaredo");
}
}
上面利用Javassist動态的建立了一個類,并且實作了Service接口的hello方法,但是有幾個問題:
- 擷取定義接口的時候,Service.class.getName()是寫死的,如果接口不叫Service那麼就不好使了
- 邏輯代碼是通過手寫的,編譯器無法識别,容易出錯
改進後代碼如下:
- 将接口和邏輯代碼都通過參數形式傳遞進來
package com.doaredo.test.proxy;
import javassist.*;
import org.junit.Test;
public class JavassistProxy {
public static <T> T createClass(Class<T> interfaceClass, String code) throws Exception {
ClassPool classPool = ClassPool.getDefault();
// 建立一個類
CtClass clazz = classPool.makeClass("ServiceImpl");
// 擷取定義的接口,轉換成CtClass
CtClass interfaceCtClass = classPool.get(interfaceClass.getName());
// 給類添加接口
clazz.addInterface(interfaceCtClass);
// String類型的CtClass
CtClass stringCtClass = classPool.get(String.class.getName());
CtMethod hello = CtNewMethod.make(CtClass.voidType, "hello",
new CtClass[]{stringCtClass},// 參數
new CtClass[0],// 異常
code,// 邏輯代碼
clazz);// 實作方法的類
clazz.addMethod(hello);
// 執行個體化Class
Class cla = classPool.toClass(clazz);
return (T) cla.newInstance();
}
@Test
public void test2() throws Exception {
Service service = createClass(Service.class,"{System.out.println(\"hello:\"+$1);}");
service.hello("doaredo");
// 缺點:換方法後不能用了
//service.bye("doaredo");
}
}
經過改進後發現這裡hello方法與是寫死的,我們的目的是要能代理接口的所有方法,以及自定義邏輯代碼。既然要自定義邏輯代碼,那麼就定義一個執行邏輯代碼的接口,将邏輯代碼以參數形式傳遞進來。
public interface InvocationHandler {
Object invoke(String method, Object args[]);
}
接下來動态的實作接口中的所有的方法:
- 建立接口的實作類
- 給類添加InvocationHandler屬性,通過InvocationHandler可以調用實作的邏輯代碼
- 周遊接口中所有的方法,并實作所有的方法,所有方法都調用InvocationHandler的invoke方法來執行代理邏輯以及目标方法
- 将InvocationHandler實作類注入到handler屬性中
- $args是javassist中的文法,相當于Object args[]
package com.doaredo.test.proxy;
import javassist.*;
import org.junit.Test;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.stream.Collectors;
public class JavassistProxy {
private static int count = 0;
public static<T> T proxy(Class<T> interfaceClass, InvocationHandler h) throws Exception {
ClassPool classPool = ClassPool.getDefault();
// 建立一個類
CtClass clazz = classPool.makeClass("&proxy&"+(count++)+"."+interfaceClass.getName());
clazz.addInterface(classPool.get(interfaceClass.getName()));
// 添加handler屬性
CtField ctField = CtField.make("public com.doaredo.test.proxy.JavassistProxy.InvocationHandler handler=null;", clazz);
clazz.addField(ctField);
// 周遊接口中的所有方法,并實作接口中的方法
for (Method m : interfaceClass.getMethods()){
// 擷取方法的傳回類型
CtClass returnType = classPool.get(m.getReturnType().getName());
// 擷取方法名稱
String name = m.getName();
// 擷取方法參數
CtClass[] parameters = ctClass(classPool, m.getParameterTypes());
// 擷取異常
CtClass[] errors = ctClass(classPool, m.getExceptionTypes());
String code = "";
if(Void.class.equals(returnType)){
// 沒有傳回值
code = "this.handler.invoke(\"%s\", $args);";
}else{
code = "return ($r)this.handler.invoke(\"%s\", $args);";
}
// 實作接口中的方法
CtMethod hello = CtNewMethod.make(returnType, name,
parameters,
errors,
String.format(code, m.getName()),
clazz);
clazz.addMethod(hello);
}
// 将CtClass轉換成Java的Class
Class cla = classPool.toClass(clazz);
// 執行個體化Class
Object o = cla.newInstance();
// 擷取執行個體化類的handler屬性,并且指派為h。h為傳入進來的邏輯代碼實作類
cla.getField("handler").set(o, h);
// 将生成的類的代碼寫入到項目的target目錄下
byte[] bytes = clazz.toBytecode();
Files.write(Paths.get(System.getProperty("user.dir") + "/target/" + clazz.getName() + ".class"), bytes);
return (T) o;
}
private static CtClass[] ctClass(ClassPool cp, Class[] classes) {
CtClass[] result = Arrays.stream(classes).map(c -> {
try {
return cp.get(c.getName());
} catch (NotFoundException e) {
e.printStackTrace();
}
return null;
}).collect(Collectors.toList()).toArray(new CtClass[0]);
return result;
}
@Test
public void test3() throws Exception {
Service service = proxy(Service.class, new InvocationHandler() {
@Override
public Object invoke(String method, Object[] args) {
if(method.equals("hello")){
System.out.println("hello " + args[0]);
}else if(method.equals("bye")){
System.out.println("bye " + args[0]);
}
return null;
}
});
service.hello("doaredo");
service.bye("doaredo");
}
}
注意上面invoke方法中的method參數,是一個String類型,需要通過判斷具體方法名稱來決定具體的代理邏輯,顯然不太合适,注意:上面隻是利用Javassist動态的給Service接口建立了一個實作類,并沒有給哪個類做代理。
下面我們稍微改造一下代碼,來對具體的實作類進行動态代理:
package com.doaredo.test.proxy;
import javassist.*;
import org.junit.Test;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.stream.Collectors;
public class JavassistProxy {
/**
* 支援所有接口代理
* 不直接傳代碼,容易出錯
*/
private static int count = 0;
public static<T> T proxy(Class<T> interfaceClass, InvocationHandler h) throws Exception {
ClassPool classPool = ClassPool.getDefault();
// 建立一個類
CtClass clazz = classPool.makeClass("&proxy&"+(count++)+"."+interfaceClass.getName());
clazz.addInterface(classPool.get(interfaceClass.getName()));
// 添加handler屬性
CtField ctField = CtField.make("public com.doaredo.test.proxy.JavassistProxy.InvocationHandler handler=null;", clazz);
clazz.addField(ctField);
// 周遊接口中的所有方法,并實作接口中的方法
Method[] methods = interfaceClass.getMethods();
for (int i = 0; i < methods.length; i++){
Method m = methods[i];
// 擷取方法的傳回類型
CtClass returnType = classPool.get(m.getReturnType().getName());
// 擷取方法名稱
String name = m.getName();
// 擷取方法參數
CtClass[] parameters = ctClass(classPool, m.getParameterTypes());
// 擷取異常
CtClass[] errors = ctClass(classPool, m.getExceptionTypes());
String code = "";
String methodFieldTpl = "private static java.lang.reflect.Method %s=Class.forName(\"%s\").getDeclaredMethod(\"%s\", %s);";
String classParams = "new Class[0]";
if (m.getParameterTypes().length>0) {
for (Class param : m.getParameterTypes()){
classParams = classParams.equals("new Class[0]") ? param.getName() + ".class" : classParams + "," + param.getName() + ".class";
}
classParams = "new Class[]{"+classParams+"}";
}
// 方法字段
String methodFieldBody = String.format(methodFieldTpl, "m" + i, interfaceClass.getName(), m.getName(), classParams);
// 為代理類添加反射方法字段
CtField methodField=CtField.make(methodFieldBody,clazz);
clazz.addField(methodField);
if(Void.class.equals(returnType)){
// 沒有傳回值
// $0=this / $1,$2,$3... 代表方法參數
code = "&0.handler.invoke("+methodField.getName()+", $args);";
}else{
code = "return ($r)this.handler.invoke("+methodField.getName()+", $args);";
}
// 實作接口中的方法
CtMethod hello = CtNewMethod.make(returnType, name,
parameters,
errors,
String.format(code, m),
clazz);
clazz.addMethod(hello);
}
// 将生成的類的代碼寫入到項目的target目錄下
byte[] bytes = clazz.toBytecode();
Files.write(Paths.get(System.getProperty("user.dir") + "/target/" + clazz.getName() + ".class"), bytes);
// 将CtClass轉換成Java的Class
Class cla = classPool.toClass(clazz);
// 執行個體化Class
Object o = cla.newInstance();
// 擷取執行個體化類的handler屬性,并且指派為h。h為傳入進來的邏輯代碼實作類
cla.getField("handler").set(o, h);
return (T) o;
}
private static CtClass[] ctClass(ClassPool cp, Class[] classes) {
CtClass[] result = Arrays.stream(classes).map(c -> {
try {
return cp.get(c.getName());
} catch (NotFoundException e) {
e.printStackTrace();
}
return null;
}).collect(Collectors.toList()).toArray(new CtClass[0]);
return result;
}
@Test
public void test3() throws Exception {
Service service = proxy(Service.class, new InvocationHandlerImpl(new ServiceImpl()));
service.hello("doaredo");
service.bye("doaredo");
}
// 實作代理接口,完成代理邏輯
public class InvocationHandlerImpl implements InvocationHandler {
private Service service;
public InvocationHandlerImpl(Service service) {
this.service = service;
}
@Override
public Object invoke(Method method, Object[] args) {
try {
System.out.println("代理前");
// 通過反射執行被代理對象的方法
method.invoke(service, args);
System.out.println("代理後");
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
// 代理類接口
public interface InvocationHandler {
Object invoke(Method method, Object args[]);
}
// 服務接口
public interface Service {
public void hello(String msg);
public void bye(String name);
}
// 服務實作
public class ServiceImpl implements Service {
@Override
public void hello(String msg) {
System.out.println("hello " + msg);
}
@Override
public void bye(String name) {
System.out.println("bye " + name);
}
}
}
下面就是使用Javassist來實作JDK的動态代理,反編譯生成的代碼如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package &proxy&0.com.doaredo.test.proxy;
import com.doaredo.test.proxy.JavassistProxy.InvocationHandler;
import com.doaredo.test.proxy.JavassistProxy.Service;
import java.lang.reflect.Method;
public class JavassistProxy$Service implements Service {
public InvocationHandler handler = null;
private static Method m0 = Class.forName("com.doaredo.test.proxy.JavassistProxy$Service").getDeclaredMethod("hello", String.class);
private static Method m1 = Class.forName("com.doaredo.test.proxy.JavassistProxy$Service").getDeclaredMethod("bye", String.class);
public void hello(String var1) {
this.handler.invoke(m0, new Object[]{var1});
}
public String bye(String var1) {
return (String)this.handler.invoke(m1, new Object[]{var1});
}
public JavassistProxy$Service() {
}
}
初次看會覺得比較繞,前面Jdk生成的代理類跟我們自己生成的代理類原理都是一樣的,但是Jdk中還生成了Object類中的所有方法,同時繼承了Proxy類。