最近項目因爲一些需求需要網關平臺做增強,需要通過agent做字節碼的修改,項目同事提議用Javassist & ASM, 考慮到團隊技術儲備,ASM項目上手難度大學期週期長,Javassist代碼不夠優雅,排錯比較麻煩,故而考慮使用今天要講的主角ByteBuddy.
1.Bytebuddy的定義
ByteBuddy是一個字節碼生成和操作庫。除了Java類庫附帶的代碼生成實用程序外,ByteBuddy還允許創建任意類,並且不限於實現用於創建運行時代理的接口。此外,ByteBuddy提供了一種方便的API,可以使用Java代理或在構建過程中手動更改類。 無需理解字節碼指令,即可使用簡單的API就能很容易操作字節碼,控制類和方法。 已支持Java11,庫輕量,僅取決於Java字節代碼解析器庫ASM的訪問者API,它本身不需要任何其他依賴項。 比起JDK動態代理、cglib、Javassist,ByteBuddy在性能上具有一定的優勢。
2.Bytebuddy的應用場景
1:動態生成JAVA CLASS 這是ByteBuddy最基本但是最有用的USECASE, 比如說,我定義一個SCHEMA,希望根據SCHEMA生成對應的JAVA類並加載到JVM中. 當然你可以用一些模板工具比如VELOCITY之類生成JAVA源代碼再進行編譯,
但是這樣一般需要對項目的BUILD腳本(maven/gradle)進行一些定製. 用ByteBuddy生成類的強大之處在於, 這個類可以通過JAVA代碼生成並且可以自動加載到當前的JVM中,這樣我可以在application初始化的時候從SCHEMA REGISTRY中讀取到SCHEMA定義,
通過ByteBuddy生成對應的JAVA類加載到JVM中, 並且一旦SCHEMA發生變化如增加新屬性, 不需要做任何改動, 在JVM重啓後類定義會自動同步更新. 當然一個限制是你只能用反射的方式訪問這個新定義的類. 2:JAVA AGENT代理. 這是另一個異常強大的功能,可以動態修改類的定義,用於強行修改一些第三方LIB中一些不容易擴展的類,而不需要修改類的源代碼和JAR包. 不知道大家有沒有用過JAVA本身的AGENT, 在一些性能監控的工具常常會用到,
需要在JVM啓動的時候加agentlib參數,在AGENT中可以對原始JAVA的二進制碼做增強埋點. ByteBuddy強大之處是它連agentlib參數都不需要了,侵入性更小.
一個很好的例子是FLYWAY,我們需要在FLYWAY執行數據庫腳本的時候將腳本執行到一個額外的數據庫(SPANNER). 需要擴展org.flywaydb.core.internal.resolver.sql.SqlMigrationExecutor的execute方法
執行額外的寫操作, 不得不吐槽一下FLYWAY,一大堆的繼承接口但是代碼基本沒法擴展. 這時可以通過ByteBuddy Agent 攔截SqlMigrationExecutor的execute方法,在原始方法執行之後實現額外的數據庫寫操作.
唯一需要提的一點是ByteBuddy Agent只支持JVM不支持JRE. 3:AOP切面 和場景2類似,但有時我們不需要改變原始類的實現,而是希望產生一個新的類對原始類的某些行爲做增強. AOP本質是生成一個新的PROXY代理類替換原有的實現.JAVA本身提供了基於InvocationHandler的DynamicProxy,
但是有幾個比較大的限制. 1. 被攔截的類必須實現一個接口. 2. InvocationHandler 只提供了一個方法: public Object invoke(Object proxy, Method method, Object[] args) throws Throwable.
假設被攔截的接口有很多個方法, 如java.sql.PreparedStatement, 需要對某些方法進行特殊處理,那需要基於方法名寫一大堆的If/else邏輯,代碼不夠優雅. Spring提供了基於AspectJ的AOP, 但是這個強依賴於Spring體系必須是在Spring容器中受管理的Bean.
而ByteBuddy則可通過靈活的匹配模式指定需要代理的方法,其他方法則可默認爲原始類的實現不改變行爲. 並且類似於ASPECTJ, 切面的實現可以獨立出來. 一個使用場景是代理java.sql.DataSource/Connection/PreparedStatement/ResultSet.
指標統計,分庫分表等實現都需要. 這裏實現了一個簡單通用的代理織入器,可以對某個類的某一組方法應用一個Advisor攔截器,返回一個被增強的原始類的子類.
3. Bytebuddy的基礎方法
new ByteBuddy().subclass(Foo.class) - 可以用來擴展父類或接口的方法(可以理解成子類繼承父類,或者接口實現類) new ByteBuddy().redefine(Foo.class) - 當重定義一個類時,Bytebuddy可以對一個已有的類添加屬性和方法,或者刪除已經存在的方法實現。如果使用其他的方法實現替換已經存在的方法實現,則原來存在的方法實現就會消失。 new ByteBuddy().rebase(Foo.class) - 當重定基底一個類時,Bytebuddy保存基底類所有方法的實現。當Bytebuddy 如執行類型重定義時,它將所有這些方法實現複製到具有兼容簽名的重新命名的私有方法中,而不是拋棄重寫的方法。重定義的方法可以繼續通過它們重命名過的名稱調用原來的方法。
先上一個目標方法UserService,後面的demo都是以這個方法爲目標方法
------target method --------- @Slf4j public class UserService { public int getAge(String name) { log.info("--call getAge method--"); return 18; } public String getIdNumber(String name) { log.info("--call getIdNumber method--"); throw new RuntimeException(); } }
使用FixedValue.value()來返回一個固定值
/** * overwrite the method return fixed value * for com.kawa.mock.UserService#getAge(java.lang.String) */ @Test public void Overwrite_Method_Return_Value() { try { UserService bbUserService = new ByteBuddy() .subclass(UserService.class) .name("com.kawa.bb.BbUserService") .method(named("getAge").and(returns(int.class).and(takesArguments(1)))) .intercept(FixedValue.value(999)) .make() .load(this.getClass().getClassLoader()) .getLoaded() .getDeclaredConstructor().newInstance(); int age = bbUserService.getAge(""); log.info(">>>>age:{}", age); assertThat(age, equalTo(999)); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { e.printStackTrace(); } }
上面是最簡單的入門demo,通過ByteBuddy修改的getAge()的返回值,我們還可以通過託管到指定類實現方法來實現方法的overwrite,比如我們定義一個OvUserService類擁有和UserService一樣命名的方法如下
@Slf4j public class OvUserService { public int getAge(String name) { log.info("--call OvUserService getAge--"); return 99; } public static String getIdNumber(String name) { log.info("--call OvUserService getIdNumber--"); return String.format("%s - %s", UUID.randomUUID().toString().replace("-", ""), name); } }
ok,我們上測試代碼
/** * overwrite the method by subclass * if hit below error, need update delegate method to static method * None of [] allows for delegation from XXXXXXXX */ @Test public void Overwrite_Method() { try { UserService userService = new ByteBuddy() .subclass(UserService.class) .method(named("getIdNumber").and(returns(String.class))) .intercept(to(OvUserService.class)) .make().load(getClass().getClassLoader()) .getLoaded() .getDeclaredConstructor() .newInstance(); String idNum = userService.getIdNumber("sean"); log.info("idNum:{}", idNum); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { e.printStackTrace(); } }
查看測試結果,可以看到,實際上實行了OvUserService的getIdNumber()方法
然後我們還可以通過saveIn() 方法導出class文件,一般可以用來用於定義問題的一種方式,如下面的測試代碼就是,定義的UserService的子類並導出class類到target/classes下面
@Test public void Output_ByteCode() { try { // output the byte code new ByteBuddy() .subclass(UserService.class) .name("com.kawa.mock.BcUserService") .make() .saveIn(new File("/home/un/code/springBoot_2017/spb-demo/target/classes")); } catch (IOException e) { e.printStackTrace(); } }
這可以通過NamingStrategy可以自定一個新的類的命名(不過name()方法也可以實現相同的功能),比如下面的測試代碼實現一個同樣名稱UserService的子類,只是包名不一樣
/** * with(new NamingStrategy.AbstractBase()) and name(String) are the same function */ @Test public void Output_ByteCode_With_Name_Strategy() { try { // output the byte code new ByteBuddy() .with(new NamingStrategy.AbstractBase() { @Override protected String name(TypeDescription superClass) { return "com.kawa.name.stratgy." + superClass.getSimpleName(); } }) .subclass(UserService.class) .make() .saveIn(new File("/home/un/code/springBoot_2017/spb-demo/target/classes")); } catch (IOException e) { e.printStackTrace(); } }
對應class文件如下
如果想動態的添加字段可以使用方法defineFiled(), 如下
@Test public void Add_Field() { try { new ByteBuddy() .subclass(UserService.class) .name("com.kawa.mock.UserServiceMock") .defineField("address", String.class) .make() .saveIn(new File("/home/un/code/springBoot_2017/spb-demo/target/classes")); } catch (IOException e) { throw new RuntimeException(e); } }
編譯之後的可以看下如下
上面的demo代碼地址: https://github.com/showkawa/springBoot_2017/blob/master/spb-demo/src/test/java/com/kawa/bb/ByteBuddyTest.java
ByteBuddy還有一些常見的Annotation
比如@Advice.OnMethodEnter和@Advice.OnMethodExit, 和AOP裏面的before和after攔截方法差不多, demo代碼如下
@Before public void setup() { classLoader = new ByteArrayClassLoader .ChildFirst(getClass().getClassLoader(), ClassFileLocator.ForClassLoader.readToNames(OvUserService.class), ByteArrayClassLoader.PersistenceHandler.MANIFEST); ByteBuddyAgent.install(); } @Test public void Method_Enter_And_Exit_Advice() { new AgentBuilder.Default() .with(AgentBuilder.PoolStrategy.Default.EXTENDED) .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE) // setup match condition .type(ElementMatchers.is(OvUserService.class), ElementMatchers.is(classLoader)) .transform((builder, typeDescription, classLoader, module) -> builder.visit(Advice .to(OnMethodEnterExitAdvice.class) .on(ElementMatchers.not(ElementMatchers.isConstructor()) .and(ElementMatchers.any())))) .installOnByteBuddyAgent(); try { Class<?> classType = classLoader.loadClass(OvUserService.class.getName()); Object newInstance = classType.getDeclaredConstructor().newInstance(); // call method classType.getDeclaredMethod("getAge", String.class).invoke(newInstance, "sean"); classType.getMethod("getIdNumber", String.class).invoke(newInstance, "sean"); } catch (ClassNotFoundException | InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) { e.printStackTrace(); } }
攔截的class爲
public class OnMethodEnterExitAdvice { @Advice.OnMethodEnter public static void enter() { System.out.println("----- OnMethodEnterExitAdvice ----- enter"); } @Advice.OnMethodExit public static void exit() { System.out.println("----- OnMethodEnterExitAdvice ----- exit"); } }
測試的結果如下
除了上面的註解之外, 其他常用的方法註解還有如下
1.skipOn @OnMethodEnter 進入方法時 skipOn() 跳過某個方法。這個跳過方法的判定比較奇葩,他是根據advice中返回值,來確定是否跳過。注意不能跳過構造器方案。OnDefaultValue 當advice方法的返回值是{false for boolean , 0 for byte、short、char、int、long、float 、double ,
或者null for 引用類型 } 那麼就跳過目標方法。 2. @Advice.AllArguments 獲取方法的入參 3. @Advice.Return 獲取方法的返回值 4. @Advice.FieldValue 用來的獲取目標類中的成員變量 5[email protected] 用來獲取目標類和目標方法
上面的註解不展開講了,bytebuddy annotation demo地址: https://github.com/showkawa/springBoot_2017/blob/master/spb-demo/src/test/java/com/kawa/bb/ByteBuddyAnnotationTest.java
4. Bytebuddy對目標方法的overwrite
需要被overwrite的方法我這邊將他們分爲兩類
1. 入參和返回類型 都是基礎類型,如:int, boolean,String等等。針對的這類方法的overwrite比較簡單,在前面列舉的demo就有實現 2. 另外一直比較複雜,例如我們要overwirte gateway裏面的一個filter,target方法如下,入參爲ServerWebExchange和GatewayFilterChain,返回參數是Mono<Void>,都不是簡單的基礎類型 @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); URI requestUrl = exchange.getRequest().getURI(); ReactiveCircuitBreaker cb = reactiveCircuitBreakerFactory.create(requestUrl.getPath()); Set<HttpStatus> statuses = defaultStatusCodes.stream().map(HttpStatusHolder::parse) .filter(statusHolder -> statusHolder.getHttpStatus() != null).map(HttpStatusHolder::getHttpStatus) .collect(Collectors.toSet()); return cb.run(chain.filter(exchange).doOnSuccess(v -> { if (statuses.contains(exchange.getResponse().getStatusCode())) { HttpStatus status = exchange.getResponse().getStatusCode(); exchange.getResponse().setStatusCode(null); reset(exchange); throw new CircuitBreakerStatusCodeException(status); } }), t -> { if (defaultFallbackUri == null) { return Mono.error(t); } URI uri = exchange.getRequest().getURI(); boolean encoded = containsEncodedParts(uri); URI fallbackUrl = UriComponentsBuilder.fromUri(uri).host(null).port(null) .uri(defaultFallbackUri).scheme(null).build(encoded).toUri(); exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, fallbackUrl); addExceptionDetails(t, exchange); // Reset the exchange reset(exchange); ServerHttpRequest request = exchange.getRequest().mutate().uri(requestUrl).build(); return getDispatcherHandler().handle(exchange.mutate().request(request).build()); }).onErrorResume(t -> handleErrorWithoutFallback(t)); }
針對第二類情形,一般多是一個已經存在的項目項目需要做overwrite,所以這裏就需要Java Agent技術了,Agent 的premian方法可以在目標項目啓動前替換代碼。ByteBuddy如果要對對gateway的filter進行修改替換,需要引用相同版本的依賴,如我這邊就需要引用到下面的版本依賴:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> <version>3.0.3</version> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-core</artifactId> <version>3.4.13</version> </dependency>
ok,我們要開始寫Agent項目了,pom.xml如下
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.brian</groupId> <artifactId>brian-agent</artifactId> <version>0.0.1</version> <name>brian-agent</name> <description>Demo project for java agent</description> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.encoding>UTF-8</maven.compiler.encoding> <java.version>11</java.version> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>log4j-over-slf4j</artifactId> <version>1.7.30</version> </dependency> <dependency> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy</artifactId> <version>1.12.7</version> </dependency> <dependency> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy-agent</artifactId> <version>1.12.7</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> <version>3.0.3</version> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-core</artifactId> <version>3.4.13</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.2.4</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <manifestEntries> <Premain-Class>com.brian.brianagent.BrianAgentApplication</Premain-Class> </manifestEntries> </transformer> </transformers> <filters> <filter> <artifact>*:*</artifact> <excludes> <exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.RSA</exclude> </excludes> </filter> </filters> </configuration> </execution> </executions> </plugin> </plugins> </build> </project>
啓動類如下BrianAgentApplication, 這裏寫了Transformer和Listener這兩個內部類,其中Transformer類是做字節代碼替換的邏輯,Listener類主要是被掃描到的類的生命週期的監控開放擴展的接口的一些實現
package com.brian.brianagent; import com.brian.brianagent.filter.MaskHelperFilter; import net.bytebuddy.ByteBuddy; import net.bytebuddy.agent.builder.AgentBuilder; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.dynamic.scaffold.TypeValidation; import net.bytebuddy.utility.JavaModule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.instrument.Instrumentation; import static net.bytebuddy.implementation.MethodDelegation.to; import static net.bytebuddy.matcher.ElementMatchers.*; public class BrianAgentApplication { private static final Logger log = LoggerFactory.getLogger(BrianAgentApplication.class); private final static String scanPackage = "com.kawa.spbgateway.filter"; private final static String targetMethod = "filter"; private final static String implInterface = "org.springframework.cloud.gateway.filter.GlobalFilter"; public static void premain(String agentArgs, Instrumentation inst) { System.out.println(">>>>> BrianAgentApplication - premain()"); final ByteBuddy byteBuddy = new ByteBuddy().with(TypeValidation.of(false)); new AgentBuilder.Default(byteBuddy) .type(nameStartsWith(scanPackage)) .transform(new Transformer()) // update the byte code .with(new Listener()) .installOn(inst); } /** * */ private static class Transformer implements AgentBuilder.Transformer { @Override public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) { if (typeDescription.getPackage().getActualName().equals(scanPackage) && typeDescription.getInterfaces().size() > 0 && typeDescription.getInterfaces().get(0).getActualName().equals(implInterface)) { String targetClassName = typeDescription.getSimpleName(); System.out.println("----------------------- target class:" + targetClassName); return builder.method(named(targetMethod).and(isPublic())).intercept(to(MaskHelperFilter.class)); } return builder; } } /** * Listener */ private static class Listener implements AgentBuilder.Listener { private int count; @Override public void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) { if (typeName.startsWith(scanPackage)) { System.out.println("--- onDiscovery ---" + typeName); } } @Override public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded, DynamicType dynamicType) { if (typeDescription.getSimpleName().startsWith(scanPackage)) { System.out.println("--- onTransformation ---" + typeDescription.getSimpleName()); } } @Override public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded) { } @Override public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) { if (typeName.startsWith(scanPackage)) { System.out.println("--- onError ---" + throwable); } } @Override public void onComplete(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) { if (typeName.startsWith(scanPackage)) { System.out.println("--- onComplete ---" + typeName); } } } }
根據上面的代碼邏輯可以知道,這個Agent主要是掃描包“com.kawa.spbgateway.filter”下所有的有實現"org.springframework.cloud.gateway.filter.GlobalFilter"接口的類,並且把這個類的“filter”接口委託給Agent的MaskHelperFilter的filter方法來實現。
public class MaskHelperFilter { public static Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); System.out.println("=== <agent method>request ===:" + request.getURI()); return chain.filter(exchange.mutate().request(request).build()); } }
現在我們要啓動測試項目 spb-gateway (地址: https://github.com/showkawa/springBoot_2017/tree/master/spb-demo/spb-gateway), 啓動參數 -javaagent:/home/un/code/brian-agent/target/brian-agent-0.0.1.jar -Dspring.config.location=/home/un/code/springBoot_2017/spb-demo/spb-gateway/src/main/resources/
我這邊用spb-gateway的MaskFilter來測試下
通過啓動項目的log可以看到,Agent的項目的premain方法也執行了
>>>>> BrianAgentApplication - premain() . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.5.1) INFO [restartedMain] 2022-05-05 09:48:55.333 c.k.spbgateway.SpbGatewayApplication - Starting SpbGatewayApplication using Java 11 on brian with PID 42297 INFO [background-preinit] 2022-05-05 09:48:55.330 o.h.validator.internal.util.Version - HV000001: Hibernate Validator 6.2.0.Final INFO [restartedMain] 2022-05-05 09:48:55.336 c.k.spbgateway.SpbGatewayApplication - No active profile set, falling back to default profiles: default INFO [restartedMain] 2022-05-05 09:48:55.399 o.s.b.d.e.DevToolsPropertyDefaultsPostProcessor - Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable INFO [restartedMain] 2022-05-05 09:48:55.399 o.s.b.d.e.DevToolsPropertyDefaultsPostProcessor - For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG' INFO [restartedMain] 2022-05-05 09:48:56.866 o.s.d.r.c.RepositoryConfigurationDelegate - Multiple Spring Data modules found, entering strict repository configuration mode! INFO [restartedMain] 2022-05-05 09:48:56.868 o.s.d.r.c.RepositoryConfigurationDelegate - Bootstrapping Spring Data R2DBC repositories in DEFAULT mode. INFO [restartedMain] 2022-05-05 09:48:56.880 o.s.d.r.c.RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 8 ms. Found 0 R2DBC repository interfaces. --- onDiscovery ---com.kawa.spbgateway.filter.BrianConfigGatewayFilterFactory --- onComplete ---com.kawa.spbgateway.filter.BrianConfigGatewayFilterFactory --- onDiscovery ---com.kawa.spbgateway.filter.InjectProtectFilter ----------------------- target class:InjectProtectFilter --- onComplete ---com.kawa.spbgateway.filter.InjectProtectFilter --- onDiscovery ---com.kawa.spbgateway.filter.MaskFilter ----------------------- target class:MaskFilter --- onComplete ---com.kawa.spbgateway.filter.MaskFilter INFO [restartedMain] 2022-05-05 09:48:57.761 o.s.d.r.c.RepositoryConfigurationDelegate - Multiple Spring Data modules found, entering strict repository configuration mode! INFO [restartedMain] 2022-05-05 09:48:57.762 o.s.d.r.c.RepositoryConfigurationDelegate - Bootstrapping Spring Data R2DBC repositories in DEFAULT mode. INFO [restartedMain] 2022-05-05 09:48:58.020 o.s.d.r.c.RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 257 ms. Found 1 R2DBC repository interfaces. INFO [restartedMain] 2022-05-05 09:48:58.068 o.s.d.r.c.RepositoryConfigurationDelegate - Multiple Spring Data modules found, entering strict repository configuration mode! INFO [restartedMain] 2022-05-05 09:48:58.070 o.s.d.r.c.RepositoryConfigurationDelegate - Bootstrapping Spring Data Redis repositories in DEFAULT mode. INFO [restartedMain] 2022-05-05 09:48:58.097 o.s.d.r.c.RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 12 ms. Found 0 Redis repository interfaces. INFO [restartedMain] 2022-05-05 09:48:58.576 o.s.cloud.context.scope.GenericScope - BeanFactory id=303454f5-6add-3bfb-8879-5a507a52a856 INFO [restartedMain] 2022-05-05 09:48:59.117 o.s.b.w.e.tomcat.TomcatWebServer - Tomcat initialized with port(s): 8080 (http) INFO [restartedMain] 2022-05-05 09:48:59.130 o.a.coyote.http11.Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8080"] INFO [restartedMain] 2022-05-05 09:48:59.131 o.a.catalina.core.StandardService - Starting service [Tomcat] INFO [restartedMain] 2022-05-05 09:48:59.132 o.a.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.46] --- onDiscovery ---com.kawa.spbgateway.filter.BrianConfigGatewayFilterFactory$Config --- onComplete ---com.kawa.spbgateway.filter.BrianConfigGatewayFilterFactory$Config
ok,類來測試下接口 http://127.0.0.1:8080/api/hk/card/v1/card/query
通過log可以看到gateway的filter已經被代理,實際執行的是Agent裏面的filter代碼邏輯。當前上面的例子只是demo,實際項目也不會這麼玩。關於ByteBuddy的overwrite的目標方法就講到這,關於ByteBuddy的基礎知識點我沒講的太詳細,如果對ByteBuddy的不太瞭解了可以看下下面大佬的博客:
ByteBuddy入門
https://blog.csdn.net/wanxiaoderen/article/details/107369629
ByteBuddy源碼分析
https://blog.csdn.net/wanxiaoderen/article/details/107079741