ByteBuddy工具的使用场景有哪些?-创新互联

ByteBuddy工具的使用场景有哪些?针对这个问题,今天小编总结这篇有关ByteBuddy的文章,可供感兴趣的小伙伴们参考借鉴,希望对大家有所帮助。

十年的昆明网站建设经验,针对设计、前端、开发、售后、文案、推广等六对一服务,响应快,48小时及时工作处理。成都全网营销推广的优势是能够根据用户设备显示端的尺寸不同,自动调整昆明建站的显示方式,使网站能够适用不同显示终端,在浏览器中调整网站的宽度,无论在任何一种浏览器上浏览网站,都能展现优雅布局与设计,从而大程度地提升浏览体验。创新互联从事“昆明网站设计”,“昆明网站推广”以来,每个客户项目都认真落实执行。

使用场景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重启后类定义会自动同步更新. 当然一个限制是你只能用反射的方式访问这个新定义的类. 代码样例如下 (Kotlin语言):

private fun buildSchemaClass(schema: Schema, classLoader: ClassLoader): Class<*> {
     logger.info("Start building schema class for schema={}, version={}", schema.name, schema.version)
     val typeBuilder = ByteBuddy().subclass(Any::class.java).name(className(schema))
     var tempBuilder = typeBuilder
     schema.columns.forEach { column ->
       tempBuilder = tempBuilder
         .defineField(column.name, columnTypeToJavaClassMapping[column.type], Visibility.PRIVATE)
     }
     schema.columns.forEach { column ->
       tempBuilder = tempBuilder.defineMethod(toGet(column.name), columnTypeToJavaClassMapping[column.type], Modifier.PUBLIC)
         .intercept(FieldAccessor.ofBeanProperty())
         .defineMethod(toSet(column.name), Void.TYPE, Modifier.PUBLIC).withParameters(columnTypeToJavaClassMapping[column.type])
         .intercept(FieldAccessor.ofBeanProperty())
     }
     return tempBuilder.make().load(classLoader, ClassLoadingStrategy.Default.WRAPPER).loaded.also {
       logger.info("Success building schema class: {}", it.name)
     }
   }

大家可以看到,定义GET/SET方法完全不需要实现方法体,通过intercept(FieldAccessor.ofBeanProperty) 可以自动将GET/SET方法和对应的属性绑定自动生成方法体.

使用场景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.  代码示例如下:

object SqlMigrationResolverEnhancer {
  fun enhance(inst: Instrumentation) {
     if (!SpannerConfigUtils.enableSpanner) {
       logger.info("Spanner is not enabled!!!!!!!!!!!!!!!!, no intercept will occure")
       return
     }
     val temp = Files.createTempDirectory("tmp").toFile()
     ClassInjector.UsingInstrumentation.of(temp, ClassInjector.UsingInstrumentation.Target.BOOTSTRAP, inst).inject(
         Collections.singletonMap(
             TypeDescription.ForLoadedType(MigrationResolverInterceptor::class.java),
             ClassFileLocator.ForClassLoader.read(MigrationResolverInterceptor::class.java!!)
         )
     )
     AgentBuilder.Default()
         .ignore(ElementMatchers.nameStartsWith("net."))
         .ignore(ElementMatchers.nameStartsWith("com."))
         .enableBootstrapInjection(inst, temp)
         .type(ElementMatchers.nameEndsWith("SqlMigrationResolver"))
         .transform { builder, _, _, _ ->
           builder.method(ElementMatchers.hasMethodName("resolveMigrations"))
               .intercept(MethodDelegation.to(MigrationResolverInterceptor::class.java))
         }.with(object : AgentBuilder.Listener {
           override fun onComplete(
             typeName: String?,
             classLoader: ClassLoader?,
             module: JavaModule?,
             loaded: Boolean
           ) {
             // just ignore onComplete
           }

           override fun onDiscovery(
             typeName: String?,
             classLoader: ClassLoader?,
             module: JavaModule?,
             loaded: Boolean
           ) {
             // just ignore onDiscovery
           }

           override fun onIgnored(
             typeDescription: TypeDescription?,
             classLoader: ClassLoader?,
             module: JavaModule?,
             loaded: Boolean
           ) {
             // just ignore onIgnored
           }

           override fun onTransformation(
             typeDescription: TypeDescription?,
             classLoader: ClassLoader?,
             module: JavaModule?,
             loaded: Boolean,
             dynamicType: DynamicType?
           ) {
             logger.debug("Tranforming class: $typeDescription")
           }

           override fun onError(
             typeName: String?,
             classLoader: ClassLoader?,
             module: JavaModule?,
             loaded: Boolean,
             throwable: Throwable?
           ) {
             logger.error("Error intercepting type: $typeName", throwable)
           }
         })
         .installOn(inst)
   }
}

class MigrationResolverInterceptor {
   companion object {
     private val logger: Logger = LoggerFactory.getLogger(MigrationResolverInterceptor::class.java)

     @JvmStatic
     @RuntimeType
     fun intercept(@SuperCall delegate: Callable<Collection<ResolvedMigration>>): Collection<ResolvedMigration> {
       val spannerProperties = SpannerProperties(SpannerConfigUtils.projectId, SpannerConfigUtils.instanceId, SpannerConfigUtils.databaseName)
       val originalCol = delegate.call() as MutableList<ResolvedMigration>
       logger.info("Intercepting migration resolver method ---------------------------------------- $originalCol")
       return ResolvedMigrationExecutorReplacer.replaceSqlMigrationExecutor(originalCol, spannerProperties)
     }
   }
}

代码并不复杂,通过ElementMatchers先缩小CLASSPATH中扫描包的范围,在通过ElementMatcher类名和方法名指定需要拦截的方法,再指定拦截器的类名. 这里注意的是Agent的enhance方法必须在被拦截的类被JVM加载之前执行,因为一个类在一个CLASSLOADER中只能被加载一次,加载完无法修改了. 注册AgentBuilder.Listener并非必须,但是对排查期望的类方法没有被正确拦截的问题非常有用. 另外注意我们只指定了Interceptor的类名而没有指定方法, 而且Interceptor类中的方法必须是一个Static方法,通过@RuntimeType指定是拦截器需要执行的方法. @SuperCall用于注入原始方法调用的代理. 可以在SpringBoot 主方法的开始调用

SqlMigrationResolverEnhancer.enhance(ByteBuddyAgent.install())

至于ResolveMigrationExecutorReplacer的实现和ByteBuddy无关, 代码仅供参考不再赘述.

object ResolvedMigrationExecutorReplacer {
   private val databaseField = SqlMigrationExecutor::class.java.getDeclaredField("database")
   private val sqlScriptField = SqlMigrationExecutor::class.java.getDeclaredField("sqlScript")

   init {
     databaseField.isAccessible = true
     sqlScriptField.isAccessible = true
   }

   fun replaceSqlMigrationExecutor(migrationList: MutableList<ResolvedMigration>, spannerProperties: SpannerProperties): List<ResolvedMigration> {
     migrationList.forEach { resolvedMigration ->
       val migrationExecutor = resolvedMigration.executor
       if (migrationExecutor is SqlMigrationExecutor &&
           resolvedMigration is ResolvedMigrationImpl) {
         val database: Database<*> = databaseField.get(migrationExecutor) as Database<*>
         val sqlScript: SqlScript = sqlScriptField.get(migrationExecutor) as SqlScript
         resolvedMigration.executor = SpannerSqlMigrationExecutor(database, sqlScript, spannerProperties)
       }
     }
     return migrationList
   }
}

使用场景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拦截器,返回一个被增强的原始类的子类.

object DelegateAgent {
   fun <T> buildDelegateClass(sourceClass: Class<T>, methodNames: List<String>,
              advisorClass: Class<*>): Class<out T> {
     val builder = ByteBuddy().subclass(sourceClass, ConstructorStrategy.Default.IMITATE_SUPER_CLASS)
     val methodMatchers = getMethodMachers(methodNames)
     return builder.method(methodMatchers)
       .intercept(MethodDelegation.to(advisorClass))
       .make().load(
         DelegateAgent::class.java.classLoader
       ).loaded
   }

   private fun getMethodMachers(methodNames: List<String>): ElementMatcher<MethodDescription> {
     var methodMatcher =
       ElementMatchers.none<MethodDescription>()
     if (methodNames.isEmpty()) {
       return ElementMatchers.any()
     }
     methodNames.forEach {methodName ->
      methodMatcher = methodMatcher.or(ElementMatchers.named<MethodDescription>(methodName))
     }
     return methodMatcher
   }
}

注意ByteBuddy().subclass(sourceClass, ConstructorStrategy.Default.IMITATE_SUPER_CLASS), 这样生成的子类自动拥有父类所有的Constructor. 无需重新定义. 使用的例子如下:

object DataSourceAdvisor {
   @JvmStatic
   @RuntimeType
   fun onMethodExecution(
     @This sourceObj: Any,
     @Origin method: Method
     @AllArguments arguments: Array<Any?>): Any {
     //just for demo purpose
         println("Current method is: " + method.name)
     return method.invoke(sourceObj, * arguments)
   }
}

fun testAgent() {
     val config = HikariConfig().apply {
       this.jdbcUrl = "jdbc:mysql://xxxx"
       this.driverClassName = "org.postgresql.Driver"
       this.username = "postgres"
       this.password = "postgres"
       this.maximumPoolSize = 1
     }
     val resultDsClass = DelegateAgent.buildDelegateClass(HikariDataSource::class.java, listOf("getConnection"),
       DataSourceAdvisor::class.java)
     val newDs = resultDsClass.getConstructor(HikariConfig::class.java).newInstance(config)
     println(newDs.connection)

   }

这里的拦截器仅仅打印了方法名. 和场景二的非常相似,拦截器的实现也是一个类的静态方法,唯一的区别是原始对象,参数列表等使用的Annotation不同,场景2中应该使用net.bytebuddy.asm.Advice 中的annotation. 场景3应该使用的是net.bytebuddy.implementation.bind.annotation包中的annotation

使用ByteBuddy中的碰到一些问题:

  • 场景2 Agent实现中如果方法的参数签名和拦截器的参数不完全匹配,则需要使用@RuntimeType annotation. 否则可能遇到以下错误:
    java.lang.IllegalArgumentException: None of [interceptor methods]  allows for delegation from [target method]
    at net.bytebuddy.implementation.bind.MethodDelegationBinder$Processor.bind(MethodDelegationBinder.java:1096)

    关于ByteBuddy工具的使用场景就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

另外有需要云服务器可以了解下创新互联scvps.cn,海内外云服务器15元起步,三天无理由+7*72小时售后在线,公司持有idc许可证,提供“云服务器、裸金属服务器、高防服务器、香港服务器、美国服务器、虚拟主机、免备案服务器”等云主机租用服务以及企业上云的综合解决方案,具有“安全稳定、简单易用、服务可用性高、性价比高”等特点与优势,专为企业上云打造定制,能够满足用户丰富、多元化的应用场景需求。

当前文章:ByteBuddy工具的使用场景有哪些?-创新互联
分享链接:https://www.cdcxhl.com/article38/dspssp.html

成都网站建设公司_创新互联,为您提供响应式网站定制开发网站导航网站设计公司移动网站建设商城网站

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联

小程序开发