# 字节码增强
在每个Sermant插件的插件主模块(plugin)
中,都可以声明一些增强逻辑,针对宿主应用的某些特定方法进行字节码增强,从而实现某种功能,因此描述好该增强什么类及类的方法是一个重要的课题。
# 增强声明
插件开发中,需要在插件主模块中定义字节码增强的声明,声明插件字节码增强逻辑需要实现PluginDeclarer (opens new window)接口,其中包含三个接口方法:
getClassMatcher
方法用于获取被增强类的匹配器ClassMatcher。getInterceptDeclarers
方法用于获取被增强类的拦截方法的匹配器MethodMatcher,以及为拦截点声明的拦截器Interceptor,他们封装于拦截声明InterceptDeclarer (opens new window)中。getSuperTpeDecarers
方法用于获取插件的超类声明SuperTypeDeclarer (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
,处理异常的方法,当被拦截方法执行异常时触发。这里处理异常并不会影响异常的正常抛出。