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文件,不会传递给父类加载器