# 字节码增强

在每个Sermant插件的插件主模块(plugin)中,都可以声明一些增强逻辑,针对宿主应用的某些特定方法进行字节码增强,从而实现某种功能,因此描述好该增强什么类及类的方法是一个重要的课题。

# 增强声明

插件开发中,需要在插件主模块中定义字节码增强的声明,声明插件字节码增强逻辑需要实现PluginDeclarer (opens new window)接口,其中包含三个接口方法:

实现完成后,需要添加PluginDeclarer接口的SPI配置文件:

  • 在资源目录resources下添加META-INF/services文件夹。
  • META-INF/services中添加com.huaweicloud.sermant.core.plugin.agent.declarer.PluginDeclarer配置文件。
  • 在上述文件中,以换行为分隔,键入插件包中所有的增强定义PluginDeclarer实现。

# 类匹配器

对匹配器 ClassMatcher (opens new window),在核心模块中提供了两种类型的匹配器:

ClassTypeMatcher (opens new window)(类的类型匹配器)

  • 完全通过名称匹配,也是最常见的定位方式,通过以下方法获取:

    ClassMatcher.nameEquals("${class reference}");
    

    其中${class reference}为被增强类的全限定名。

  • 通过名称匹配多个类,属于nameEquals的复数版,可通过以下方法获取:

    ClassMatcher.nameContains("${class reference array}");
    

    其中${class reference array}为被增强类的全限定名可变数组。

ClassFuzzyMatcher (opens new window)(类的模糊匹配器)

  • 通过全限定名前缀定位到被增强类,可通过以下方法获取:

    ClassMatcher.namePrefixedWith("${prefix}");
    

    其中${prefix}为全限定名前缀。

  • 通过全限定名后缀定位到被增强类,可以通过一下方法获取:

    ClassMatcher.nameSuffixedWith("${suffix}")
    

    其中${suffix}为全限定名后缀。

  • 通过全限定名内缀定位到被增强类,可以通过以下方法获取:

    ClassMatcher.nameinfixedWith("${infix}")
    

    其中${infix}为全限定名内缀。

  • 通过正则表达式匹配全限定名定位到被增强类,可以通过以下方法获取:

    ClassMatcher.nameMatches("${pattern}")
    

    其中${pattern}为正则表达式。

  • 通过注解定位到被该注解修饰的类,可通过以下方法获取:

    ClassMatcher.isAnnotationWith("${annotation reference array}");
    

    其中${annotation reference array}为注解的全限定名可变数组。

  • 通过超类定位到该类的子类,可通过以下方法获取:

    ClassMatcher.isExtendedFrom("${super class array}");
    

    其中${super class array}为超类可变数组。考虑到Java的继承规则,该数组只能有一个Class,其余必须全为Interface

  • 匹配的逻辑操作,匹配器全部不匹配时为真:

    ClassMatcher.not("${class matcher array}")
    

    其中${class matcher array}为匹配器可变长数组

  • 匹配的逻辑操作,匹配器全部都匹配时为真:

    ClassMatcher.and("${class matcher array}")
    

    其中${class matcher array}为匹配器可变长数组

  • 匹配的逻辑操作,匹配器其中一个匹配时为真:

    ClassMatcher.or("${class matcher array}")
    

    其中${class matcher array}为匹配器可变长数组

# 方法匹配器

对于方法匹配器MethodMatcher (opens new window),提供了多种匹配方法:

  • 全数匹配:

    MethodMatcher.any();
    
  • 名称匹配:

    MethodMatcher.nameEquals("${method name}");
    

    其中${method name}为方法名称。

  • 匹配静态方法:

    MethodMatcher.isStaticMethod();
    
  • 匹配构造函数:

    MethodMatcher.isConstructor();
    
  • 匹配多个方法:

    MethodMatcher.nameContains("${method name array}");
    

    其中${method name array}为方法名称数组。

  • 根据方法名称前缀匹配:

    MethodMatcher.namePrefixedWith("${method name prefix}");
    

    其中${method name prefix}为方法名称前缀。

  • 根据方法名称后缀匹配:

    MethodMatcher.nameSuffixedWith("${method name suffix}");
    

    其中${method name suffix}为方法名称后缀。

  • 根据方法名称内缀匹配:

    MethodMatcher.nameinfixedWith("${method name infix}");
    

    其中${method name infix}为方法名称内缀。

  • 根据正则表达式匹配:

    MethodMatcher.nameMatches("${pattern}");
    

    其中${pattern}为正则表达式。

  • 匹配被传入注解修饰的方法:

    MethodMatcher.isAnnotatedWith("${annotations array}");
    

    其中${annotations array}为注解集。

  • 匹配指定入参数量的方法:

    MethodMatcher.paramCountEquals("${param count}");
    

    其中${param count}为入参数量。

  • 匹配指定入参类型的方法:

    MethodMatcher.paramTypeEquals("${param type array}");
    

    其中${param type array}为入参类型集。

  • 匹配指定返回值类型的方法:

    MethodMatcher.resultTypeEquals("${result type}");
    

    其中${result type}返回值类型。

  • 逻辑操作,方法匹配器集全不匹配时则结果为真

    MethodMatcher.not("${element matcher array}");
    

    其中${element matcher array}为方法匹配器集。

  • 逻辑操作,方法匹配器集全匹配时则结果为真

    MethodMatcher.and("${element matcher array}");
    

    其中${element matcher array}为方法匹配器集。

  • 逻辑操作,方法匹配器集其中一个匹配时则结果为真

    MethodMatcher.or("${element matcher array}");
    

    其中${element matcher array}为方法匹配器集。

更多方法匹配方式可以参考byte-buddy (opens new window)中含MethodDescription泛型的方法。

# 原生类增强

增强原生类和增强普通类在增强定义和拦截器编写上没有什么区别,但是还是希望插件开发者尽量少地对原生类进行增强,原因有三:

  • 对原生类的增强往往是发散的,对他们增强很可能会对其他插件或宿主功能造成影响。
  • 对原生类的增强逻辑,将使用反射的方式调用系统类加载器中的拦截器方法。由于Java重定义Class的限制,每次调用被增强方法的时候,都会进行反射处理的逻辑,这将极大影响被增强方法的性能。
  • 对原生类的增强过程中,涉及到使用Advice模板类生成动态拦截类。对于每个被增强的原生类方法,都会动态生成一个,他们将被系统类加载器加载。如果不加限制的增强原生类,加载动态类也会成为启动过程中不小的负担。

综上,Sermant核心功能模块 (opens new window)中提供对Java原生类增强的能力,但不建议不加限制地对他们进行增强,如果有多个增强点可选,优先考虑增强普通类。

# 拦截器

拦截器用于定义在对被增强类的方法进行字节码增强时的前置、后置及处理异常时的增强逻辑:

  • Interceptor (opens new window): 拦截器接口,其中包含三个方法:
    • before,前置方法,该方法在拦截点之前执行。ExecuteContext参数为插件执行的上下文,里面封装拦截器运作所需的所有参数,通过skip方法可跳过主要流程,并设置最终方法结果,注意,增强构造函数时,不能跳过主要流程。
    • after,后置方法,无论被拦截方法是否正常执行,最后都会进入后置方法中。后置方法可以通过返回值覆盖被拦截方法的返回值,因此这里开发者需要注意不要轻易返回null。
    • onThrow,处理异常的方法,当被拦截方法执行异常时触发。这里处理异常并不会影响异常的正常抛出。
上次更新: 2024/4/1 03:31:01