如何使用CGLib等字节码库高效创建无默认构造的Java领域代理对象
问题1:是否需要担心该场景的效率问题?
如果领域对象单批次创建量在万级以上,需要做优化,否则可以不用过度提前优化。
CGLib的性能开销核心来自首次运行时生成代理类的字节码生成、类加载过程,这部分开销是一次性的。如果复用代理类,后续仅通过Objenesis实例化、设置回调的开销非常低,和普通对象实例化的性能差距在一个数量级以内,完全可以支撑十万甚至百万级的实例创建需求。
问题2:更优实现方式、CGLib无默认构造函数的正确实例化方案
无默认构造函数的CGLib实例化两种正确姿势:
- 显式调用父类构造函数:调用
Enhancer.create(Class[] argumentTypes, Object[] arguments)方法,传入和父类构造匹配的参数类型、参数值,会自动调用父类对应构造完成实例化。该方案适合你需要初始化父类其他字段的场景。 - 跳过构造函数实例化:就是你当前用的Objenesis方案,完全跳过构造函数执行,不会初始化父类的任何字段,也不需要父类存在无参构造,非常适合你当前只需要响应
getId方法、不需要其他字段的场景,性能比调用构造的方案更高。
最优优化方案
你只需要缓存代理类和对应的Objenesis实例化器即可,不需要缓存Factory实例,代码示例如下:
public class DomainProxyFactory { public static final ObjenesisStd OBJENESIS = new ObjenesisStd(); // 缓存代理类 private static final Class<?> DOMAIN_FOO_PROXY_CLASS; // 缓存实例化器 private static final ObjectInstantiator<?> DOMAIN_FOO_INSTANTIATOR; static { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(DomainFoo.class); enhancer.setCallbackType(MethodInterceptor.class); DOMAIN_FOO_PROXY_CLASS = enhancer.createClass(); DOMAIN_FOO_INSTANTIATOR = OBJENESIS.getInstantiatorOf(DOMAIN_FOO_PROXY_CLASS); } public static DomainFoo newFoo(Integer id) { DomainFoo proxy = (DomainFoo) DOMAIN_FOO_INSTANTIATOR.newInstance(); ((Factory) proxy).setCallbacks(new Callback[]{new DomainProxyInterceptor(id)}); return proxy; } @RequiredArgsConstructor private static 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.getName().equals("getId")) { return id; } throw new UnsupportedOperationException(method.getName() + " is not supported"); } } }
该方案把字节码生成、类加载、实例化器创建的开销都放到类加载阶段执行,运行时创建实例仅需要实例化对象、设置回调两步,性能达到最高。
问题3:修复NoSuchMethodError报错
报错原因确实是Factory.newInstance()的无参重载默认尝试调用代理类的无参构造,而你的代理类继承自没有无参构造的DomainFoo,生成的代理类也没有无参构造,所以调用失败。
两种修复方案:
- 改用问题2中给出的缓存代理类+实例化器的方案,完全不使用
Factory.newInstance()方法,从根源避免该问题。 - 如果你一定要用
Factory.newInstance(),可以调用带构造参数的重载方法,显式传入父类构造的参数类型和值:
public static DomainFoo newFoo(Integer id) { val callbacks = new Callback[]{new DomainProxyInterceptor(id)}; // 传入父类构造的参数类型、参数值,匹配@RequiredArgsConstructor生成的Integer参数构造 return (DomainFoo) FOO_FACTORY.newInstance(new Class[]{Integer.class}, new Object[]{id}, callbacks); }
注意该方案会执行父类DomainFoo的构造函数,会初始化所有final字段,如果你的父类构造有其他逻辑也会执行,不符合你仅需要getId返回指定值的需求,更推荐第一种方案。
问题4:其他可选方案
- ByteBuddy:比CGLib更活跃,API设计更友好,对JDK 9+的模块化、高版本JDK的支持更好,性能和CGLib相当。你可以用ByteBuddy生成代理类,同样可以搭配Objenesis实例化,代码可读性、可维护性比CGLib更高。
- 手写静态代理类:如果你的
DomainFoo类的方法变更频率很低,可以直接手写一个DomainFoo的静态代理子类,重写getId返回指定值,其他方法全部抛出异常,该方案没有任何运行时字节码开销,性能是所有方案中最高的,唯一缺点是需要和父类方法同步变更。 - 如果你后续会抽取领域类的公共接口:可以直接用JDK动态代理实现,不需要引入第三方字节码库,兼容性最好。
内容的提问来源于stack exchange,提问作者Rad
相关产品推荐
相关产品推荐

