【发布时间】:2021-09-23 08:06:20
【问题描述】:
目标:(正如问题标题所说):使用 CGLib 或任何其他反射库(例如 ByetBuddy?)有效地创建代理(域)对象
拥有我们的领域类(请注意 Lombok 注释):
@Getter
@Setter
@RequiredArgsConstructor
public class DomainFoo {
@NonNull
private final Integer id;
// some other fields, final or otherwise!
public void doSomething() {
// do something here!
}
}
我正在尝试创建一个 DomainFoo 类型的代理对象,它只响应具有给定 ID(见下文)的 getId 方法,否则(调用任何其他方法)它会抛出一个 UnsupportedOperationException。
我设法使用 GCLib(如果重要的话是 Spring 版本)做到了这一点:
public static final ObjenesisStd OBJENESIS = new ObjenesisStd();
public static Factory newFoo(Integer id) {
val enhancer = new Enhancer();
enhancer.setSuperclass(domainClass);
enhancer.setCallbackType(MethodInterceptor.class);
val proxyClass = enhancer.createClass();
// Since there's no default constructor in domains:
val instantiator = OBJENESIS.getInstantiatorOf(proxyClass);
val proxyInstance = instantiator.newInstance();
val factory = (Factory) proxyInstance;
factory.setCallbacks(new Callback[]{new DomainProxyInterceptor(id)});
return factory; // see below for why it's called factory!
}
DomainProxyInterceptor 在哪里:
@RequiredArgsConstructor
private class DomainProxyInterceptor implements MethodInterceptor {
@NonNull
private final Integer id;
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy proxy) throws Throwable {
if (method.getDeclaringClass() != Object.class && method.getName().equals("getId")) {
return id;
} else {
throw new NotSupportedException(method.getName());
}
}
}
一切正常。现在为了提高效率,我正在尝试缓存 factory 并实际使用它作为工厂:
// newFooFactory is the above method rename!
private static final Factor FOO_FACTORY = newFooFactory(0);
public static DomainFoo newFoo(Integer id) {
val callbacks = new Callback[]{new DomainProxyInterceptor(id)};
return (DomainFoo) FOO_FACTORY.newInstance(callbacks);
}
但是调用上面的方法会抛出一个NoSuchMethodError:
Exception in thread "main" java.lang.NoSuchMethodError: com/example/demo/DomainFoo$$EnhancerByCGLIB$$8423c3c4.<init>()V (loaded from file:/Users/rad/works/demo/target/classes/ by jdk.internal.loader.ClassLoaders$AppClassLoader@85a856f6) called from class com.example.demo.DomainFoo$$EnhancerByCGLIB$$8423c3c4 (loaded from file:/Users/rad/works/demo/target/classes/ by jdk.internal.loader.ClassLoaders$AppClassLoader@85a856f6).
at com.example.demo.DomainFoo$$EnhancerByCGLIB$$8423c3c4.newInstance(<generated>)
at com.example.demo.DomainFactory.proxyFoo(DomainFactory.java:28)
at com.example.demo.DemoApplication.main(DemoApplication.java:6)
这大概是因为Factory.newInstance 尝试使用普通构造函数但它不存在(因为我们使用Objenesis 创建了对象?)。
我也试过ReflectionFactory来创建工厂实例,但它有同样的问题。
问题(它们有点相关):
- 我需要担心效率吗? (这些对象是普通域,所以它们通常太多了。)
- 是否有更好的方法来完成上述操作?在效率方面还是在其他方面?具体来说,当没有默认构造函数时,使用 GCLib 实例化的正确方法是什么?
- 有没有办法解决上述问题?解决
NoSuchMethodError失败? - 我可以尝试任何其他库或解决方案吗?
谢谢。
【问题讨论】:
-
那些不是反射库,而是字节码生成器。 Byte Buddy 是在 Java 17 之后仍然有效的替代方案(注意:我是作者),它当然允许您创建这样的类型。 Byte Buddy 不提供实例化,因为这应该由用户完成。默认情况下,它不会缓存类型,因为您可以创建更高效的类型。
-
感谢您的评论。你是对的。修正了标题。 > 默认情况下,它不会缓存类型,因为您可以创建更高效的类型。不确定这是否是拼写错误。所以它确实缓存了它,我不需要担心它。正确的?另外,请您提供一个代码 sn-p 开始吗?或者分享一个链接什么的?谢谢。
-
构造函数还有其他需要的参数,还是
id是唯一需要的? -
确实有。这就是直接使用构造函数无法扩展的原因。
标签: java reflection byte-buddy cglib objenesis