本文实现java aop,用到了asm4.0,agentmain,eclipse bycode插件
之前自己写了一个orm框架,但是无奈事务只能靠手动代码控制,曾经想过集成到spring mvc的事务注解里面去,无奈技术太浅。如果使用需要配置拦截器,各种包名或者方法名的方式来做到的话,感觉很麻烦,所以打算自己来实现aop,实现注解控制事务。。
初步的想法是,扫描配置包下面所有的class文件,并读取带有事务注解的方法。然后使用asm4.0修改方法执行之前和返回之前插入一段事务控制的代码,并重新生成byte [] 加载到虚拟机里面去。
先完成第一步,扫描出带事务注解的方法和类
<span style="white-space:pre"> </span>Set<Class<?>> classes = ScanUtil.getClasses("com.wddiaosi.orm.main.db.impl");
String [] scanPackages=scanPackage.split(",");
for(int i=0;i<scanPackages.length;i++){
classes.addAll(ScanUtil.getClasses(scanPackages[i]));
}
com.wddiaosi.orm.main.db.impl 是我orm框架的默认主要事务注解的类
把扫描出来的class放在set中
ScanUtil.getClasses(scanPackages[i])
是在网上找到别人的代码
读取刚才包含有事务注解的类,并保持事务注解参数
for (Class<?> clazz : classes) {
ReadClassTransaction.readClass(clazz);
}
asm修改类方法,具体使用自行搜索,需要注意的是,在解析事务注解的参数时,需要生成方法的签名,以确定当前方法的唯一性
用TransactionVisitor来实现,在方法运行前,返回前插入自己的事务控制代码
asm 尽量使用调用方法的方式来做,不适合用asm生成大量的逻辑代码
AopManager。这个类是在agen项目里面的
for (Entry<Class<?>,Set<VisitorAction>> entry:ReadClassTransaction.map.entrySet()) {
try {
ClassReader cr = new ClassReader(entry.getKey().getName());
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
ClassVisitor cv = new TransactionVisitor(cw,entry.getValue());
cr.accept(cv, Opcodes.ASM4);
byte[] code = cw.toByteArray();
AopManager.aopMap.put(entry.getKey(), code);
} catch (IOException e) {
e.printStackTrace();
}
}
自己写了一个方法签名生成的方法,也可以使用javap -s xxx.class
private static final Map<String,String> typeMap=new HashMap<String,String>();
static{
typeMap.put("void", "V");
typeMap.put("boolean", "Z");
typeMap.put("byte", "B");
typeMap.put("char", "C");
typeMap.put("short", "S");
typeMap.put("int", "I");
typeMap.put("long", "J");
typeMap.put("float", "F");
typeMap.put("double", "D");
typeMap.put("[]", "[");
}
方法类型对应,如果不是基本类型返回值或者参数前面加大写 L代表引用类型
(参数)返回值
比如 public int test (int a,String b);
方法前面为 (ILjava.lang.String)I
使用agent需要新建一个项目写一个类,类似于main方法,这个类会在jvm加载类进去之后执行
public static void agentmain(String agentArgs, Instrumentation inst) throws IOException, ClassNotFoundException, UnmodifiableClassException {
Class<?>[] clazzs = inst.getAllLoadedClasses();
for (int i = 0; i < clazzs.length; i++) {
byte[] bytes = AopManager.aopMap.get(clazzs[i]);
if (bytes != null) {
ClassDefinition cd= new ClassDefinition(clazzs[i], bytes);
try {
inst.redefineClasses(cd);
} catch (ClassNotFoundException | UnmodifiableClassException e) {
e.printStackTrace();
}
}
}
AopManager.aopMap=null;
}
在其他项目中添加需要修改的类,和新的byte[] 到aopManager.aopMap中,在jvm加载之前替换
把agen打包成jar
被其他项目引用
注意,在打包jar这个过程中。
遇到了很多坑。。
打包后文件目录
代码包
META-INF\MANIFEST.MF
MANIFEST.MF文件内
Agent-Class: Agent
Can-Retransform-Classes: true
Can-Redefine-Classes: true
Can-Set-Native-Method-Prefix: true
Agent-Class:( 必须存在空格)Agent
在引用agen.jar的项目中。初始化的时候写入下面这段代码,动态添加代理
String name = ManagementFactory.getRuntimeMXBean().getName();
String pid = name.split("@")[0];
try {
VirtualMachine vm = VirtualMachine.attach(pid);
String agentJarPath=AopManager.class.getProtectionDomain().getCodeSource().getLocation().getPath();
vm.loadAgent(agentJarPath.substring(1, agentJarPath.length()));
} catch (Exception e) {
e.printStackTrace();
}
可以测试一下自己写的注解事务的效果了
这样已经实现了事务的传播
ps:我只记录下了,我遇到的几个坑点的问题,并没有详细去记录整个项目实现。