问题
在前面的代码中,为了用最简单的代码理清逻辑以及实现功能,我们使用了很多的常量值,比如named("org.apache.catalina.core.StandardWrapperValve")
,named("invoke")
;而且现在只能拦截某些类的某些方法,那如果我要拦截A类的a方法,拦截B类的b方法,那要怎么办呢?
开始重构
- 转移类限定
public static ElementMatcher getMatcher(){
return named("org.apache.catalina.core.StandardWrapperValve");
}
转移获取哪些类需要拦截
new AgentBuilder.Default().type(InterceptClassLoader.getMatcher())....
public class InterceptClassLoader {
public static ElementMatcher getMatcher(){
return named("org.apache.catalina.core.StandardWrapperValve");
}
}
由于需要对于不同类拦截不同的方法,那么对于统一的transform
方法肯定就不再合适了
PluginEnhancer pluginEnhancer = new PluginEnhancer(typeDescription.getTypeName());
builder = pluginEnhancer.doIntercept(builder);
public class PluginEnhancer {
private String className;
public PluginEnhancer(String className){
this.className= className;
}
public DynamicType.Builder<?> doIntercept(DynamicType.Builder<?> builder){
ElementMatcher matcher = named("invoke");
builder = builder.method(not(isStatic()).and(matcher)).intercept(MethodDelegation.to(new ClassInstanceMethodInterceptor()));
return builder;
}
}
参看提交重构阶段1
,现在已经完成的情况是:
- 获取哪些类需要拦截已经独立出来了,这个获取到时候可以做到配置文件,就不会再出现常量
- 但是对于不同类拦截不同方法还是没有解决
继续:在transform函数所有类使用同一个pluginEnhancer不合理,肯定要通过不同类获取不同的pluginEnhancer,那么就提取出父类。
public interface PluginEnhancer {
public DynamicType.Builder<?> doIntercept(DynamicType.Builder<?> builder);
}
public class TomcatEnhancer implements PluginEnhancer {
public DynamicType.Builder<?> doIntercept(DynamicType.Builder<?> builder){
ElementMatcher matcher = named("invoke");
builder = builder.method(not(isStatic()).and(matcher)).intercept(MethodDelegation.to(new ClassInstanceMethodInterceptor()));
return builder;
}
}
public class TestEnhancer implements PluginEnhancer {
public DynamicType.Builder<?> doIntercept(DynamicType.Builder<?> builder){
ElementMatcher matcher = named("test1").or(named("test2")).or(named("testStatic"));
builder = builder.method(not(isStatic()).and(matcher)).intercept(MethodDelegation.to(new ClassInstanceMethodInterceptor()));
return builder;
}
}
然后再transform阶段通过不同类返回不同PluginEnhancer,通过一个Map就可以对应了。
public Builder<?> transform(Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader) {
System.out.println("transform...");
builder = PluginEnhancerFactory.getPluginEnhancerByClass(typeDescription.getTypeName())
.doIntercept(builder);
return builder;
}
InterceptClassLoader 加上以前的Test类
public static ElementMatcher getMatcher(){
return named("org.apache.catalina.core.StandardWrapperValve").or(named("younian.apmsample.agent.test.Test"));
}
好了,现在重新运行tomcat运行正常,参看提交重构阶段2
;
但是运行Test的main方法的时候报错了,什么情况呢?因为我们现在虽然能够返回不同的enhangcer,但是拦截器还是用的一样的,那就像解决enhangcer一样再来解决interceptor。
public interface ClassInstanceMethodInterceptor {
@RuntimeType
Object intercept(@This Object obj, @AllArguments Object[] allArguments, @Origin Method method, @SuperCall Callable<?> zuper) throws Throwable;
}
public class TomcatInterceptor implements ClassInstanceMethodInterceptor {
@Override
@RuntimeType
public Object intercept(@This Object obj, @AllArguments Object[] allArguments, @Origin Method method, @SuperCall Callable<?> zuper) throws Throwable {
long start = System.currentTimeMillis();
HttpServletRequest request = (HttpServletRequest) allArguments[0];
Object result = null;
try {
result = zuper.call();
} catch (Throwable t) {
t.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("intercept " + method.getName() + " ,user requested url:" + request.getRequestURI() + " ,costs:" + (end - start));
return result;
}
}
public class TomcatEnhancer implements PluginEnhancer {
public DynamicType.Builder<?> doIntercept(DynamicType.Builder<?> builder){
ElementMatcher matcher = named("invoke");
builder = builder.method(not(isStatic()).and(matcher)).intercept(MethodDelegation.to(new TomcatInterceptor()));
System.out.println("TomcatEnhancer doIntercept");
return builder;
}
}
到这里实现了对于不同类拦截不同方法并且有不同的行为了,参看提交重构阶段3
:
到这里完了吗?没有,我们查看ClassInstanceMethodInterceptor 的两个实现类发现有不少重复的地方,我们需要优化。
public abstract class ClassInstanceMethodInterceptor {
public abstract void before();
public abstract Object after(Object result);
@RuntimeType
public Object intercept(@This Object obj, @AllArguments Object[] allArguments, @Origin Method method, @SuperCall Callable<?> zuper) throws Throwable{
Object result = null;
try {
this.before();
result = zuper.call();
} catch (Throwable t) {
t.printStackTrace();
throw t;
} finally {
result = this.after(result);
}
return result;
}
}
public class TomcatInterceptor extends ClassInstanceMethodInterceptor {
@Override
public void before() {
System.out.println("TomcatInterceptor before...");
}
@Override
public Object after(Object result) {
System.out.println("TomcatInterceptor after...");
return result;
}
}
这是最容易想到的方法,多态嘛,但是执行为什么tomcat刷新页面什么都没有呢?
这里将before 和 after的类型改为 protected 即可,至于原因需要研究一下ByteBuddy的代码了。
参看提交重构阶段4
重构阶段2
在上面的阶段完成,其实已经基本实现了模块化—-如果需要拦截新的类,只需要添加一组新的TestEnhancer
和TestInterceptor
并且在InterceptClassLoader
中加入目标类并且还要在PluginEnhancerFactory
中加入被拦截类和Enhancer的对应。
但是看起来添加还是很麻烦InterceptClassLoader和PluginEnhancerFactory还是有优化的空间。
先改造PluginEnhancerFactory,这里手动添加对应关系是不优秀的,需要自动完成
- 自动对应关系
Enhangcer添加被拦截类属性;public class TomcatEnhancer implements PluginEnhancer {
public static String ENHANCE_CLASSNAME = "org.apache.catalina.core.StandardWrapperValve";
...
}
自动加载所有Enhangcer
在resources文件夹添加文件plugin.def
文件
younian.apmsample.agent.plugin.test.TestEnhancer
younian.apmsample.agent.plugin.tomcat.TomcatEnhancer
在PluginEnhancerFactory
读取文件加载即可
public class PluginEnhancerFactory {
private final static String PLUGIN_FILE_NAME = "plugin.def";
private final static Map<String, PluginEnhancer> pluginEnhancerHashMap = new HashMap<>();
static {
try {
//找出配置文件路径
Enumeration<URL> urls = PluginEnhancerFactory.class.getClassLoader().getResources(PLUGIN_FILE_NAME);
List<URL> cfgUrlPaths = new ArrayList<URL>();
while (urls.hasMoreElements()) {
cfgUrlPaths.add(urls.nextElement());
}
//读取所有配置文件内容
List<String> classList = new ArrayList<>();
for (URL url : cfgUrlPaths) {
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(url.openStream()));
String classname = null;
while ((classname = in.readLine()) != null) {
classList.add(classname);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
}
System.out.println("Enhancer List:" + classList);
//初始化pluginEnhancerHashMap
for (String classFullname : classList) {
try {
Class<?> clazz = Class.forName(classFullname);
PluginEnhancer instance = (PluginEnhancer) clazz.newInstance();
pluginEnhancerHashMap.put(instance.getEnhanceClassName(), instance);
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static PluginEnhancer getPluginEnhancerByClass(String className) {
System.out.println("getPluginEnhancerByClass:" + className);
return pluginEnhancerHashMap.get(className);
}
}
到这里PluginEnhancerFactory
完成,只有一个配置文件需要更改了。
改造InterceptClassLoader
,这里面的内容和Enhancer也是一一对应的
public class InterceptClassLoader {
public static ElementMatcher getMatcher(){
Iterator<PluginEnhancer> iter = PluginEnhancerFactory.pluginEnhancerHashMap.values().iterator();
ElementMatcher.Junction matcher = none();
while (iter.hasNext()) {
matcher = matcher.or(named( iter.next().getEnhanceClassName()));
}
return matcher;
}
}
甚至直接将函数挪到PluginEnhancerFactory中,删除此类。
结语
目前为止,如果需要拦截新的类,那么只需要添加一组新的Enhancer和Interceptor然后将enhancer类名加进plugin.def即可。模块化实现了。阶段Tag:重构