0
点赞
收藏
分享

微信扫一扫

Agent和ClassLoader

拾杨梅记 2022-02-18 阅读 93
javajarmaven

JavaAgent是什么时候引入的

JavaAgent是JDK1.5之后引入,也可以叫做Java代理

JavaAgent和我们的业务代码执行的顺序是什么?

JavaAgent是运行在main方法之前的拦截器,它内定的方法叫premain,也就是说先执行premain方法,然后再执行main方法。

怎么运行JavaAgent呢?

在我们的启动参数中增加 -javaagent:agent-2.0.jar 就可以了。
agent通常应用在哪里呢,看下图:
在这里插入图片描述

agent开发用到的jar包有哪些

<dependency>    
  <groupId>org.javassist</groupId>    
  <artifactId>javassist</artifactId>    
  <version>3.18.2-GA</version>
</dependency>
<dependency>    
  <groupId>net.bytebuddy</groupId>    
  <artifactId>byte-buddy</artifactId>    
  <version>1.10.19</version>
</dependency>
<dependency>    
  <groupId>net.bytebuddy</groupId>    
  <artifactId>byte-buddy-agent</artifactId>    
  <version>1.10.19</version>
</dependency>

Agent能做什么?

1、可以在加载java文件之前做拦截把字节码做修改
2、可以在运行期将已经加载的类的字节码做变更
3、获取所有已经被加载过的类
4、获取所有已经被初始化过了的类
5、获取某个对象的大小
6、将某个jar加入到bootstrapclasspath里作为高优先级被bootstrapClassloader加载
7、将某个jar加入到classpath里供AppClassloard去加载

Agent打包

方式一、MANIFEST.MF
1、在resources目录下创建META-INF/MANIFEST.MF
2、MANIFEST.MF文件内增加文件内容如下
3、使用maven进行打成jar包
在这里插入图片描述方式二、Pom.xml
1、pom.xml中增加plugin,内容如下截图
2、使用maven进行打成jar包
在这里插入图片描述

简单agent的demo

1、拦截类名为" com.jd.jsf.gd.server.ProviderProxyInvoker”结尾的类。
2、在代码开始之前或者之后插入一些代码
insertBefore 在代码执行前插入
insertAfter 在代码执行后插入
setBody 替换方法体
在这里插入图片描述

agent增强代码的方式

方式一、利用className.endsWith定向拦截类

在这里插入图片描述

方式二、利用ElementMatchers进行代码拦截,使用intercept拦截
在这里插入图片描述
方式三、利用ElementMatchers进行代码拦截,使用advice增强
在这里插入图片描述

代码增强示例

方式一、Advice代码示例
在这里插入图片描述
方式二、Intercept代码示例
在这里插入图片描述

如果我们在agent的advice或者intercept调用业务系统的代码会怎么样?

执行下面代码,我们会收获什么样的结果呢
在这里插入图片描述
我们将收获一个
Caused by: java.lang.ClassNotFoundException: org.apache.dubbo.rpc.RpcContext

为什么呢?

Agent和类加载器

先看一下我们熟悉的双亲委派模型
在这里插入图片描述

我们再看一下tomcat的类加载器
在这里插入图片描述
总结一下出现ClassNotFoundException异常的原因:
Agent的类加载器默认是AppClassLoader,
我们的代码一般都是部署在tomcat下,所以
我们的类加载器一般都是WebAppClassLoader;

所以找个就解释了为什么上一页代码出现
ClassNotFoundException的原因。

原因:
AppClassLoader去加载我们的业务代码肯定加载不到

Agent类和业务类加载器解决方案

1、自定义类加载器,继承URLClassLoader
2、重写loadClass,先尝试自己加载,如果自己加载不到则交给父类加载器
3、指定自定义类加载的父类加载器(业务代码的类加载器,advice或者intercept能拿到业务类加载)
4、指定我们agent代码的插件地址(即需要增强的代码位置),通过URL获取
5、初始化自定义类加载的时候将第四步的URL传进去
5、使用自定义类加载器loadClass方法加载我们的插件代码

类加载器代码实现如下图:

public class AgentClassLoader extends URLClassLoader {

    private final ClassLoader upperClassLoader;

    public AgentClassLoader(URL[] urls, ClassLoader parent) {
        super(urls, parent);
        this.upperClassLoader = parent;
    }

    @Override
    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        System.out.println("name:"+name+";开始尝试加载;");
        final Class<?> loadedClass = findLoadedClass(name);
        if (loadedClass != null) {
            if (resolve) {
                resolveClass(loadedClass);
            }
            return loadedClass;
        }

        // 优先从parent(SystemClassLoader)里加载系统类,避免抛出ClassNotFoundException
        //if (name != null && (name.startsWith("sun.") || name.startsWith("java.") || name.contains("com.jd.car.log.plugins"))) {
//        if (name != null && (name.startsWith("sun.") || name.startsWith("java.") || name.contains("com.jd.car.log.plugins"))) {
//            System.out.println("super开始尝试加载:"+ super.toString() +" ;name:"+name +";super的父类加载器:"+super.getParent());
//            return super.loadClass(name, resolve);
//        }
//        System.out.println("开始尝试父类加载器加载;upperClassLoader的父加载器:"+upperClassLoader.getParent());
//        return Class.forName(name, true, upperClassLoader);
        System.out.println("super开始尝试加载:"+ super.toString() +" ;name:"+name +";super的父类加载器:"+super.getParent());
        return super.loadClass(name, resolve);
    }
}

Intercept代码实现如下图:

public class MvcInterceptor {
    AgentClassLoader agentClassLoader;

    public MvcInterceptor(ClassLoader classLoader) {

        try {
            URL agentJarUrl = new File("/export/carAgent/carLogPlugin-1.0.jar").toURI().toURL();
            System.out.println("Linux下地址:"+agentJarUrl);
            agentClassLoader = new AgentClassLoader(new URL[]{
                    agentJarUrl
            }, classLoader);
            System.out.println("创建类加载器成功;agentClassLoader的类加载器:" + agentClassLoader
                    + ";agentClassLoader的父加载器:" + agentClassLoader.getParent());
            Class<?> clazz = agentClassLoader.loadClass("com.jd.car.log.plugins.spring.MvcMethodInterceptor", true);
            System.out.println("Class<?> clazz的类加载器" + clazz.getClassLoader());
            Object plugin = clazz.newInstance();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    @RuntimeType
    public Object intercept(@This Object obj, @Origin Method method, @AllArguments Object[] allArguments, @SuperCall Callable<?> callable) throws Exception {
        Class<?> clazz = agentClassLoader.loadClass("com.jd.car.log.plugins.spring.MvcMethodInterceptor", true);
        Object plugin = clazz.newInstance();

        Object before = plugin.getClass().getMethod("before", Object.class).invoke(plugin, obj);
        try {
            return callable.call();
        } finally {
            plugin.getClass().getMethod("after", Object.class).invoke(plugin, before);
        }
    }

思考和反思

问题一、我们刚刚自定义的类加载器是否符合双亲委派模型?
不符合,因为我们首先是先尝试自己加载,然后再交给父类加载器加载

问题二、Tomcat类加载器是否符合双亲委派模型呢?
不符合,每个webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器

举报

相关推荐

0 条评论