bytebuddy overwrite 目标方法
最近项目因为一些需求需要网关平台做增强,需要通过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.@Advice.Origin 用来获取目标类和目标方法
上面的注解不展开讲了,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