Java类加载器( 死磕8)
【正文】Java类加载器( CLassLoader ) 死磕 8:
使用ASM,和类加载器实现AOP
本小节目录
8.1. ASM字节码操作框架简介
8.2. ASM和访问者模式
8.3. 用于增强字节码的事务类
8.4 通过ASM访问注解
8.5. 通过ASM注入AOP事务代码
8.6. 实现AOP的类加载器
1.1. 使用类加载器实现AOP
前面讲到,编程过程中,出现了很多需要动态加强字节码的场景:为了性能、统计、安全等等可能的加强,根据实际情况动态创建加强代码并执行。
这次使用asm来动态实现事务AOP功能。
更详细的说,适用ASM技术,对原始类动态生成子类,调用子类的方法覆盖父类,来实现AOP的功能。著名的 Hibernate 和 Spring 框架,就是使用这种技术实现了 AOP 的“无损注入”的。
1.1.1. ASM字节码操作框架简介
ASM是一个JAVA字节码分析、创建和修改的开源应用框架。在ASM中提供了诸多的API用于对类的内容进行字节码操作的方法。与传统的BCEL和SERL不同,在ASM中提供了更为优雅和灵活的操作字节码的方式。目前ASM已被广泛的开源应用架构所使用,例如:Spring、Hibernate等。
Asm是很好的ByteCode generator 和 ByteCode reader。Asm提供了ClassVisitor来访问Class中的每个元素。当用ClassReader来读取Class的字节码时,每read一个元素,ASM会调用指定的ClassVisitor来访问这个元素。这就是访问者模式。利用这个特点,当ClassVisitor访问Class的Annotation元素时,我们会把annotation的信息记录下来。这样就可以在将来使用这个Annotation。
简单的说,ASM能干什么呢?
分析一个类、从字节码角度创建一个类、修改一个已经被编译过的类文件
1.1.2. ASM和访问者模式
关于访问者模式,后面会详细为大家介绍。
使用ASM框架,需要理解访问者模式。大家可以自行百度,理解访问者模式有助于我们理解ASM的CoreAPI;
如果仅仅简单的使用ASM框架,只需要掌握框架中的基本的ClassVisitor、ClassAdapter、MethodVisitor、FieldVisitor、ClassReader和ClassWriter这几个类即可。
1.1.3. 用于增强字节码的事务类
本案例,模拟Spring的配置事务功能。如果在方法前面加上@Transaction注解,则使用ASM进行方法的代码增强。在方法的前面加上开始事务的字节码,在方法的后面加上结束事务的字节码。
作为示意,Transaction 的代码很简单,具体如下:
public class Transaction
{
public static void beginTransaction()
{
Logger.info("开始事务:");
}
public static void commit()
{
Logger.info("提交事务 ");
}
}
现在的场景是:
修改目标字节码,现在需要对加上@Transaction注解方法做AOP增强,在方法执行之前执行如下类的beginTransaction()方法,方法执行结束后,执行commit()方法。
1.1.4. 通过ASM访问注解
第一步,需要通过ASM,提取字节码中带有@Transaction注解的方法名称。
简单粗暴,直接上代码:
public class FilterClassVisitor extends ClassVisitor
{
private Set<String> tranMehodSet;
public FilterClassVisitor()
{
super(Opcodes.ASM5);
tranMehodSet=new HashSet<>();
}
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions)
{
MethodVisitor methodVisitor=
super.visitMethod(access, name, desc, signature, exceptions);
return new FilterMethodVisitor( name, methodVisitor,this);
}
public void addTranMehod(String methName)
{
tranMehodSet.add(methName);
}
public Set<String> getTranMehods()
{
return tranMehodSet;
}
}
案例路径:com.crazymakercircle.classLoaderDemo.AOP.ClassVisitor
这个类,调用了 FilterMethodVisitor ,来逐个访问类的方法。其代码如下:
public class FilterMethodVisitor extends MethodVisitor
{
private final FilterClassVisitor classVisitor;
String methName;
public FilterMethodVisitor(String name, MethodVisitor methodVisitor, FilterClassVisitor transactionClassVisitor)
{
super(Opcodes.ASM5, methodVisitor);
this.methName = name;
this.classVisitor = transactionClassVisitor;
}
@Override
public AnnotationVisitor visitAnnotation(String s, boolean b)
{
if (s.contains("Tanscation"))
{
this.classVisitor.addTranMehod(this.methName);
}
return super.visitAnnotation(s, b);
}
}
案例路径:com.crazymakercircle.classLoaderDemo.AOP.FilterMethodVisitor
在这个类的 visitAnnotation,对每个访问到的方法注解,进行判断。如果一个方法的某个注解的名称包含Tanscation,说明这个方法需要进行AOP的事务增强,将这个方法的名称,加到classVisitor的AOP 事务方法Set集合中,等待后面的进一步处理。
1.1.5. 通过ASM注入AOP事务代码
通过ASM,实现在进入方法和退出方法时注入代码实现aop代码增强。涉及到MethodVisitor的两个方法:
(1)visitCode方法。将会在ASM开始访问某一个方法时调用,因此这个方法一般可以用来在进入分析JVM字节码之前来新增一些字节码。
(2)visitInsn方法。它是ASM访问到无参数指令时调用的,这里我们判断了当前指令是否为无参数的return,来在方法结束前添加一些指令。
简单粗暴,上代码。
package com.crazymakercircle.classLoaderDemo.AOP;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class ModifyMethodVisitor extends MethodVisitor
{
public ModifyMethodVisitor(MethodVisitor methodVisitor)
{
super(Opcodes.ASM5, methodVisitor);
}
public void visitCode()
{
super.visitMethodInsn(
Opcodes.INVOKESTATIC,
"com/crazymakercircle/classLoaderDemo/AOP/Transaction",
"beginTransaction",
"()V",
false);
super.visitCode();
}
public void visitInsn(int opcode)
{
/**
* 方法return之前,植入代码
*/
if(opcode == Opcodes.RETURN
|| opcode == Opcodes.ARETURN )
{
super.visitMethodInsn(
Opcodes.INVOKESTATIC,
"com/crazymakercircle/classLoaderDemo/AOP/Transaction",
"commit",
"()V",
false);
}
super.visitInsn(opcode);
}
}
这个是方法级别的Visitor,需要类级别的访问者来调用。
类级别的ClassVisitor,代码如下:
public class ModifyClassVisitor extends ClassVisitor
{
private Set<String> tranMehodSet;
public ModifyClassVisitor(ClassVisitor classVisitor,Set<String> tranMehodSet)
{
super(Opcodes.ASM5, classVisitor);
this.tranMehodSet=tranMehodSet;
}
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions)
{
if(tranMehodSet.contains(name))
{
MethodVisitor methodVisitor= super.visitMethod(access, name, desc, signature, exceptions);
return new ModifyMethodVisitor( methodVisitor);
}
return super.visitMethod(access, name, desc, signature, exceptions);
}
}
在上面的visitMethod方法中,判断方法名称,是否在需要进行事务增强的方法集合中。
如果是,则使用前面定义ModifyMethodVisitor,进行事务的增强。
1.1.6. 实现AOP的类加载器
简单粗暴,上代码
public class TransactionClassLoader extends FileClassLoader
{
public TransactionClassLoader(String rootDir)
{
super(rootDir);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException
{
byte[] classData = super.getClassData(name);
Class<?> target = null;
if (classData == null)
{
throw new ClassNotFoundException();
}
Set<String> tranMehodSet = null;
InputStream inputStream = null;
/**
* 读取之前的class 字节码
*/
ClassReader classReader = null;
try
{
inputStream = new ByteArrayInputStream(classData);
tranMehodSet = getTransSet(inputStream);
} catch (IOException e)
{
e.printStackTrace();
}
if (null == tranMehodSet)
{
target = super.defineClass(name, classData, 0, classData.length);
return target;
}
/**
* 进行字节码的解析
*/
try
{
inputStream = new ByteArrayInputStream(classData);
classReader = new ClassReader(inputStream);
} catch (IOException e)
{
e.printStackTrace();
}
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
classReader.accept(
new ModifyClassVisitor(classWriter, tranMehodSet),
ClassReader.SKIP_DEBUG);
/**
* 取得解析后的字节码
*/
byte[] transactionClassBits = classWriter.toByteArray();
target = defineClass(
name,
transactionClassBits,
0,
transactionClassBits.length);
Logger.info("src class=" + target.getName());
return target;
}
private static Set<String> getTransSet(InputStream inputStream) throws IOException
{
ClassReader classReader = new ClassReader(inputStream);
FilterClassVisitor filterClassVisitor = new FilterClassVisitor();
classReader.accept(filterClassVisitor, ClassReader.SKIP_DEBUG);
Set<String> tranMehodSet = filterClassVisitor.getTranMehods();
return tranMehodSet;
}
}
源码:
代码工程: classLoaderDemo.zip
下载地址:在疯狂创客圈QQ群文件共享。
疯狂创客圈:如果说Java是一个武林,这里的聚集一群武痴, 交流编程体验心得
QQ群链接:疯狂创客圈QQ群
无编程不创客,无案例不学习。 一定记得去跑一跑案例哦
类加载器系列全目录
5. 入门案例:自定义一个文件系统的自定义classLoader
8. 高级案例1:使用ASM技术,结合类加载器,解密AOP原理
Java类加载器( 死磕8)的更多相关文章
- Java类加载器(死磕 1-2)
Java类加载器( CLassLoader ) 死磕 1.2: 导入 & 类加载器分类 本小节目录 1.导入 1.1. 从class文件的载入开始 1.2. 什么是类加载器 2. JA ...
- Java类加载器(死磕5)
Java类加载器( CLassLoader ) 死磕5: 自定义一个文件系统classLoader 本小节目录 5.1. 自定义类加载器的基本流程 5.2. 入门案例:自定义文件系统类加载器 5 ...
- Java类加载器( 死磕9)
[正文]Java类加载器( CLassLoader ) 死磕9: 上下文加载器原理和案例 本小节目录 9.1. 父加载器不能访问子加载器的类 9.2. 一个宠物工厂接口 9.3. 一个宠物工厂管理 ...
- Java类加载器( 死磕7)
[正文]Java类加载器( CLassLoader )死磕7: 基于加密的自定义网络加载器 本小节目录 7.1. 加密传输Server端的源码 7.2. 加密传输Client端的源码 7.3. 使 ...
- Java类加载器( 死磕 6)
[正文]Java类加载器( CLassLoader )死磕 6: 自定义网络类加载器 本小节目录 6.1. 自定义网络类加载器的类设计 6.2. 文件传输Server端的源码 6.3. 文件传输C ...
- Java类加载器( 死磕 4)
[正文]Java类加载器( CLassLoader ) 死磕 之4: 神秘的双亲委托机制 本小节目录 4.1. 每个类加载器都有一个parent父加载器 4.2. 类加载器之间的层次关系 4.3. ...
- Java类加载器(死磕3)
[正文]Java类加载器( CLassLoader ) 死磕3: 揭秘 ClassLoader抽象基类 本小节目录 3.1. 类的加载分类:隐式加载和显示加载 3.2. 加载一个类的五步工作 3. ...
- java笔记--理解java类加载器以及ClassLoader类
类加载器概述: java类的加载是由虚拟机来完成的,虚拟机把描述类的Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成能被java虚拟机直接使用的java类型,这就是虚拟机的类加载机制 ...
- java类加载器深入研究
看了下面几篇关于类的加载器的文章,豁然开朗.猛击下面的地址开始看吧. Java类加载原理解析 深入探讨 Java 类加载器 分析BootstrapClassLoader/ExtClassLo ...
随机推荐
- Unsafe in Java
http://www.cnblogs.com/xrq730/p/4976007.html http://www.importnew.com/14511.html http://blog.csdn.ne ...
- Kafka应用实践与生态集成
1.前言 Apache Kafka发展至今,已经是一个很成熟的消息队列组件了,也是大数据生态圈中不可或缺的一员.Apache Kafka社区非常的活跃,通过社区成员不断的贡献代码和迭代项目,使得Apa ...
- CentOS6.7源码安装MySQL5.6
1.源码安装MySQL5.6 # CentOS6操作系统安装完成后,默认会在/etc目录下存在一个my.cnf, # 强制卸载了mysql-libs之后,my.cnf就会消失 rpm -qa | gr ...
- 第4章 CentOS软件安装
一.安装JDK 1.1 卸载旧版JDK 首先,在你的服务器上运行一下更新. yum update 然后,在您的系统上搜索,任何版本的已安装的JDK组件. rpm -qa | grep -E '^ope ...
- jvm 问题分析
jmap dump:file=[文件名].dump [进程号] 生成dump root@VM-185-251-ubuntu:/opt/scripts# jmap -dump:file=three.d ...
- hadoop之hdfs及其工作原理
hadoop之hdfs及其工作原理 (一)hdfs产生的背景 随着数据量的不断增大和增长速度的不断加快,一台机器上已经容纳不下,因此就需要放到更多的机器中,但这样做不方便维护和管理,因此需要一种文件系 ...
- angular http ajax header
myAppModule.config(['$httpProvider', function($httpProvider) { $httpProvider.defaults.headers.common ...
- Android View源码解读:浅谈DecorView与ViewRootImpl
前言 对于Android开发者来说,View无疑是开发中经常接触的,包括它的事件分发机制.测量.布局.绘制流程等,如果要自定义一个View,那么应该对以上流程有所了解.研究.本系列文章将会为大家带来V ...
- SilverLight:基础控件使用(1)
ylbtech-SilverLight-Basic-Control:基础控件使用(1) 本文详解控件有: Label, TextBox, PasswordBox, Image, Button , Ra ...
- PS 如何用制作键盘图标
1 键盘可以大致分为笔记本键盘和台式机键盘,颜色一般是黑色或白色.不同的键盘,拍摄角度不同(俯视或者平视)得到的效果也不一样.一般我们根据自己需要得到需要的键盘形式.比如下面别人制作的一套立体键盘,立 ...