【问题标题】:How efficiently create Java proxy object using CGLib (or any other reflection/byte-code library)使用 CGLib(或任何其他反射/字节码库)如何有效地创建 Java 代理对象
【发布时间】: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来创建工厂实例,但它有同样的问题。

问题(它们有点相关):

  1. 我需要担心效率吗? (这些对象是普通域,所以它们通常太多了。)
  2. 是否有更好的方法来完成上述操作?在效率方面还是在其他方面?具体来说,当没有默认构造函数时,使用 GCLib 实例化的正确方法是什么?
  3. 有没有办法解决上述问题?解决NoSuchMethodError 失败?
  4. 我可以尝试任何其他库或解决方案吗?

谢谢。

【问题讨论】:

  • 那些不是反射库,而是字节码生成器。 Byte Buddy 是在 Java 17 之后仍然有效的替代方案(注意:我是作者),它当然允许您创建这样的类型。 Byte Buddy 不提供实例化,因为这应该由用户完成。默认情况下,它不会缓存类型,因为您可以创建更高效​​的类型。
  • 感谢您的评论。你是对的。修正了标题。 > 默认情况下,它不会缓存类型,因为您可以创建更高效​​的类型。不确定这是否是拼写错误。所以它确实缓存了它,我不需要担心它。正确的?另外,请您提供一个代码 sn-p 开始吗?或者分享一个链接什么的?谢谢。
  • 构造函数还有其他需要的参数,还是id是唯一需要的?
  • 确实有。这就是直接使用构造函数无法扩展的原因。

标签: java reflection byte-buddy cglib objenesis


【解决方案1】:

Cglib 和 ByteBuddy 是 2 个创建代理的好库。 Objenesis 会在上面做更多的事情。它将阻止调用代理类的构造函数。您可以查看org.easymock.internal.ClassProxyFactory 或使用它来创建这样的代理。它与您的代码非常相似。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-31
    • 1970-01-01
    • 2021-09-13
    • 1970-01-01
    • 2020-03-24
    • 1970-01-01
    相关资源
    最近更新 更多