一步一步写APM(三)--拦截以及Interceptor重构

问题

在前面的代码中,为了用最简单的代码理清逻辑以及实现功能,我们使用了很多的常量值,比如named("org.apache.catalina.core.StandardWrapperValve")named("invoke");而且现在只能拦截某些类的某些方法,那如果我要拦截A类的a方法,拦截B类的b方法,那要怎么办呢?

开始重构

  1. 转移类限定
    public static ElementMatcher getMatcher(){
         return named("org.apache.catalina.core.StandardWrapperValve");
    }
    
  2. 转移获取哪些类需要拦截

    new AgentBuilder.Default().type(InterceptClassLoader.getMatcher())....
    
    public class InterceptClassLoader {
    
     public static ElementMatcher getMatcher(){
         return named("org.apache.catalina.core.StandardWrapperValve");
     }
    }
    
  3. 由于需要对于不同类拦截不同的方法,那么对于统一的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

在上面的阶段完成,其实已经基本实现了模块化—-如果需要拦截新的类,只需要添加一组新的TestEnhancerTestInterceptor并且在InterceptClassLoader中加入目标类并且还要在PluginEnhancerFactory中加入被拦截类和Enhancer的对应。
但是看起来添加还是很麻烦InterceptClassLoader和PluginEnhancerFactory还是有优化的空间。

  1. 先改造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完成,只有一个配置文件需要更改了。

  1. 改造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:重构

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×