想写一个数据库连接工具[Kettle]连接Redis的功能,所以要知道它调用了Driver的哪些类。但是苦于很少有代码侵入性低但是能监控所有执行方法返回值的。干脆自己撸一个代码比较少,但是解决了几个比较头疼的问题终于算是实现了。 只要给定一个入口类,几乎把该类下面的所有方法及其方法的返回的类都能监控到, 其中操作比较有意思。
主要涉及的技术点:
- 动态字节码生成无参构造函数的类,骗过CGLIB检测
- 自写类加载器加载类
- 重写CGLIB代理类,不用通过构造函数,通过实体直接生成代理类
先放整合过的代码Git地址 method-tracer
防止有人看不到最后。
但是花10分钟了解以下推导地过程和思想其实也挺好地。
效果图,只对Driver加了代理,Driver下所有Connect PrepareStatement ResultSet 的方法都打印出来了,这样代码侵入性少,我们只要改入口就能分析Kettle的执行逻辑。
2020-12-24 15:48:53.644 DriverX - toString [[]] [wang.hex.DriverX@4bbfb90a] [0 ms] [e: ]
2020-12-24 15:48:53.708 DriverX - connect [[jdbc:hx2:tcp://localhost:8977/./test, {user=sa, password=sa}]] [conn1: url=jdbc:h2:tcp://localhost:8977/./test user=SA] [34 ms] [e: ]
2020-12-24 15:48:53.723 JdbcConnection - prepareStatement [[CREATE TABLE IF NOT EXISTS X1(ID INT)]] [prep2: CREATE TABLE IF NOT EXISTS X1(ID INT)] [1 ms] [e: ]
2020-12-24 15:48:53.741 JdbcPreparedStatement - execute [[]] [false] [0 ms] [e: ]
2020-12-24 15:48:53.741 JdbcPreparedStatement - close [[]] [] [0 ms] [e: ]
2020-12-24 15:48:53.742 JdbcConnection - prepareStatement [[INSERT INTO X1 VALUES (?)]] [prep3: INSERT INTO X1 VALUES (?)] [0 ms] [e: ]
2020-12-24 15:48:53.742 JdbcPreparedStatement - setInt [[1, 0]] [] [0 ms] [e: ]
2020-12-24 15:48:53.743 JdbcPreparedStatement - execute [[]] [false] [0 ms] [e: ]
2020-12-24 15:48:53.743 JdbcPreparedStatement - setInt [[1, 1]] [] [0 ms] [e: ]
2020-12-24 15:48:53.743 JdbcPreparedStatement - execute [[]] [false] [0 ms] [e: ]
2020-12-24 15:48:53.744 JdbcPreparedStatement - setInt [[1, 2]] [] [0 ms] [e: ]
2020-12-24 15:48:53.744 JdbcPreparedStatement - execute [[]] [false] [0 ms] [e: ]
2020-12-24 15:48:53.744 JdbcPreparedStatement - close [[]] [] [0 ms] [e: ]
2020-12-24 15:48:53.748 JdbcConnection - prepareStatement [[SELECT * FROM X1]] [prep4: SELECT * FROM X1] [4 ms] [e: ]
2020-12-24 15:48:53.752 JdbcPreparedStatement - executeQuery [[]] [rs7: columns: 1 rows: 6 pos: -1] [4 ms] [e: ]
2020-12-24 15:48:53.773 JdbcResultSet - next [[]] [true] [1 ms] [e: ]
2020-12-24 15:48:53.774 JdbcResultSet - getInt [[1]] [0] [0 ms] [e: ]
0
2020-12-24 15:48:53.774 JdbcResultSet - next [[]] [true] [0 ms] [e: ]
2020-12-24 15:48:53.774 JdbcResultSet - getInt [[1]] [1] [0 ms] [e: ]
1
2020-12-24 15:48:53.774 JdbcResultSet - next [[]] [true] [0 ms] [e: ]
2020-12-24 15:48:53.774 JdbcResultSet - getInt [[1]] [2] [0 ms] [e: ]
2
2020-12-24 15:48:53.774 JdbcResultSet - next [[]] [true] [0 ms] [e: ]
2020-12-24 15:48:53.774 JdbcResultSet - getInt [[1]] [0] [0 ms] [e: ]
0
2020-12-24 15:48:53.775 JdbcResultSet - next [[]] [true] [0 ms] [e: ]
2020-12-24 15:48:53.775 JdbcResultSet - getInt [[1]] [1] [0 ms] [e: ]
1
2020-12-24 15:48:53.775 JdbcResultSet - next [[]] [true] [0 ms] [e: ]
2020-12-24 15:48:53.775 JdbcResultSet - getInt [[1]] [2] [0 ms] [e: ]
2
2020-12-24 15:48:53.775 JdbcResultSet - next [[]] [false] [0 ms] [e: ]
2020-12-24 15:48:53.775 JdbcPreparedStatement - close [[]] [] [0 ms] [e: ]
以下是推导过程:
技术都是一步步验证可行性的,要解放思想,落地代码!
方案1[失败]:
通过CGLIB来代理这个类,并且在这个类方法返回的时候,把返回值也变成代理类!
网上也告诉你,CGLIB可以通过对象生成代理,代码如下:
package cglib;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class CglibTest {
public static class Proxy implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("我被代理了");
return methodProxy.invokeSuper(o, objects);
}
public static <T> T of(Object o) {
if (o == null) {
return null;
}
Class aClass = o.getClass();
if (aClass.getSimpleName().contains("$$EnhancerByCGLIB$$")) {
return (T) o;
}
if (Modifier.isFinal(o.getClass().getModifiers())) {
return (T) o;
}
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(o.getClass());
enhancer.setCallback(new Proxy());
return (T) enhancer.create();
}
}
public static class X {
public void s() {
System.out.println("x");
}
}
public static class X1 {
public X1(String x) {
}
public void s1() {
System.out.println("x1");
}
}
public static void main(String[] args) {
X x = Proxy.of(new X());
x.s();
X x1 = Proxy.of(new X1(""));
x.s();
}
}
可以看到其实CGLIG它其实Create方法是调用的构造函数,而我们有的只有一个object哪里知道构造函数是什么。
很明显代理失败了,因为我们对方法的返回值我们只能拿到的是对象,无法判断它当时的构造方法传入了什么值做了什么事情。
cglib会检测到该方法没有无参的构造函数然后抛出错误。
方案2[失败]:
在方案1的基础上我们通过动态字节码框架生成一个代理类的子类并且实现一个无参构造方法呢?
失败:cglib用反射创建的时候,java去看该类的父类有没有无参构造方法,结果发现没有抛出了错误。
Exception in thread "main" net.sf.cglib.core.CodeGenerationException: java.lang.NoSuchMethodError-->cglib.CglibTest$X1: method <init>()V not found
at net.sf.cglib.core.ReflectUtils.newInstance(ReflectUtils.java:235)
at net.sf.cglib.core.ReflectUtils.newInstance(ReflectUtils.java:220)
at net.sf.cglib.core.ReflectUtils.newInstance(ReflectUtils.java:216)
at net.sf.cglib.proxy.Enhancer.createUsingReflection(Enhancer.java:643)
at net.sf.cglib.proxy.Enhancer.firstInstance(Enhancer.java:538)
at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:225)
at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)
at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:285)
at cglib.CglibTest$Proxy.of(CglibTest.java:44)
at cglib.CglibTest.main(CglibTest.java:68)
Caused by: java.lang.NoSuchMethodError: cglib.CglibTest$X1: method <init>()V not found
at cglib.CglibTest$X1XCreate.<init>(Unknown Source)
at cglib.CglibTest$X1XCreate$$EnhancerByCGLIB$$30a91407.<init>(<generated>)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at net.sf.cglib.core.ReflectUtils.newInstance(ReflectUtils.java:228)
... 9 more
这个是动态创建子类的方法,包括类加载器。
package cglib;
import jdk.internal.org.objectweb.asm.Opcodes;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import java.io.FileOutputStream;
import java.util.HashMap;
import java.util.Map;
import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static jdk.internal.org.objectweb.asm.Opcodes.V1_1;
public class CreateClassLoader extends ClassLoader {
private static Map<String, byte[]> classBytesMap = new HashMap<String, byte[]>();
private static Map<Class, Class> noConstructClass = new HashMap<Class, Class>();
public CreateClassLoader() {
}
public CreateClassLoader(ClassLoader parent) {
super(parent);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] bytes = classBytesMap.get(name);
return this.defineClass(name, bytes, 0, bytes.length);
}
public void putClass(String name, byte[] bytes) {
classBytesMap.put(name, bytes);
}
public static Class getClassNoConstruct(Class clazz) {
Class aClass1 = noConstructClass.get(clazz);
if (aClass1 != null) {
return aClass1;
}
try {
String extend = clazz.getName().replace(".", "/");
String create = extend + "XCreate";
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cw.visit(V1_1, ACC_PUBLIC, create, null, extend, null);
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, extend, "<init>", "()V");
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
cw.visitEnd();
CreateClassLoader createClassLoader = new CreateClassLoader();
String replace = create.replace("/", ".");
FileOutputStream fileOutputStream = new FileOutputStream("d:\\x.java");
fileOutputStream.write(cw.toByteArray());
createClassLoader.putClass(replace, cw.toByteArray());
Class<?> aClass = createClassLoader.loadClass(replace);
noConstructClass.put(clazz, aClass);
return aClass;
} catch (Exception e) {
}
return null;
}
}
测试类
package cglib;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class CglibTest {
public static class Proxy implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("我被代理了");
return methodProxy.invokeSuper(o, objects);
}
public static <T> T of(Object o) {
if (o == null) {
return null;
}
Class aClass = o.getClass();
if (aClass.getSimpleName().contains("$$EnhancerByCGLIB$$")) {
return (T) o;
}
if (Modifier.isFinal(o.getClass().getModifiers())) {
return (T) o;
}
Enhancer enhancer = new Enhancer();
//修改了一行代码
enhancer.setSuperclass(CreateClassLoader.getClassNoConstruct(aClass));
enhancer.setCallback(new Proxy());
return (T) enhancer.create();
}
}
public static class X {
public void s() {
System.out.println("x");
}
}
public static class X1 {
public X1(String x) {
}
public void s1() {
System.out.println("x1");
}
}
public static void main(String[] args) {
X x1 = Proxy.of(new X1(""));
x1.s();
}
}
方案3[成功]
那如果我重写Cglib的Create方法呢,不从反射来构建类转而用 unsafe.allocateInstance(type)
这个方法能跳过构造函数直接生成对象。
重写的Enhancer 如下
package org.h2.proxy;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
/**
* @Author fox
* @Class MyEnhancer
* @Description
* @Date 2020/12/23 22:01
*/
public class MyEnhancer extends Enhancer {
private static Unsafe unsafe;
private Callback callback;
private static Field callbacksFiled;
static {
try {
Class superclass = Enhancer.class;
Field[] fields = superclass.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
if (field.getName().equals("setCallbacks")) {
callbacksFiled = field;
}
}
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
unsafe = (Unsafe) theUnsafe.get(null);
} catch (Exception e) {
}
}
public void setCallback(Callback callback) {
this.callback = callback;
super.setCallback(callback);
}
protected Object firstInstance(Class type) throws Exception {
registerCallbacks(type, new Callback[]{callback});
return unsafe.allocateInstance(type);
}
}
方案4[成功]
不用动态字节码生成类,调试 Enhancer可知抛错的条件有3个
if (!this.classOnly && !seenNull && this.arguments == null) {
throw new IllegalArgumentException("Superclass has no null constructors but no arguments were given");
}
create的时候设置一个虚假的参数就能跳过检测。
package wang.hex;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
/**
* @Author fox
* @Class TracerEnhancer
* @Description
* @Date 2020/12/24 11:40
*/
public class TracerEnhancer extends Enhancer {
private Callback callback;
/**
* 重写callback方法 省去反射去获取父类的逻辑
*
* @param callback
*/
public void setCallback(Callback callback) {
this.callback = callback;
super.setCallback(callback);
}
/**
* 骗过构造方法检测
* @return
*/
public Object create() {
return super.create(new Class[]{int.class}, new Object[]{0});
}
/**
* 重写建立动态代理实体的方法
*
* @param type
* @return
* @throws Exception
*/
protected Object firstInstance(Class type) throws Exception {
//注册拦截信息
registerCallbacks(type, new Callback[]{callback});
return CreateObjectSkipConstruction.getObject(type);
}
}
最后执行成功了。
总共花费6小时时间。Debug CGLIB查看创建代理类的流程10分钟,想新方案3小时。摸索实现代码1小时50分钟。重构1小时。
你也可以脱离Spring对所有类执行Aop了呢!