Bytebuddy如何高效包装POJO?
How to efficiently wrap POJO with Bytebuddy?
我想包装简单的 POJO class。问题是我事先对 class 一无所知,只知道它是带有 setters 和 getters 的 POJO。我想用我的 Proxyclass 替换此 class,以便每次客户端调用 getter 或 setter 时我都能够拦截该调用。所以当调用被拦截时,我想做一些预获取(或设置)操作,然后调用getter(或setter),然后做一些post-get (或设置)操作。
我正在这样创建我的代理
private Pojo generatePojoProxy(Class<? extends PojoInterface> myPojo) {
Class<?> PojoProxyClass;
PojoProxyClass = new ByteBuddy()
.subclass(myPojo)
.method(ElementMatchers.nameStartsWith("get"))
.intercept(MethodDelegation.to(GetterInterceptor.class))
.method(ElementMatchers.nameStartsWith("set"))
.intercept(MethodDelegation.to(SetterInterceptor.class))
.name("Proxy" + myPojo.getName())
.make()
.load(myPojo.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();
Object pojoProxyInstance = null;
try {
pojoProxyInstance = PojoProxyClass.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
return (Pojo) pojoProxyInstance;
}
我的 GetterInterceptor 看起来像那样
public class GetterInterceptor {
@RuntimeType
public static Object intercept(@AllArguments Object[] allArguments, @Origin Method method, @Super(proxyType = TargetType.class) Object delegate) {
preGetHandle();
Object result = null;
try {
result = method.invoke(delegate, allArguments);
} catch (InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
postGetHandle();
return result;
}
private static void preGetHandle() {}
private static void postGetHandle() {}
和setter看起来一样。
但是当我设置并从我的 Proxyclass 实例中获取某些内容时,它比初始 Pojo class 实例慢得多(慢 1.5-2 倍)。
难道我做错了什么?我相信,一定有办法让它更快。
感谢任何帮助!
我用以下方式衡量性能
public class Main {
private static final int LOOP_COUNT = 10_000_000;
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
Pojo pojo = new Pojo();
Pojo myProxy = (Pojo) ProxyFactory.getInstance().getProxy(Pojo.class);
testTime(pojo);
testTime(myProxy);
}
private static void testTime(Pojo pojo) {
long startTime = System.currentTimeMillis();
Random random = new Random();
long totalSum = 0;
for (int i = 0; i<LOOP_COUNT; i++){
pojo.setId(random.nextLong());
totalSum += pojo.getId();
}
long endTime = System.currentTimeMillis();
System.out.println(pojo.getClass() + " time = " + (endTime-startTime) + " total= " + totalSum);
}
我的结果是
class Pojo time = 288 total= 1060564671495946244
class ProxyPojo time = 738 total= 5879857558672375335
这种放缓的主要原因是通过反思开展业务的成本。
考虑这个测试:
import java.lang.reflect.Method;
import java.util.Random;
public class Main {
public interface Pojo {
public long getId();
public void setId(long id);
}
public static class SimplePojo implements Pojo {
private long id;
@Override public long getId() { return id; }
@Override public void setId(long id) {this.id = id;}
}
public static class DelegatingPojo implements Pojo {
private final Pojo pojo;
public DelegatingPojo(Pojo p) {this.pojo = p;}
@Override public long getId() { return pojo.getId(); }
@Override public void setId(long id) { pojo.setId(id); }
}
public static class ReflectingPojo implements Pojo {
private final Object delegate;
private final Method getmethod;
private final Method setmethod;
public ReflectingPojo(Pojo p) throws NoSuchMethodException {
this.delegate = p;
this.getmethod = p.getClass().getMethod("getId");
this.setmethod = p.getClass().getMethod("setId", Long.TYPE);
}
@Override public long getId() {
Object result = null;
Object[] allarguments = new Object[0];
try {
result = getmethod.invoke(delegate, allarguments);
} catch (InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
return (Long)result;
}
@Override public void setId(long id) {
Object[] allarguments = new Object[]{id};
try {
setmethod.invoke(delegate, allarguments);
} catch (InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
return;
}
}
public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException {
Pojo pojo = new SimplePojo();
Pojo proxy = new DelegatingPojo(pojo);
Pojo refl = new ReflectingPojo(pojo);
testTime(pojo);
testTime(proxy);
testTime(refl);
}
private static final int LOOP_COUNT = 10_000_000;
private static void testTime(Pojo pojo) {
long startTime = System.currentTimeMillis();
Random random = new Random();
for (int i = 0; i < LOOP_COUNT; i++) {
pojo.setId(random.nextLong());
pojo.getId();
}
long endTime = System.currentTimeMillis();
System.out.println(pojo.getClass() + " time = " + (endTime - startTime));
}
}
我得到以下结果:
class Main$SimplePojo time = 295
class Main$DelegatingPojo time = 328
class Main$ReflectingPojo time = 544
如您所见,使用反射的速度大约是原来的两倍。这不应该是一个巨大的惊喜,那些 try 块和 method.invoke 调用意味着你正在做一大堆基本 pojo 不需要做的额外工作。
恐怕这只是部分答案...反思会减慢您的速度,因此要跳过反思,您的解决方案需要采用以下形式:
PojoProxyClass = new ByteBuddy()
.subclass(myPojo)
.method(ElementMatchers.nameStartsWith("get"))
.intercept(
MethodDelegation.to(<pre method>)
.andThen(MethodDelegation.to(<getter on the proxy>)
.andThen(MethodDelegation.to(<post method>)))
)
...
唉,我不知道bytebuddy巫术到底是什么。都在某处。
如前所述,您应该避免反射调用。在 Byte Buddy 中,使用 @SuperCall
注入:
public class GetterInterceptor {
@RuntimeType
public static Object intercept(@SuperCall Callable<?> zuper) throws Exception {
preGetHandle();
try {
return zuper.call();
} finally {
postGetHandle();
}
}
private static void preGetHandle() {}
private static void postGetHandle() {}
}
对于 setter,您不需要 return 一个值,因此您可以使用可运行的。
我想包装简单的 POJO class。问题是我事先对 class 一无所知,只知道它是带有 setters 和 getters 的 POJO。我想用我的 Proxyclass 替换此 class,以便每次客户端调用 getter 或 setter 时我都能够拦截该调用。所以当调用被拦截时,我想做一些预获取(或设置)操作,然后调用getter(或setter),然后做一些post-get (或设置)操作。 我正在这样创建我的代理
private Pojo generatePojoProxy(Class<? extends PojoInterface> myPojo) {
Class<?> PojoProxyClass;
PojoProxyClass = new ByteBuddy()
.subclass(myPojo)
.method(ElementMatchers.nameStartsWith("get"))
.intercept(MethodDelegation.to(GetterInterceptor.class))
.method(ElementMatchers.nameStartsWith("set"))
.intercept(MethodDelegation.to(SetterInterceptor.class))
.name("Proxy" + myPojo.getName())
.make()
.load(myPojo.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();
Object pojoProxyInstance = null;
try {
pojoProxyInstance = PojoProxyClass.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
return (Pojo) pojoProxyInstance;
}
我的 GetterInterceptor 看起来像那样
public class GetterInterceptor {
@RuntimeType
public static Object intercept(@AllArguments Object[] allArguments, @Origin Method method, @Super(proxyType = TargetType.class) Object delegate) {
preGetHandle();
Object result = null;
try {
result = method.invoke(delegate, allArguments);
} catch (InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
postGetHandle();
return result;
}
private static void preGetHandle() {}
private static void postGetHandle() {}
和setter看起来一样。
但是当我设置并从我的 Proxyclass 实例中获取某些内容时,它比初始 Pojo class 实例慢得多(慢 1.5-2 倍)。 难道我做错了什么?我相信,一定有办法让它更快。
感谢任何帮助!
我用以下方式衡量性能
public class Main {
private static final int LOOP_COUNT = 10_000_000;
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
Pojo pojo = new Pojo();
Pojo myProxy = (Pojo) ProxyFactory.getInstance().getProxy(Pojo.class);
testTime(pojo);
testTime(myProxy);
}
private static void testTime(Pojo pojo) {
long startTime = System.currentTimeMillis();
Random random = new Random();
long totalSum = 0;
for (int i = 0; i<LOOP_COUNT; i++){
pojo.setId(random.nextLong());
totalSum += pojo.getId();
}
long endTime = System.currentTimeMillis();
System.out.println(pojo.getClass() + " time = " + (endTime-startTime) + " total= " + totalSum);
}
我的结果是
class Pojo time = 288 total= 1060564671495946244
class ProxyPojo time = 738 total= 5879857558672375335
这种放缓的主要原因是通过反思开展业务的成本。
考虑这个测试:
import java.lang.reflect.Method;
import java.util.Random;
public class Main {
public interface Pojo {
public long getId();
public void setId(long id);
}
public static class SimplePojo implements Pojo {
private long id;
@Override public long getId() { return id; }
@Override public void setId(long id) {this.id = id;}
}
public static class DelegatingPojo implements Pojo {
private final Pojo pojo;
public DelegatingPojo(Pojo p) {this.pojo = p;}
@Override public long getId() { return pojo.getId(); }
@Override public void setId(long id) { pojo.setId(id); }
}
public static class ReflectingPojo implements Pojo {
private final Object delegate;
private final Method getmethod;
private final Method setmethod;
public ReflectingPojo(Pojo p) throws NoSuchMethodException {
this.delegate = p;
this.getmethod = p.getClass().getMethod("getId");
this.setmethod = p.getClass().getMethod("setId", Long.TYPE);
}
@Override public long getId() {
Object result = null;
Object[] allarguments = new Object[0];
try {
result = getmethod.invoke(delegate, allarguments);
} catch (InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
return (Long)result;
}
@Override public void setId(long id) {
Object[] allarguments = new Object[]{id};
try {
setmethod.invoke(delegate, allarguments);
} catch (InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
return;
}
}
public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException {
Pojo pojo = new SimplePojo();
Pojo proxy = new DelegatingPojo(pojo);
Pojo refl = new ReflectingPojo(pojo);
testTime(pojo);
testTime(proxy);
testTime(refl);
}
private static final int LOOP_COUNT = 10_000_000;
private static void testTime(Pojo pojo) {
long startTime = System.currentTimeMillis();
Random random = new Random();
for (int i = 0; i < LOOP_COUNT; i++) {
pojo.setId(random.nextLong());
pojo.getId();
}
long endTime = System.currentTimeMillis();
System.out.println(pojo.getClass() + " time = " + (endTime - startTime));
}
}
我得到以下结果:
class Main$SimplePojo time = 295
class Main$DelegatingPojo time = 328
class Main$ReflectingPojo time = 544
如您所见,使用反射的速度大约是原来的两倍。这不应该是一个巨大的惊喜,那些 try 块和 method.invoke 调用意味着你正在做一大堆基本 pojo 不需要做的额外工作。
恐怕这只是部分答案...反思会减慢您的速度,因此要跳过反思,您的解决方案需要采用以下形式:
PojoProxyClass = new ByteBuddy()
.subclass(myPojo)
.method(ElementMatchers.nameStartsWith("get"))
.intercept(
MethodDelegation.to(<pre method>)
.andThen(MethodDelegation.to(<getter on the proxy>)
.andThen(MethodDelegation.to(<post method>)))
)
...
唉,我不知道bytebuddy巫术到底是什么。都在某处。
如前所述,您应该避免反射调用。在 Byte Buddy 中,使用 @SuperCall
注入:
public class GetterInterceptor {
@RuntimeType
public static Object intercept(@SuperCall Callable<?> zuper) throws Exception {
preGetHandle();
try {
return zuper.call();
} finally {
postGetHandle();
}
}
private static void preGetHandle() {}
private static void postGetHandle() {}
}
对于 setter,您不需要 return 一个值,因此您可以使用可运行的。