Java 動態代理

文章目錄

          • 靜態代理
          • Jdk動態代理
          • cglib動態代理
          • 使用案例
            • 低配Mybatis
            • 低配Feign
            • 攔截器
          • 附錄代碼



大家好,我是入錯行的bug貓。(http://blog.csdn.net/qq_41399429,謝絕轉載)
每天進步一點,今日再接再勵~


動態代理在Java中有著廣泛的應用,比如Spring AOP、Mybatis數據查詢、RPC遠程調用、性能監控,甚至事務處理等。


代理模式,根據代碼的生成時機,分為兩種:

  • 靜態代理:代碼塊在源碼階段已存在,經過編譯之后生成在class文件中;
  • 動態代理:代碼塊在運行過程中,根據運行環境參數決定代碼如何生成,自動生成class字節碼,然后再加載到JVM中;

所謂靜態,也就是在程序運行前,就已經存在代理類的字節碼文件,代理類和被代理類的關系在運行前就確定了。
而動態代理的源碼,是在程序運行期間由JVM根據反射等機制動態的生成,所以在運行前并不存在代理類的字節碼文件。


而根據動態生成字節碼的技術手段,又分兩種:

  • Jdk動態代理
  • cglib動態代理



靜態代理

先了解靜態代理,然后理解靜態代理的優缺點缺點,再來學習動態代理;

編寫一個接口IUserService,以及該接口的一個實現類UserService

interface IUserService {void findById(String uid);void update(Object user, String uid);
}class UserService implements IUserService {public void findById(String uid) {System.out.println("查詢 findById");}public void update(Object user, String uid) {System.out.println("更新 update");}
}

通過靜態代理對IUserService已存在的實例,進行功能增強;在調用findByIdupdate之前記錄一些日志,模擬開啟事務。
寫一個代理類UserServiceProxy,代理類需要實現IUserService;整體結構有些類似裝飾模式;

class UserServiceProxy implements IUserService {private final IUserService target; // 被代理的對象public UserServiceProxy() {this.target = new UserService(); //被代理對象,是在代理類中生成}@Overridepublic void findById(String uid) {try {before();target.findById(uid);    // 這里才實際調用真實對象的方法after();} catch ( Exception ex ) {exception(ex);throw ex;}}@Overridepublic void update(Object user, String uid) {try {before();target.update(user, uid);    // 這里才實際調用真實對象的方法after();} catch ( Exception ex ) {exception(ex);throw ex;}}private void before() {     // 在執行方法之前執行System.out.println(String.format("log start time [%s] ", new Date()));System.out.println("開啟事務");}private void after() {      // 在執行方法之后執行System.out.println(String.format("log end time [%s] ", new Date()));System.out.println("提交事務");}private void exception(Exception ex){System.out.println(String.format("log error [%s] ", ex.getMessage()));System.out.println("提交回滾");}
}

客戶端測試:

    @Testpublic void demo1(){IUserService proxy = new UserServiceProxy();proxy.findById("1");System.out.println("");proxy.update(new Object(), "4");}

輸出:

log start time [Sat Jun 17 09:34:05 CST 2023] 
開啟事務
查詢 selectById
log end time [Sat Jun 17 09:34:05 CST 2023] 
提交事務log start time [Sat Jun 17 09:34:05 CST 2023] 
開啟事務
更新 update
log end time [Sat Jun 17 09:34:05 CST 2023] 
提交事務

通過靜態代理,我們達到了功能增強的目的,而且沒有侵入原代碼,這是靜態代理的一個優點。

靜態代理實現簡單,且不侵入原代碼,但是,當場景稍微復雜一些的時候,靜態代理的缺點也會暴露出來:

  1. 當需要代理多個類的時候,由于代理對象要實現與目標對象一致的接口,有兩種方式:
    1.1 只維護一個代理類,由這個代理類實現多個接口,但是這樣就導致代理類過于龐大;
    1.2 新建多個代理類,每個目標對象對應一個代理類,但是這樣會產生過多的代理類

  2. 當接口需要增加、刪除、修改方法的時候,目標對象與代理類都要同時修改,不易維護。


如何改進?

可以發現,代理類的代碼塊流程絕大部分是相似的:在執行真實方法之前執行before,在執行真實方法成功之后執行after,發生異常執行exception

類似固定的模板,就可以使用程序來自動編寫代碼,用程序自動寫程序,也就是動態代理。

哪些類可以動態生成代碼?EnhancerInterfaceMakerBeanGenerator 有機會再單獨寫一篇


實現動態代理的思考方向:
為了讓生成的代理類與目標對象保持一致性,從現在開始將介紹以下兩種最常見的方式:

通過實現接口的方式:JDK動態代理
通過繼承類的方式:CGLIB動態代理




Jdk動態代理

JDK動態代理主要涉及兩個類:java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler

編寫一個調用邏輯處理器JdkProxyHandler類,提供日志增強功能,并實現InvocationHandler接口;
JdkProxyHandler中維護一個目標對象,這個對象是被代理的對象;在invoke方法中編寫方法調用的邏輯處理;

class JdkProxyHandler implements InvocationHandler {private final Object target;  // 被代理的對象,實際的方法執行者public JdkProxyHandler(Object target) {this.target = target;}/*** @param proxy 代理對象實例 todo ①* @param method 被代理類的Interface中的方法; todo ②* @param args * */@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {before();//反射調用 target 的 method 方法。//proxy是代理對象實例,因此在反射調用的時候,需要替換成被代理類target對象;Object result = method.invoke(target, args);  after();return result; // 返回方法的執行結果} catch ( Exception ex ) {exception(ex);throw ex;}}private void before() {     // 在執行方法之前執行System.out.println(String.format("log start time [%s] ", new Date()));System.out.println("開啟事務");}private void after() {      // 在執行方法之后執行System.out.println(String.format("log end time [%s] ", new Date()));System.out.println("提交事務");}private void exception(Exception ex){System.out.println(String.format("log error [%s] ", ex.getMessage()));System.out.println("提交回滾");}
}

編寫客戶端,獲取動態生成的代理類的對象須借助java.lang.reflect.Proxy類的newProxyInstance方法:

    @Testpublic void demo2(){// 設置變量可以保存動態代理類,默認名稱以 $Proxy0 格式命名System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");// 1. 創建被代理的對象。實際代碼中,可以使用到對象工廠IUserService userService = new UserService();// 2. 代理類請求處理器:攔截處理代理對象上的所有的方法調用。// 和demo1中的UserServiceProxy類相似。// 由于JdkProxyHandler可以復用,被代理類(userService)可以使多例,所以JdkProxyHandler也應該是多例,被代理類應該顯示傳入。// 主要JdkProxyHandler構造器使用Interface作為入參,因此JdkProxyHandler可以代理一切Interface的實現類。InvocationHandler proxyHandler = new JdkProxyHandler(userService);// 3. 獲取對應的 ClassLoader。類加載機制,如果搞錯ClassLoader,可能會導致動態生成的代理類,無法被加載:提示 ClassNotFoundException;保持和被代理類在一個ClassLoader中ClassLoader classLoader = userService.getClass().getClassLoader();// 4. 獲取所有接口的Class,這里的UserService只實現了一個接口IUserService,Class[] interfaces = userService.getClass().getInterfaces();/*5.根據上面提供的信息,創建代理對象 在這個過程中,a.JDK會通過根據傳入的參數信息動態地在內存中創建和.class 文件等相關字節碼;b.然后根據相應的字節碼轉換成對應的class加載到JVM中;c.然后調用newInstance()創建代理實例;*/IUserService proxy = (IUserService) Proxy.newProxyInstance(classLoader, interfaces, proxyHandler);//輸出動態代理之后的class代碼//generateClassFile(proxy.getClass(), "UserServiceProxy"); //在代碼在文章最后// 調用代理的方法proxy.findById("3");System.out.println("");proxy.update(new Object(), "4");//JDK動態代理,底層本質是使用方法反射;性能瓶頸受Jdk版本影響}

執行之后可以查看動態生成的代理類:

//Jdk動態生成的類片段
public final class UserServiceProxy extends Proxy implements IUserService {private static Method m3;private static Method m4;//注意這個入參,就是JdkProxyHandlerpublic UserServiceProxy(InvocationHandler var1) throws  {super(var1);}public final void findById(String var1) throws  {try {//注意第一個參數this,即代表JdkProxyHandler.invoke的第一個入參,是代理類本身; todo ①//第二個參數m4,是從cn.bugcat.code.IUserService中獲取 todo ②super.h.invoke(this, m4, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}public final void update(Object var1, String var2) throws  {try {super.h.invoke(this, m3, new Object[]{var1, var2});} catch (RuntimeException | Error var4) {throw var4;} catch (Throwable var5) {throw new UndeclaredThrowableException(var5);}}static {try {//findById、update方法,都是從Interface(IUserService)中獲取  todo ②m4 = Class.forName("cn.bugcat.code.IUserService").getMethod("findById", Class.forName("java.lang.String"));m3 = Class.forName("cn.bugcat.code.IUserService").getMethod("update", Class.forName("java.lang.Object"), Class.forName("java.lang.String"));} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}
}
  • UserServiceProxy繼承了Proxy類,并且實現了被代理的所有接口,以及equalshashCodetoString等方法;
  • 由于UserServiceProxy繼承了Proxy類,所以每個代理類都會關聯一個InvocationHandler方法調用處理器;
  • 類和所有方法都被public final修飾,所以代理類只可被使用,不可以再被繼承;
  • 每個方法都有一個Method對象來描述,Method對象在static靜態代碼塊中創建,以m + 數字的格式命名;
  • 調用方法的時候通過super.h.invoke(this, m1, new Object[]{});調用,其中的super.h.invoke實際上是在創建代理的時候傳遞給Proxy.newProxyInstanceJdkProxyHandler對象,它繼承InvocationHandler類,負責實際的調用處理邏輯;
  • JdkProxyHandlerinvoke方法接收到methodargs等參數后,進行一些處理,然后通過反射讓被代理的對象target執行方法;

仔細觀察JdkProxyHandler,構造器中入參使用Object類型接受,也就是意味JdkProxyHandler類不光可以傳入UserService實現類,也可以傳入其他任何對象實例;
Proxy.newProxyInstance是根據第二個參數Interfaces動態生成方法,而這些方法恰好UserService也實現了,代理類和具體類,通過interfaces聯系到一起;
最終執行JdkProxyHandler構造器 入參對象的所有方法 ,都會統一執行JdkProxyHandler.invoke代碼。




cglib動態代理

spring 框架中內置了cglib相關Jar包內容,spring項目可以直接使用:

被代理類:原對象;
代理子類:由cglib自動根據原對象生成的子類;

class CglibProxyHandler implements MethodInterceptor {/*** @param target 代理子類對象;* @param method 被代理類的方法;* @param args * @param methodProxy 被代理的句柄方法  todo ③* */@Overridepublic Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {try {before();//注意這里是調用invokeSuper而不是invoke,否則死循環。 todo ③//method是被代理的方法,但是由于tagert是代理子類,執行method.invoke,實際上是表示執行代理子類的方法,代理子類又會繼續執行MethodInterceptor.intercept方法,導致又回到此處代碼,造成死循環;//此處應該直接執行invokeSuper,表示直接調用被代理類的句柄方法;Object result = methodProxy.invokeSuper(target, args); after();return result; // 返回方法的執行結果} catch ( Exception ex ) {exception(ex);throw ex;}}private void before() {     // 在執行方法之前執行System.out.println(String.format("log start time [%s] ", new Date()));System.out.println("開啟事務");}private void after() {      // 在執行方法之后執行System.out.println(String.format("log end time [%s] ", new Date()));System.out.println("提交事務");}private void exception(Exception ex){System.out.println(String.format("log error [%s] ", ex.getMessage()));System.out.println("提交回滾");}
}

調用方式:

    @Test    public void demo3(){System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, paths);MethodInterceptor proxyHandler = new CglibProxyHandler();Enhancer enhancer = new Enhancer();enhancer.setSuperclass(UserService.class);  // 設置超類,cglib是通過繼承來實現的enhancer.setCallback(proxyHandler);IUserService userService = (IUserService)enhancer.create();   // 創建代理類userService.findById("4");userService.update(new Object(), "4");}

動態生成類片段:

public class UserService$$EnhancerByCGLIB$$a1b35990 extends UserService implements Factory {final void CGLIB$update$0(Object var1, String var2) {super.update(var1, var2); //調用父類方法 => UserService.update}public final void update(Object var1, String var2) { //代理類暴露的方法,等價于重寫了父類UserService的update方法MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;  // 實際上是我們自定義的CglibProxyHandler對象if (var10000 == null) {CGLIB$BIND_CALLBACKS(this);var10000 = this.CGLIB$CALLBACK_0;}if (var10000 != null) {// 第一個參數,代理類對象  todo ③// CGLIB$update$0$Method 父類對應的方法// CGLIB$update$0$Proxy當前代理的 CGLIB$update$0方法,該方法會繼續調用父類的對應方法var10000.intercept(this, CGLIB$update$0$Method, new Object[]{var1, var2}, CGLIB$update$0$Proxy);} else {super.update(var1, var2);}}
}	

Jdk動態代理與cglib動態代理對比

  • Jdk動態代理:基于Java反射機制實現,必須要實現了接口的類,才能用這種辦法生成代理對象。
  • cglib動態代理:基于ASM機制實現,通過操作字節碼生成子類,代理代碼塊集成在子類上,實際調用被代理類的方法時,和原生調用效率一樣;
  • cglib由于使用繼承被代理實現,因此如果是類、方法被final修飾,則無法使用!被cglib代理的類,無法再次被代理!
  • Jdk不受final關鍵詞影響;被代理之后,仍然可以被Jdk再次代理;
  • cglib在創建代理子類的時候,可以通過CallbackFilter,可以為每個方法創建單獨的MethodInterceptor;后續調用時,方法與方法之間,代碼在物理層面隔離,互相不影響;
  • Jdk只能在InvocationHandler實現類中,需要自行做分支處理;




使用案例
低配Mybatis
interface UserDao {String findById(String uid);String update(Object user, String uid);
}class MapperProxyHandler implements MethodInterceptor {private static Map<String, Map<String, String>> mapperMap = new HashMap<>();static {//初始化,模擬Mapper.xml文件Map<String, String> mapper = new HashMap<>();//Map的key,及為UserDao中的方法名,以及Mapper.xml的標簽idmapper.put("findById", "select * from user where id = ?");mapper.put("update", "update user set name = ? where id = ?");//UserDao.class.getName(),對應到Mapper.xml的namespacemapperMap.put(UserDao.class.getName(), mapper); }@Overridepublic Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {String methodName = method.getName();Class<?> mapperClass = method.getDeclaringClass();Map<String, String> mapper = mapperMap.get(mapperClass.getName());if( mapper != null ){return mapper.get(methodName);}return null;}
}@Testpublic void demo4(){System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, paths);MethodInterceptor proxyHandler = new MapperProxyHandler();Enhancer enhancer = new Enhancer();enhancer.setSuperclass(Object.class);  // 設置超類;此時UserDao只是一個Interface,沒有任何具體的實現類,因此超類設置為默認的Object;enhancer.setInterfaces(new Class[]{UserDao.class});enhancer.setCallback(proxyHandler);UserDao userDao = (UserDao)enhancer.create();   // 創建代理類String findById = userDao.findById("4");System.out.println(findById); //打印sqlString update = userDao.update(new Object(), "4");System.out.println(update);}



低配Feign

interface MyFeign {@RequestMapping(value = "http://127.0.0.1:8080/aq", method = RequestMethod.POST)ResponseEntity<Void> aq();@RequestMapping(value = "https://www.baidu.com/s", method = RequestMethod.GET)String baidu(@RequestParam("wd") String keyword);}class FeignProxyHandler implements MethodInterceptor {/*** target:代理子類* method:被代理類方法*/@Overridepublic Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {//method是被代理類中的方法,因此可以通過method獲取到:方法上注解;入參列表;入參上的注解;方法的返回類型RequestMapping requestMapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);RequestMethod[] requestMethods = requestMapping.method();String[] paths = requestMapping.value();Type returnType = method.getGenericReturnType();Parameter[] parameters = method.getParameters();Map<String, Integer> argsIndex = new HashMap<>();for ( int idx = 0; idx < parameters.length; idx++ ) {Parameter parameter = parameters[idx];RequestParam requestParam = AnnotationUtils.findAnnotation(parameter, RequestParam.class);argsIndex.put(requestParam.name(), idx);}Map<String, Object> reqMap = new HashMap<>();argsIndex.forEach((pname, idx) -> {reqMap.put(pname, args[idx]);});if( requestMethods[0] == RequestMethod.GET ){String resp = HttpFactory.mutate().doGet().send(paths[0], reqMap);return resultType(resp, returnType);} else if (requestMethods[0] == RequestMethod.POST ){String resp = HttpFactory.mutate().doPost().send(paths[0], reqMap);return resultType(resp,returnType);}return null;}private <R> R resultType(String resp, Type type){if( resp == null ){return null;}if( type instanceof Class ){if( String.class.isAssignableFrom((Class) type) ){return (R) resp;}}return JSONObject.parseObject(resp, type);}    
}@Testpublic void demo5(){System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, paths);MethodInterceptor proxyHandler = new FeignProxyHandler();Enhancer enhancer = new Enhancer();enhancer.setSuperclass(Object.class);  // 設置超類;此時MyFeign只是一個Interface,沒有任何具體的實現類,因此超類設置為默認的Object;enhancer.setInterfaces(new Class[]{MyFeign.class});enhancer.setCallback(proxyHandler);MyFeign feign = (MyFeign)enhancer.create();   // 創建代理類ResponseEntity<Void> aq = feign.aq();System.out.println(JSONObject.toJSONString(aq));String baidu = feign.baidu("csdn");System.out.println(baidu);}

攔截器

攔截器,不屬于sevletfilter這一類物理層面存在的組件,而是通過代碼邏輯出來的一種代碼結構;通過對動態代理學習,我們可以在Object result = method.invoke(target, args); Object result = methodProxy.invokeSuper(target, args); 代碼的前后,添加自定義的代碼,增強被代理類方法;

但是,這部分屬于核心代碼JdkProxyHandler CglibProxyHandler,一般編寫完之后,會統一存放在公共位置。如果業務模塊想添加功能,勢必需要修改到這部分核心代碼;違背了支持擴展、封閉修改原則;

因此在此基礎之上,邏輯出一套攔截器模式,對業務模塊開放修改;

//切入點對象
class Point {private final Object target;private final Method method;private final Object[] args;private final MethodProxy methodProxy;public Point(Object target, Method method, Object[] args, MethodProxy methodProxy) {this.target = target;this.method = method;this.args = args;this.methodProxy = methodProxy;}public Object postHandle() throws Throwable{Object result = methodProxy.invokeSuper(target, args);return result;}
}//攔截器接口
interface MyInterceptor {default Object doInterceptor(Point point) throws Throwable {return point.postHandle();}
}class MyProxyHandler implements MethodInterceptor {private final MyInterceptor interceptor;public MyProxyHandler(MyInterceptor interceptor) {this.interceptor = interceptor;}@Overridepublic Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {//注意,本應該在此處執行的「Object result = methodProxy.invokeSuper(target, args);」,被轉移到Point#postHandle方法內了 todo ⑤Point point = new Point(target, method, args, methodProxy);return interceptor.doInterceptor(point);}
}@Testpublic void demo6() {MyInterceptor interceptor = new MyInterceptor(){@Overridepublic Object doInterceptor(Point point ) throws Throwable {try {System.out.println("befor");Object resp = point.postHandle(); //在此處,才真正執行被代理類的方法  todo ⑤;//本應該在MethodInterceptor子類中執行的代碼,被轉移到MyInterceptor子類了;MethodInterceptor子類只能有一個,但是MyInterceptor子類,結合責任鏈設計模式,卻可以有很多個!System.out.println("success");return resp;} catch ( Throwable e ) {System.out.println("error");throw e;} finally {System.out.println("finally");}}};MethodInterceptor proxyHandler = new MyProxyHandler(interceptor);Enhancer enhancer = new Enhancer();enhancer.setSuperclass(UserService.class);  // 設置超類,cglib是通過繼承來實現的enhancer.setCallback(proxyHandler);IUserService userService = (IUserService)enhancer.create();   // 創建代理類userService.findById("4");userService.update(new Object(), "4");}



可用來動態生成代碼的有很多,這里推薦cglib的EnhancerInterfaceMakerBeanGeneratorClassWriter類;
畢竟現在基本上都是spring全家桶,而且cglib也被spring收編,只要是spring項目就可以直接使用;

動態代理也是面試必考:涉及到AOP、攔截器;而AOP的運用又是不勝枚舉,攔截器結合責任鏈模式,也是在各種場所都用運用;

以上,就醬~

~the end~

附錄代碼

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.io.FileUtils;
import org.junit.Test;
import org.springframework.cglib.core.DebuggingClassWriter;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.client.RestTemplate;
import sun.misc.ProxyGenerator;import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;public class ProxyDemo {private static String paths = "E:\\tmp\\proxy\\";/*** 將根據類信息動態生成的二進制字節碼保存到硬盤中,默認的是clazz目錄下* params: clazz 需要生成動態代理類的類* proxyName: 為動態生成的代理類的名稱*/public static void generateClassFile(Class clazz, String proxyName) {try {File file = new File(paths);FileUtils.deleteDirectory(new File(paths));file.mkdirs();} catch ( IOException e ) {e.printStackTrace();}// 根據類信息和提供的代理類名稱,生成字節碼byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());try (FileOutputStream out = new FileOutputStream(paths + proxyName + ".class")){out.write(classFile);out.flush();} catch (Exception e) {e.printStackTrace();}}@Testpublic void demo1(){IUserService proxy = new UserServiceProxy();proxy.findById("1");System.out.println("");proxy.update(new Object(), "4");}@Testpublic void demo2(){// 設置變量可以保存動態代理類,默認名稱以 $Proxy0 格式命名System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");// 1. 創建被代理的對象。實際代碼中,可以使用到對象工廠IUserService userService = new UserService();// 2. 代理類請求處理器:攔截處理代理對象上的所有的方法調用。// 和demo1中的UserServiceProxy類相似。// 由于JdkProxyHandler可以復用,被代理類(userService)可以使多例,所以JdkProxyHandler也應該是多例,被代理類應該顯示傳入。// 主要JdkProxyHandler構造器使用Interface作為入參,因此JdkProxyHandler可以代理一切Interface的實現類。InvocationHandler proxyHandler = new JdkProxyHandler(userService);// 3. 獲取對應的 ClassLoader。類加載機制,如果搞錯ClassLoader,可能會導致動態生成的代理類,無法被加載:提示 ClassNotFoundException;保持和被代理類在一個ClassLoader中ClassLoader classLoader = userService.getClass().getClassLoader();// 4. 獲取所有接口的Class,這里的UserService只實現了一個接口IUserService,Class[] interfaces = userService.getClass().getInterfaces();/*5.根據上面提供的信息,創建代理對象 在這個過程中,a.JDK會通過根據傳入的參數信息動態地在內存中創建和.class 文件等相關字節碼;b.然后根據相應的字節碼轉換成對應的class加載到JVM中;c.然后調用newInstance()創建代理實例;*/IUserService proxy = (IUserService) Proxy.newProxyInstance(classLoader, interfaces, proxyHandler);//輸出動態代理之后的class代碼//generateClassFile(proxy.getClass(), "UserServiceProxy"); //在代碼在文章最后// 調用代理的方法proxy.findById("3");System.out.println("");proxy.update(new Object(), "4");//JDK動態代理,底層本質是使用方法反射;性能瓶頸受Jdk版本影響}@Test    public void demo3(){System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, paths);MethodInterceptor proxyHandler = new CglibProxyHandler();Enhancer enhancer = new Enhancer();enhancer.setSuperclass(UserService.class);  // 設置超類,cglib是通過繼承來實現的enhancer.setCallback(proxyHandler);IUserService userService = (IUserService)enhancer.create();   // 創建代理類userService.findById("4");userService.update(new Object(), "4");}@Testpublic void demo4(){System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, paths);MethodInterceptor proxyHandler = new MapperProxyHandler();Enhancer enhancer = new Enhancer();enhancer.setSuperclass(Object.class);  // 設置超類;此時UserDao只是一個Interface,沒有任何具體的實現類,因此超類設置為默認的Object;enhancer.setInterfaces(new Class[]{UserDao.class});enhancer.setCallback(proxyHandler);UserDao userDao = (UserDao)enhancer.create();   // 創建代理類String findById = userDao.findById("4");System.out.println(findById); //打印sqlString update = userDao.update(new Object(), "4");System.out.println(update);}@Testpublic void demo5(){System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, paths);MethodInterceptor proxyHandler = new FeignProxyHandler();Enhancer enhancer = new Enhancer();enhancer.setSuperclass(Object.class);  // 設置超類;此時MyFeign只是一個Interface,沒有任何具體的實現類,因此超類設置為默認的Object;enhancer.setInterfaces(new Class[]{MyFeign.class});enhancer.setCallback(proxyHandler);MyFeign feign = (MyFeign)enhancer.create();   // 創建代理類ResponseEntity<Void> aq = feign.aq();System.out.println(JSONObject.toJSONString(aq));String baidu = feign.baidu("csdn");System.out.println(baidu);}@Testpublic void demo6() {MyInterceptor interceptor = new MyInterceptor(){@Overridepublic Object doInterceptor(Point point ) throws Throwable {try {System.out.println("befor");Object resp = point.postHandle(); //在此處,才真正執行被代理類的方法  todo ⑤;//本應該在MethodInterceptor子類中執行的代碼,被轉移到MyInterceptor子類了;MethodInterceptor子類只能有一個,但是MyInterceptor子類,結合責任鏈設計模式,卻可以有很多個!System.out.println("success");return resp;} catch ( Throwable e ) {System.out.println("error");throw e;} finally {System.out.println("finally");}}};MethodInterceptor proxyHandler = new MyProxyHandler(interceptor);Enhancer enhancer = new Enhancer();enhancer.setSuperclass(UserService.class);  // 設置超類,cglib是通過繼承來實現的enhancer.setCallback(proxyHandler);IUserService userService = (IUserService)enhancer.create();   // 創建代理類userService.findById("4");userService.update(new Object(), "4");}}interface IUserService {void findById(String uid);void update(Object user, String uid);
}class UserService implements IUserService {public void findById(String uid) {System.out.println("查詢 findById");}public void update(Object user, String uid) {System.out.println("更新 update");}
}class UserServiceProxy implements IUserService {private final IUserService target; // 被代理的對象public UserServiceProxy() {this.target = new UserService(); //被代理對象,是在代理類中生成}@Overridepublic void findById(String uid) {try {before();target.findById(uid);    // 這里才實際調用真實對象的方法after();} catch ( Exception ex ) {exception(ex);throw ex;}}@Overridepublic void update(Object user, String uid) {try {before();target.update(user, uid);    // 這里才實際調用真實對象的方法after();} catch ( Exception ex ) {exception(ex);throw ex;}}private void before() {     // 在執行方法之前執行System.out.println(String.format("log start time [%s] ", new Date()));System.out.println("開啟事務");}private void after() {      // 在執行方法之后執行System.out.println(String.format("log end time [%s] ", new Date()));System.out.println("提交事務");}private void exception(Exception ex){System.out.println(String.format("log error [%s] ", ex.getMessage()));System.out.println("提交回滾");}
}class JdkProxyHandler implements InvocationHandler {private final Object target;  // 被代理的對象,實際的方法執行者public JdkProxyHandler(Object target) {this.target = target;}/*** @param proxy 代理對象實例 todo ①* @param method 被代理類的Interface中的方法; todo ②* @param args * */@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {before();//反射調用 target 的 method 方法。//proxy是代理對象實例,因此在反射調用的時候,需要替換成被代理類target對象;Object result = method.invoke(target, args);after();return result; // 返回方法的執行結果} catch ( Exception ex ) {exception(ex);throw ex;}}private void before() {     // 在執行方法之前執行System.out.println(String.format("log start time [%s] ", new Date()));System.out.println("開啟事務");}private void after() {      // 在執行方法之后執行System.out.println(String.format("log end time [%s] ", new Date()));System.out.println("提交事務");}private void exception(Exception ex){System.out.println(String.format("log error [%s] ", ex.getMessage()));System.out.println("提交回滾");}
}class CglibProxyHandler implements MethodInterceptor {/*** @param target 代理子類對象;* @param method 被代理類的方法;* @param args * @param methodProxy 被代理的句柄方法  todo ③* */@Overridepublic Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {try {before();//注意這里是調用invokeSuper而不是invoke,否則死循環。 todo ③//method是被代理的方法,但是由于tagert是代理子類,執行method.invoke,實際上是表示執行代理子類的方法,代理子類又會繼續執行MethodInterceptor.intercept方法,導致又回到此處代碼,造成死循環;//此處應該直接執行invokeSuper,表示直接調用被代理類的句柄方法;Object result = methodProxy.invokeSuper(target, args);after();return result; // 返回方法的執行結果} catch ( Exception ex ) {exception(ex);throw ex;}}private void before() {     // 在執行方法之前執行System.out.println(String.format("log start time [%s] ", new Date()));System.out.println("開啟事務");}private void after() {      // 在執行方法之后執行System.out.println(String.format("log end time [%s] ", new Date()));System.out.println("提交事務");}private void exception(Exception ex){System.out.println(String.format("log error [%s] ", ex.getMessage()));System.out.println("提交回滾");}
}interface MyProxyInterceptor {boolean preHandler();void before();void after();void exception(Exception ex);
}class CglibProxyHandler1 implements MethodInterceptor {private final List<MyProxyInterceptor> interceptors; // 100public CglibProxyHandler1(List<MyProxyInterceptor> interceptors) {this.interceptors = interceptors;}@Overridepublic Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {//2List<MyProxyInterceptor> interceptors = this.interceptors.stream().filter(interceptor -> interceptor.preHandler()).collect(Collectors.toList());try {interceptors.forEach(intercept -> {intercept.before();});Object result = methodProxy.invokeSuper(target, args);interceptors.forEach(intercept -> {intercept.after();});return result; // 返回方法的執行結果} catch ( Exception ex ) {interceptors.forEach(intercept -> {intercept.exception(ex);});throw ex;}}
}interface UserDao {String findById(String uid);String update(Object user, String uid);
}class MapperProxyHandler implements MethodInterceptor {private static Map<String, Map<String, String>> mapperMap = new HashMap<>();static {//初始化,模擬Mapper.xml文件Map<String, String> mapper = new HashMap<>();//Map的key,及為UserDao中的方法名,以及Mapper.xml的標簽idmapper.put("findById", "select * from user where id = ?");mapper.put("update", "update user set name = ? where id = ?");//UserDao.class.getName(),對應到Mapper.xml的namespacemapperMap.put(UserDao.class.getName(), mapper);}@Overridepublic Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {String methodName = method.getName();Class<?> mapperClass = method.getDeclaringClass();Map<String, String> mapper = mapperMap.get(mapperClass.getName());if( mapper != null ){return mapper.get(methodName);}return null;}
}interface MyFeign {@RequestMapping(value = "http://127.0.0.1:8080/aq", method = RequestMethod.POST)ResponseEntity<Void> aq();@RequestMapping(value = "https://www.baidu.com/s", method = RequestMethod.GET)String baidu(@RequestParam("wd") String keyword);}class FeignProxyHandler implements MethodInterceptor {/*** target:代理子類* method:被代理類方法*/@Overridepublic Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {//method是被代理類中的方法,因此可以通過method獲取到:方法上注解;入參列表;入參上的注解;方法的返回類型RequestMapping requestMapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);RequestMethod[] requestMethods = requestMapping.method();String[] paths = requestMapping.value();Type returnType = method.getGenericReturnType();Parameter[] parameters = method.getParameters();Map<String, Integer> argsIndex = new HashMap<>();for ( int idx = 0; idx < parameters.length; idx++ ) {Parameter parameter = parameters[idx];RequestParam requestParam = AnnotationUtils.findAnnotation(parameter, RequestParam.class);argsIndex.put(requestParam.name(), idx);}Map<String, Object> reqMap = new HashMap<>();argsIndex.forEach((pname, idx) -> {reqMap.put(pname, args[idx]);});RestTemplate rest = new RestTemplate();if( requestMethods[0] == RequestMethod.GET ){String resp = rest.getForObject(paths[0], String.class, reqMap);return resultType(resp, returnType);} else if (requestMethods[0] == RequestMethod.POST ){String resp = rest.postForObject(paths[0], reqMap, String.class);return resultType(resp,returnType);}return null;}private <R> R resultType(String resp, Type type){if( resp == null ){return null;}if( type instanceof Class ){if( String.class.isAssignableFrom((Class) type) ){return (R) resp;}}return JSONObject.parseObject(resp, type);}
}class Point {private final Object target;private final Method method;private final Object[] args;private final MethodProxy methodProxy;public Point(Object target, Method method, Object[] args, MethodProxy methodProxy) {this.target = target;this.method = method;this.args = args;this.methodProxy = methodProxy;}public Object postHandle() throws Throwable{Object result = methodProxy.invokeSuper(target, args);return result;}
}//攔截器接口
interface MyInterceptor {default Object doInterceptor(Point point) throws Throwable {return point.postHandle();}
}class MyProxyHandler implements MethodInterceptor {private final MyInterceptor interceptor;public MyProxyHandler(MyInterceptor interceptor) {this.interceptor = interceptor;}@Overridepublic Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {//注意,本應該在此處執行的[Object result = methodProxy.invokeSuper(target, args);],被轉移到Point#postHandle方法內了 todo ⑤Point point = new Point(target, method, args, methodProxy);return interceptor.doInterceptor(point);}
}





本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/41084.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/41084.shtml
英文地址,請注明出處:http://en.pswp.cn/news/41084.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

“深入探索JVM內部機制:解密Java虛擬機“

標題&#xff1a;深入探索JVM內部機制&#xff1a;解密Java虛擬機 摘要&#xff1a;本篇博客將深入剖析Java虛擬機&#xff08;JVM&#xff09;的內部機制&#xff0c;包括類加載、內存管理、垃圾回收、即時編譯等關鍵組成部分。通過對JVM內部機制的解密&#xff0c;我們可以更…

團團代碼生成器V1.0:一鍵生成完整的CRUD功能(提供Gitee源碼)

前言&#xff1a;在日常開發的中&#xff0c;經常會需要重復寫一些基礎的增刪改查接口&#xff0c;雖說不難&#xff0c;但是會耗費我們一些時間&#xff0c;所以我自己開發了一套純SpringBoot實現的代碼生成器&#xff0c;可以為我們生成單條數據的增刪改查&#xff0c;還可以…

中遠麒麟堡壘機 SQL注入漏洞復現

0x01 產品簡介 中遠麒麟依托自身強大的研發能力,豐富的行業經驗&#xff0c;自主研發了新一代軟硬件一體化統一安全運維平臺一-iAudit 統一安全運維平臺。該產品支持對企業運維人員在運維過程中進行統一身份認證、統一授權、統一審計、統一監控&#xff0c;消除了傳統運維過程中…

實現Python腳本錄制功能

要實現Python腳本錄制功能&#xff0c;可以使用Python的內置模塊pyautogui和opencv。 首先&#xff0c;需要安裝這兩個模塊&#xff1a; pip install pyautogui opencv-python 然后&#xff0c;可以編寫以下代碼來實現腳本錄制功能&#xff1a; import cv2 import numpy as …

CentOS7.6安裝配置MySQL 5.7及常用命令匯總

一、MySQL安裝&#xff08;rpm安裝&#xff09; 1、檢查沒有安裝過mysql或mariadb rpm -qa | grep -i mysql rpm -qa | grep -i mariadb 返回空值的話&#xff0c;就說明沒有安裝 MySQL。注意&#xff1a;在新版本的CentOS7中&#xff0c;默認的數據庫已更新為了Mariadb&#…

你需要需求管理解決方案的三個原因

我們最近研究了一份 Forrester Research 的報告&#xff0c;得出如下結論&#xff1a;高度監管的行業可以從敏捷需求管理方法中受益。在本文中&#xff0c;我們將深入探討所有行業的客戶如何從一個協作平臺中受益&#xff0c;該平臺如何幫助他們在復雜的開發周期中管理需求。 …

【支付寶小程序】支付寶小程序自定義組件技術教程

&#x1f996;我是Sam9029&#xff0c;一個前端 Sam9029的CSDN博客主頁:Sam9029的博客_CSDN博客-JS學習,CSS學習,Vue-2領域博主 **&#x1f431;?&#x1f409;&#x1f431;?&#x1f409;恭喜你&#xff0c;若此文你認為寫的不錯&#xff0c;不要吝嗇你的贊揚&#xff0c…

接口設置了responseType:‘blob‘后,接收不到后端錯誤信息

下載文件流&#xff0c;需要接口設置responseType:blob&#xff0c;接口設置了responseType:blob后&#xff0c;拿不到后端接口的異常信息&#xff0c;我們只需要添加如下代碼&#xff1a; const service axios.create({baseURL: ***, // url base url request url// withC…

影視公司技術流程設計之服務器搭建

在影視公司&#xff0c;硬件的投入占相當大的比例&#xff0c; 大到存儲&#xff0c; 服務器&#xff0c;工作站&#xff0c; 小到主機CPU&#xff0c;內存&#xff0c;顯卡&#xff0c;手繪板。 而存儲又是硬件上的大頭&#xff0c;一套合理的存儲解決方案&#xff0c;優為關鍵…

【kubernetes】持久卷PV、PVC

目錄 PV和PVC之間的相互作用遵循這個生命周期 根據這 5 個階段&#xff0c;PV 的狀態有以下 4 種 一個PV從創建到銷毀的具體流程如下 靜態PV創建 1、配置nfs存儲 2、定義PV 3、定義PVC 4、測試訪問 動態PV創建 1、在stor01節點上安裝nfs&#xff0c;并配置nfs服務 2…

計算機視覺之三維重建(二)(攝像機標定)

標定示意圖 標定目標 P ′ M P w K [ R T ] P w P^{}MP_wK[R \space T]P_w P′MPw?K[R T]Pw? 其中 K K K為內參數&#xff0c; [ R T ] [R \space T] [R T]為外參數。該式子需要使用至少六對內外點對進行求解內外參數&#xff08;11個未知參數&#xff09;。 其中 R 3 3 …

windows系統丟失mfc120u.dll的解決方法

1.mfc120u.dll是什么 mfc120u.dll是Windows操作系統中的一個動態鏈接庫&#xff08;Dynamic Link Library&#xff0c;簡稱DLL&#xff09;文件。它包含了一些用于運行C程序的函數和其他資源。這個特定的DLL文件是Microsoft Foundation Classes&#xff08;MFC&#xff09;庫的…

freeswitch的mod_xml_curl模塊動態獲取configuration

概述 freeswitch是一款簡單好用的VOIP開源軟交換平臺。 mod_xml_curl模塊支持從web服務獲取xml配置&#xff0c;本文介紹如何動態獲取acl配置。 環境 centos&#xff1a;CentOS release 7.0 (Final)或以上版本 freeswitch&#xff1a;v1.6.20 GCC&#xff1a;4.8.5 web…

第2章 性能測量

理解應用程序性能的第一步是學會對它進行測量。 與絕大多數功能問題相比&#xff0c;性能問題通常很難跟蹤和復現。 任何關注過性能評估的人可能都知道公允地進行性能測量并從中得到準確結論是多么困難。 因為在測量中存在誤差&#xff0c;性能分析通常需要統計方法進行處理…

ThreadLocal(超詳細介紹!!)

關于ThreadLocal&#xff0c;可能很多同學在學習Java的并發編程部分時&#xff0c;都有所耳聞&#xff0c;但是如果要仔細問ThreadLocal是個啥&#xff0c;我們可能也說不清楚&#xff0c;所以這篇博客旨在幫助大家了解ThreadLocal到底是個啥&#xff1f; 1.ThreadLocal是什么&…

Android設備通過藍牙HID技術模擬鍵盤實現

目錄 一&#xff0c;背景介紹 二&#xff0c;技術方案 2.1 獲取BluetoothHidDevice實例 2.2 注冊/解除注冊HID實例 2.3 Hid report description描述符生成工具 2.4 鍵盤映射表 2.5 通過HID發送鍵盤事件 三&#xff0c;實例 一&#xff0c;背景介紹 日常生活中&#xff0…

AndroidStudio中修改打包生成的apk名稱

1.配置手機架構 splits {abi {enable truereset()include armeabi-v7a,arm64-v8auniversalApk false} } 2.多渠道 productFlavors {normal {applicationId "*****"manifestPlaceholders [appName: "string/app_name_normal"]}driver {applicationId &qu…

圖片轉換成pdf格式?這幾種轉換格式方法了解一下

圖片轉換成pdf格式&#xff1f;將圖片轉換成PDF格式的好處有很多。首先&#xff0c;PDF格式具有通用性&#xff0c;可以在幾乎任何設備上查看。其次&#xff0c;PDF格式可以更好地保護文件&#xff0c;防止被篡改或者復制。此外&#xff0c;PDF格式還可以更好地壓縮文件大小&am…

使用Kaptcha生成驗證碼

說明&#xff1a;驗證碼&#xff0c;是登錄流程中必不可少的一環&#xff0c;一般企業級的系統&#xff0c;使用都是專門制作驗證碼、審核校驗的第三方SDK&#xff08;如極驗&#xff09;。本文介紹&#xff0c;使用谷歌提供的Kaptcha技術&#xff0c;制作一個簡單的驗證碼。 …

sqlserver數據庫導出到mysql

愛到分才顯珍貴&#xff0c;很多人都不懂珍惜擁有&#xff0c;只到失去才看到&#xff0c;其實那最熟悉的才最珍貴的。 這里只介紹一種方式&#xff0c;有很多的方式。 1.使用Navicat 安裝 下載 2.工具 數據傳輸 3.選擇源和目標 然后開始 4.最好導入前備份一下庫