開門見山骡技,要整下Java語言的動態(tài)代理,按照國際慣例羞反,得先來介紹下背景布朦。
為什么需要代理呢? 舉個栗子:
在我們寫的所有修改數(shù)據(jù)方法操作里面,基本上都需要事務支持昼窗。這個時候正常實現(xiàn)要怎樣呢是趴?
首先在方法開始處添加開啟事務代碼,方法結束的時候添加提交事務代碼澄惊; 如下面的偽代碼:
public void add(Object obj) {
//----------開啟事務代碼----------;
xxxDao.add(msg);
//----------判斷提交或回滾事務代碼----------";
}
在每一個需要事務的方法里面都需要有上面的開始事務和提交事務代碼唆途。再比如用戶如果還需要知道是誰以及在什么時間操作了這方法,那么我就需要做方法的操作日志記錄掸驱。
正常的又該怎么做呢毕贼,基本上就如下偽代碼:
public void add(Object obj) {
//----------開啟事務代碼----------;
//----------記錄操作日志代碼----------;
xxxDao.add(msg);
//----------判斷提交或回滾事務代碼----------";
}
上面的代碼可以很清楚的看到陶贼,我們需要在每個需要事務或者操作日志記錄的方法里面加上一堆的與核心業(yè)務無關的流程代碼,這樣就是重復勞動了痹屹。Java程序員是很懶的只要有重復的事情那都不會干,畢竟Java的抽象繼承多態(tài)是干啥的嘲恍,不就是為了消除重復淹辞,讓方法能夠復用象缀,少敲幾行代碼多喝點咖啡央星。
既然有了問題莉给,我們就得解決問題颓遏,要解決問題我們就得使用代理叁幢,代理就是在原有的方法上進行增強曼玩。說到代理演训,先給例子:
/**
* 業(yè)務接口
* @author ngcly
* @version V1.0
* @since 2021/8/20 12:30
*/
public interface IUserService {
void add(String msg);
void add();
String get();
}
/**
* 業(yè)務接口實現(xiàn)類
* @author ngcly
* @version V1.0
* @since 2021/8/20 12:30
*/
public class UserService implements IUserService{
@Override
public void add(String msg) {
System.out.println("=============保存============="+msg);
}
@Override
public void add() {
System.out.println("=============保存=============");
}
@Override
public String get() {
System.out.println("=============獲取=============");
return "捉到啦";
}
}
/**
* 靜態(tài)代理
* @author ngcly
* @version V1.0
* @since 2021/8/20 12:33
*/
public class UserServiceProxy implements IUserService{
/**代理目標類*/
private IUserService target;
public UserServiceProxy(IUserService userService){
this.target = userService;
}
@Override
public void add(String msg) {
System.out.println("----------事務開始----------");
target.add(msg);
System.out.println("----------事務結束----------");
}
@Override
public void add() {
System.out.println("***********事務開始***********");
target.add();
System.out.println("***********事務結束***********");
}
@Override
public String get() {
System.out.println("############get事務開始############");
String result = target.get();
System.out.println("############get事務結束############");
return result;
}
}
/**
* 代理類測試
* @author ngcly
* @version V1.0
* @since 2021/8/20 12:31
*/
public class Main {
public static void main(String[] args){
IUserService staticProxy = new UserServiceProxy(new UserService());
staticProxy.add("靜態(tài)代理");
}
}
如上代碼所示,就只需要在主方法調用端那里做處理去統(tǒng)一調用代理類陈症,便可以在不改原方法任何代碼的情況下進行方法的增強震糖,而原方法就只需要關心核心業(yè)務的開發(fā)就可以论咏,不用操心其他的事颁井。而這就是一個很普通的靜態(tài)代理實現(xiàn)养涮。
那么上面的方法實現(xiàn)存在什么樣的問題呢眉抬?上面只有一個 IUserService 接口蜀变,但是現(xiàn)實業(yè)務中肯定還有很多其他的 IOrderService尊沸、IFinanceService 等幾十上百個之類的接口贤惯,那么這時候要進行統(tǒng)一的增強,我就需要針對這幾十上百個接口都來實現(xiàn)代理方法烟很。那這種苦差事誰愿意干呢雾袱,反正我是不愿意的毒坛,既然不愿意那就要想辦法去解決煎殷。
那么我們能不能用一個公共的代理類來代替所有的具體代理類呢豪直,這樣就只需調用公共代理類然后傳入具體被代理類就可以進行業(yè)務代理了弓乙。首先仔細看上面的靜態(tài)代理實現(xiàn)結構暇韧,要實現(xiàn)代理语卤,我們必須要有兩大核心類:一個代理類和一個被代理類粹舵,代理類還有一個需要用戶自定義實現(xiàn)的代理增強方法眼滤。如上代碼 UserServiceProxy 是代理類诅需,UserService 是被代理類堰塌。 被代理類這個是現(xiàn)有的场刑,我們現(xiàn)在所需要的就是代理類牵现,使之能夠代理任意的類瞎疼,同時我們還得提供一個接口贼急,讓用戶能夠實現(xiàn)自己的代理方法竿裂。好了腻异,說了理論悔常,接下來就要說具體實現(xiàn)矫户。
首先我們得提供一個讓用戶實現(xiàn)業(yè)務代理方法的接口皆辽,如下:
package prox;
/**
* 自定義動態(tài)代理方法規(guī)范接口
* @author ngcly
* @version V1.0
* @since 2021/8/20 15:38
*/
public interface MyInvocationHandler {
/**
* 代理增強方法接口
* 此處方法及參數(shù)設計參照了JDK的設計 和 JDK 的 InvocationHandler接口設計一致
* 其中第一個參數(shù) obj 代理對象這個參數(shù) 在這里其實沒有什么用處 完全可以省略掉。
* 而官方設計該參數(shù)的目的及作用如下:
* 1. 可以使用反射獲取代理對象的信息(也就是proxy.getClass().getName())空另。
* 2. 可以將代理對象返回以進行連續(xù)調用扼菠,這就是proxy存在的目的循榆,因為this并不是代理對象。
* @param obj 代理對象
* @param method 代理方法
* @param args 代理參數(shù)
* @return 方法調用結果
* @throws Throwable 拋出異常
*/
Object invoke(Object obj, Method method,Object[] args) throws Throwable;
}
然后我再來一個業(yè)務代理的接口實現(xiàn):
package prox;
import java.lang.reflect.Method;
/**
* 自定義動態(tài)代理業(yè)務方法實現(xiàn)
* @author ngcly
* @version V1.0
* @since 2021/8/20 17:28
*/
public class ClassProxyFactory<T> implements MyInvocationHandler{
private T target;
public ClassProxyFactory(T t) {
this.target = t;
}
public T getProxyInstance() {
//此處采用自定義類加載器
MyClassLoader classLoader = new MyClassLoader();
Class[] classes;
/**判斷目標被代理對象是否有接口,若無接口則直接用代理對象*/
if(target.getClass().getInterfaces().length>0){
classes = target.getClass().getInterfaces();
}else{
classes = new Class[]{target.getClass()};
}
return (T) MyProxy.newProxyInstance(classLoader, classes,this);
}
/**
* 代理增強方法具體實現(xiàn)
*
* 此處方法及參數(shù)設計參照了JDK的設計 和 JDK 的 InvocationHandler接口設計一致
* 其中第一個參數(shù) obj 代理對象這個參數(shù) 在這里其實沒有什么用處 完全可以省略掉。
* 而官方設計該參數(shù)的目的及作用如下:
* 1. 可以使用反射獲取代理對象的信息(也就是proxy.getClass().getName())椎组。
* 2. 可以將代理對象返回以進行連續(xù)調用专筷,這就是proxy存在的目的,因為this并不是代理對象。
* @param obj 代理對象
* @param method 代理方法
* @param args 代理參數(shù)
* @return 方法返回結果
* @throws Throwable
*/
@Override
public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
System.out.println("**********開始事務**********");
Object value = method.invoke(target,args);
System.out.println("調用目標方法返回:"+ value);
System.out.println("**********結束事務**********");
return value;
}
}
實現(xiàn)了業(yè)務代理方法庇勃,這個時候就需要進行構建代理對象了。由于我們并不知道需要代理的對象是誰罕拂,只有在運行的時候才能確定爆班,所以這個時候就需要利用反射機制在運行期間進行動態(tài)構建代理對象蛋济。
思路如下:以字符串的形式拼湊出一個 extends 或 implement 被代理對象或接口的Java類 的這個代理對象類碗旅。同時該類還需要有 MyInvocationHandler 這個接口的成員變量祟辟,這個接口實例就是用來執(zhí)行用戶自定義的代理方法。將該類的構造方法改為要傳入MyInvocationHandler 的有參構造吼具,然后將該代理類的所有方法實現(xiàn)為調用 MyInvocationHandler 接口的 invoke() 方法。
說了這么多矩距,用個例子拗盒,假設調用端現(xiàn)在要代理的是實現(xiàn)了 IUserService 接口的 UserService 類,那我們此時要動態(tài)構建的代理類就如下所示:
public class Proxy implements IUserService {
MyInvocationHandler handler;
public Proxy(MyInvocationHandler handler) {
this.handler = handler;
}
public void add(String var1) {
try {
Method var2 = IUserService.class.getMethod("add", String.class);
this.handler.invoke(this, var2, new Object[]{var1});
} catch (Throwable var3) {
var3.printStackTrace();
throw new RuntimeException(var3.getMessage());
}
}
public void add() {
try {
Method var1 = IUserService.class.getMethod("add");
this.handler.invoke(this, var1, (Object[])null);
} catch (Throwable var2) {
var2.printStackTrace();
throw new RuntimeException(var2.getMessage());
}
}
public String get() {
try {
Method var1 = IUserService.class.getMethod("get");
return (String)this.handler.invoke(this, var1, (Object[])null);
} catch (Throwable var2) {
var2.printStackTrace();
throw new RuntimeException(var2.getMessage());
}
}
}
是不是和靜態(tài)代理類基本一致锥债,唯一的區(qū)別就是方法實現(xiàn)內容部分陡蝇,核心就是下面的語句
this.handler.invoke(this, var2, new Object[]{var1});
每個代理方法內部都是直接調用 MyInvocationHandler 實例的 invoke() 方法痊臭,而這個方法具體實現(xiàn)又是交由用戶自己去自定義實現(xiàn)的。為什么要這樣子呢登夫,因為當前這個代理類是系統(tǒng)運行時動態(tài)產(chǎn)生的广匙,對于用戶是不可見的戏蔑,所以代理方法的實現(xiàn)必須得委托出去情龄,讓用戶有地方可以進行自定義實現(xiàn)睹逃。而上面的 implements IUserService 語句和成員方法的定義都是動態(tài)決定的翼闹,如果要代理的是其他的接口或普通類关摇,那么該對象的成員方法定義就得隨之而改變。
既然如此,那么我們該如何動態(tài)地構建這么一個類呢食听?實現(xiàn)代碼如下:
package prox;
import javax.tools.*;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
/**
* 該類可以理解為方法類 其作用就是為了動態(tài)構建 代理類并實例化 的一個入口
* @author ngcly
* @version V1.0
* @since 2021/8/20 15:03
*/
public class MyProxy {
//自增數(shù)字
private static final AtomicLong ATOMIC_LONG = new AtomicLong();
//私有化構造 因為該類作用就是個方法入口
private MyProxy(){}
/** 以字符串的形式拼湊出一個 實現(xiàn)了 interfaces 接口的代理類 同時該類還需要有 MyInvocationHandler 成員變量,
* MyInvocationHandler 該接口 就是執(zhí)行代理的操作的接口
將該類的構造方法改為要傳入handle的有參構造 然后該代理類實現(xiàn)的所有方法實現(xiàn)為 調用 handler接口的 invoke方法
將上述字符串類編譯成class類逆趣, 然后利用ClassLoader進行加載
最后用反射將該class進行實例化 并返回 */
public static Object newProxyInstance(MyClassLoader classLoader, Class<?>[] interfaces, MyInvocationHandler handler){
String className = "$Proxy"+ATOMIC_LONG.getAndIncrement();
try {
String javaFileName = MyProxy.class.getResource("").getPath() + className + ".java";
// 以下 2部蛇,3,4 步驟目的就是構建一個代理類的class文件 JDK此處就是直接構造成 class 字節(jié)碼然后保存到文件中。
// 由于Class文件結構較為復雜 所以此處是先用字符串拼接一個Java文件,
// 再用JavaCompiler去編譯這個java文件将饺,生成一個class文件。
// 最后通過自定義的MyClassLoader類加載器把這個生成好的class文件加載到內存,得到Class對象拳魁。
// 1.生成源代碼字符串
String javaSrc = generateJavaSrc(className, interfaces);
// 2.把源代碼字符串作為java類文件保存到磁盤
File javaFile = new File(javaFileName);
generateJavaFile(javaSrc, javaFile);
// 3.將磁盤的代理Java類編譯成class文件
compilerJavaFile(javaFile);
// 4.使用自定義的 classLoad 將剛才編譯的代理class加載到jvm中
Class<?> proxyClass = classLoader.loadClass(className);
// 5.利用反射 將代理class實例化 并返回該代理對象
Object obj = proxyClass.getDeclaredConstructor(MyInvocationHandler.class).newInstance(handler);
if (javaFile.exists()) {
//刪除操作時生成的Java源代碼文件
javaFile.delete();
}
return obj;
}catch (Exception e){
e.printStackTrace();
return null;
}
}
/**
* 構建Java源碼字符串
* @param className java類名
* @param interfaces 要繼承或實現(xiàn)的類或接口
* @return String 源碼字符串
*/
public static String generateJavaSrc(String className, Class<?>[] interfaces) {
// 定義換行符
String ln = "\r\n";
String packageName = MyProxy.class.getPackage().getName();
StringBuffer sb = new StringBuffer();
//類所在包
sb.append("package " + packageName + ";" + ln);
//導包代碼
sb.append("import java.lang.reflect.Method;" + ln);
//類定義代碼
sb.append("public class " + className);
//此處判斷若是普通類則用繼承
if(!interfaces[0].isInterface()){
sb.append(" extends ").append(interfaces[0].getName());
}else{
sb.append(" implements ");
sb.append(Arrays.stream(interfaces).map(Class::getName).collect(Collectors.joining(",")));
}
sb.append("{" + ln);
//屬性
sb.append("MyInvocationHandler handler;" + ln);
// 構造方法
sb.append("public " + className + "(MyInvocationHandler handler){" + ln);
sb.append("this.handler = handler;" + ln);
sb.append("}").append(ln);
//拼接代理類的方法
for(Class<?> intface:interfaces){
for (Method method : intface.getDeclaredMethods()) {
//方法參數(shù)列表
Class<?>[] parameterTypes = method.getParameterTypes();
//用于拼接形參列表
StringBuffer formalArgs = new StringBuffer();
//用于拼接實參列表
StringBuffer realArgs = new StringBuffer();
//用于拼接實參Class類型的字符串
StringBuffer realArgsClass = new StringBuffer();
Iterator<Class<?>> iterator = Arrays.stream(parameterTypes).iterator();
while (iterator.hasNext()){
Class<?> parameterType = iterator.next();
String argName = "arg" + ATOMIC_LONG.getAndIncrement();
formalArgs.append(parameterType.getName()).append(" ").append(argName);
realArgs.append(argName);
realArgsClass.append(parameterType.getName()).append(".class");
if(iterator.hasNext()){
formalArgs.append(",");
realArgs.append(",");
realArgsClass.append(",");
}
}
//如果參數(shù)長度為0,那么傳null
String arg = parameterTypes.length > 0 ? "new Object[]{" +realArgs+"}" : "null";
//拼接成員方法體代碼
sb.append(" @Override");
sb.append(" public ").append(method.getReturnType().getName()).append(" ").append(method.getName()).append("(").append(formalArgs).append("){").append(ln);
sb.append(" try{ " + ln);
sb.append(" Method method = " + intface.getName() + ".class.getMethod(\"" + method.getName() + "\"");
//根據(jù)參數(shù)長度迈倍,確定是否添加參數(shù)
if (parameterTypes.length > 0) {
sb.append("," + realArgsClass);
}
sb.append(");" + ln+" ");
//返回值 如果為"void"该窗,則生成沒有返回值的方法調用
if (!method.getReturnType().getName().equals("void")) {
sb.append("return (" + method.getReturnType().getName() + ")");
}
sb.append("handler.invoke(this,method," + arg + ");" + ln);
//捕獲所有異常兑牡,轉換成RuntimeException異常
sb.append(" }catch (Throwable e){e.printStackTrace();throw new RuntimeException(e.getMessage());}" + ln);
sb.append("}"+ln);
}
}
sb.append("}" + ln);
return sb.toString();
}
/**
* 在磁盤生成.java文件
* @param javaSrc java源碼字符串
* @param javaFile java文件
*/
public static void generateJavaFile(String javaSrc, File javaFile) throws IOException {
try (FileWriter fw = new FileWriter(javaFile)){
fw.write(javaSrc);
}
}
/**
* 編譯.java文件
*/
public static void compilerJavaFile(File javaFile) {
//運行時編譯器 編譯代理類的Java源代碼代碼->class文件
JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
try (StandardJavaFileManager fileManager = javaCompiler.getStandardFileManager(null, null, null)){
Iterable<? extends JavaFileObject> iterable = fileManager.getJavaFileObjects(javaFile);
JavaCompiler.CompilationTask task = javaCompiler.getTask(null, fileManager, null, null, null, iterable);
task.call();
}catch (IOException e){
e.printStackTrace();
}
}
}
通過以上代碼我們就能動態(tài)的構建任意目標類的代理類。基本實現(xiàn)代碼注釋都很清楚,就不再贅述,相信都能看懂奴曙。由于這里是我們自己定義的動態(tài)構建薪介,所以加載這動態(tài)class文件也得用我們自己的Classload 實現(xiàn)抠蚣。自定義 ClassLoad 代碼如下:
package prox;
import java.io.*;
/**
* 自定義類加載器
* @author ngcly
* @version V1.0
* @since 2021/8/20 17:42
*/
public class MyClassLoader extends ClassLoader {
private File baseDir;
public MyClassLoader() {
String basePath = MyClassLoader.class.getResource("").getPath();
this.baseDir = new File(basePath);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String className = MyClassLoader.class.getPackage().getName() + "." + name;
File file = new File(this.baseDir, name.replace(".", "http://") + ".class");
try (FileInputStream in = new FileInputStream(file); ByteArrayOutputStream out = new ByteArrayOutputStream()){
in.transferTo(out);
return defineClass(className, out.toByteArray(), 0, out.size());
} catch (Exception e) {
throw new ClassNotFoundException();
}
}
}
以上代碼就實現(xiàn)了動態(tài)代理戒祠,最后調用方法如下:
package prox;
/**
* 代理類測試
* @author ngcly
* @version V1.0
* @since 2021/8/20 12:31
*/
public class Main {
public static void main(String[] args){
//靜態(tài)代理
IUserService staticProxy = new UserServiceProxy(new UserService());
//自定義接口類動態(tài)代理
IUserService diyProxy = new ClassProxyFactory<>(new UserService()).getProxyInstance();
//自定義 非接口類動態(tài)代理
NoInterfaceService diyNoProxy = new ClassProxyFactory<>(new NoInterfaceService()).getProxyInstance();
staticProxy.add("靜態(tài)代理");
diyProxy.add("自定義動態(tài)代理");
diyProxy.add();
diyNoProxy.save("非接口自定義代理");
System.out.println(diyProxy.get());
}
}
執(zhí)行輸出結果如下:
----------事務開始----------
=============保存=============靜態(tài)代理
----------事務結束----------
**********開始事務**********
=============保存=============自定義動態(tài)代理
調用目標方法返回:null
**********結束事務**********
**********開始事務**********
=============保存=============
調用目標方法返回:null
**********結束事務**********
**********開始事務**********
=============保存=============非接口自定義代理
調用目標方法返回:null
**********結束事務**********
**********開始事務**********
=============獲取=============
調用目標方法返回:捉到啦
**********結束事務**********
捉到啦
上面實現(xiàn)的動態(tài)代理既可以代理接口類 也可以代理普通類骇两,上述主方法執(zhí)行的被代理普通類代碼如下:
package prox;
/**
* 無接口的普通類
* @author ngcly
* @version V1.0
* @since 2021/8/20 23:19
*/
public class NoInterfaceService {
public void save(String msg) {
System.out.println("=============保存============="+msg);
}
}
這樣一個動態(tài)代理就完整實現(xiàn)了,當然上面的都是自定義實現(xiàn)姜盈。而JDK官方也是提供了動態(tài)代理低千,其基本實現(xiàn)原理跟上面沒什么太大差別。但是JDK動態(tài)代理有個缺陷就是只能針對接口方法進行代理馏颂,其原因就是因為JDK生成的代理類都繼承了Proxy類示血,由于Java單繼承自然就不能再對普通類進行代理。
通過我上面的自定義實現(xiàn)代碼可以發(fā)現(xiàn)救拉,我自定義實現(xiàn)的代理是可以對普通類進行動態(tài)代理的矾芙,原因自然是我沒有去繼承一個所謂的 Proxy 類,所以我就能夠去繼承被代理的普通類從而可以實現(xiàn)代理了近上,當然針對 final 類還是不能代理的,原因是final類是不可繼承拂铡。
那么從上可知壹无,明明不繼承Proxy類也可以實現(xiàn)動態(tài)代理,那為什么JDK實現(xiàn)要去繼承Proxy類而不去支持普通類的代理呢感帅?只能說一句這可能就是烏龜?shù)钠ü?龜腚吧斗锭!但是我想這種規(guī)定的用意應該就是規(guī)范吧。JDK官方認為代理都應該是針對接口進行代理失球,而對普通類代理這是不規(guī)范的岖是。同時普通類存在final等不可繼承等因素,而接口則不存在以上問題实苞,所以這也是一種規(guī)范豺撑,避免一些奇奇怪怪的操作。
由于JDK動態(tài)代理不支持對普通類的代理黔牵,所以對于普通類動態(tài)代理的實現(xiàn)則是由 三方類庫 Cglib 庫提供聪轿,而 Cglib 的實現(xiàn)本質也是基于繼承,但是實現(xiàn)方面卻有很大的區(qū)別猾浦,感興趣的小伙伴可以自己查閱陆错,這里就不多介紹。
通過自己實現(xiàn)一個動態(tài)代理金赦,可以從底層理解動態(tài)代理的實現(xiàn)原理音瓷。舉一反三也就知道官方實現(xiàn)的一個大致方式,這樣去閱讀源碼就可以知道官方實現(xiàn)在哪些方面做了優(yōu)化夹抗,以及在哪些方面實現(xiàn)的更加優(yōu)雅完美绳慎,這樣對自己的技術方面幫助還是很大的。