使用 Cglib 和 Objenesis 实现 java 对象懒加载

491 阅读4分钟

普通懒加载

使用懒加载容器包裹对象

public class LazyReference<T> {
    private final SupplierEx<? extends T> supplier;
    private volatile T value;


    public LazyReference(SupplierEx<? extends T> supplier) {
        Assert.notNull(supplier, "supplier can not be null");
        this.supplier = supplier;
    }

    public T get() {
        if (value == null) synchronized (this) {
            if (value == null) {
                value = supplier.get();
            }
        }
        return value;
    }
}

ps: SupplierEx 是一个自定义的Supplier的扩展,可以很方便的在lambda表达式中抛异常而不是强制catch

import lombok.SneakyThrows;

import java.util.function.Supplier;

/**
 * 用于lambda摆脱java的异常捕捉限制(并非包装为runtime)
 */
@FunctionalInterface
public interface SupplierEx<T> extends Supplier<T> {

    T getEx() throws Exception;

    @Override
    @SneakyThrows
    default T get() {
        return getEx();
    }
}

使用字节码懒加载

对于很多情况下,不方便使用容器的时候,就必须使用动态代理来完成懒加载了,这里使用Cglib来实现

import lombok.experimental.UtilityClass;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.LazyLoader;
/**
 * 懒加载工具类,可以使用{@link LazyReference} 或者 Enhancer 动态代理创建懒加载对象
 */
@UtilityClass
public class LazyLoaderUtil {
    public static <T> LazyReference<T> lazyReference(SupplierEx<? extends T> lazyLoader) {
        return new LazyReference<>(lazyLoader);
    }
    @SuppressWarnings("unchecked")
    public static <T> T lazyLoad(Class<T> clazz, LazyLoader lazyLoader) {
        return (T) Enhancer.create(clazz, lazyLoader);
    }
}

LazyLoader 是Cglib 的一种Callback,因为只有一个方法,可以视为函数式接口以lambda表达式构建

public interface LazyLoader extends Callback {
    Object loadObject() throws Exception;
}

LazyLoaderUtil 在使用时,就是这样的:

@Test
void test_2022_08_04_09_39_34() throws InterruptedException {
    MyBean myBean = LazyLoaderUtil.lazyLoad(MyBean.class, MyBeanImpl::new);
    System.out.println(myBean.helloProxy());
    AtomicLong start = new AtomicLong();
    myBean = LazyLoaderUtil.lazyLoad(MyBeanImpl.class, () -> {
        MyBeanImpl myBean22 = new MyBeanImpl();
        myBean22.name = "myBean22";
        start.set(System.nanoTime());
        return myBean22;
    });
    long end = System.nanoTime();
    TimeUnit.NANOSECONDS.sleep(5000000);
    System.out.println(myBean.helloProxy());
    Assertions.assertTrue(start.get() - end > 5000000);//创建时间晚于方法结束时间,代表延迟创建
}

interface MyBean {
    String helloProxy();
}

static class MyBeanImpl implements MyBean {
    private String name = getClass().getSimpleName();

    @Override
    public String helloProxy() {
        return "Hello Proxy, I'm " + name;
    }
}

但是有一个问题是,如果要延迟加载的是没有默认构造函数的类,就必须在Enhancer创建动态代理的时候使用有参构造

Enhancer e = new Enhancer();
e.setSuperclass(type);
e.setCallback(callback);
//需要参数类型和参数
e.create(constructorArgTypes, constructorArgs);

这太不方便了!我们要避开构造参数! 从手牌中发动 Objenesis

使用SpringObjenesis跳过构造方法

objenesis是一个小型java类库用来实例化一个特定class的对象,可以跳过构造方法 我们参考org.springframework.aop.framework.ObjenesisCglibAopProxy中的写法来写一个CglibUtil,这也是Spring中默认创建动态代理的方式

import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.Factory;
import org.springframework.cglib.proxy.NoOp;
import org.springframework.objenesis.SpringObjenesis;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;

@Slf4j
@UtilityClass
public class CglibUtil {
    private static final SpringObjenesis objenesis = new SpringObjenesis();

    @SuppressWarnings("unchecked")
    public static <T> T create(@NotNull Class<T> type, @Nullable Callback... callbacks) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(type);
        return (T) createProxyClassAndInstance(enhancer, callbacks, null, null);
    }

    public static Object createProxyClassAndInstance(@NotNull Enhancer enhancer, @Nullable Callback[] callbacks, @Nullable Class<?>[] constructorArgTypes, @Nullable Object[] constructorArgs) {
        if (callbacks == null || callbacks.length == 0) {
            callbacks = new Callback[]{NoOp.INSTANCE};
        }
        List<Class<?>> classes = new ArrayList<>(callbacks.length);
        for (Callback callback : callbacks) {
            if (callback != null) {
                classes.add(callback.getClass());
            }
        }
        enhancer.setCallbackTypes(classes.toArray(new Class[0]));
        Class<?> proxyClass = enhancer.createClass();
        Object proxyInstance = null;
        if (objenesis.isWorthTrying()) {
            try {
                //使用Objenesis跳过构造方法
                proxyInstance = objenesis.newInstance(proxyClass, enhancer.getUseCache());
            } catch (Throwable ex) {
                log.debug("Unable to instantiate proxy using Objenesis, falling back to regular proxy construction", ex);
            }
        }
        if (proxyInstance == null) {
            // Regular instantiation via default constructor...
            try {
                Constructor<?> ctor = (constructorArgs != null ? proxyClass.getDeclaredConstructor(constructorArgTypes) : proxyClass.getDeclaredConstructor());
                ReflectionUtils.makeAccessible(ctor);
                proxyInstance = (constructorArgs != null ? ctor.newInstance(constructorArgs) : ctor.newInstance());
            } catch (Throwable ex) {
                log.debug("Unable to instantiate proxy using Objenesis, and regular proxy instantiation via default constructor fails as well", ex);
                enhancer.setCallbacks(callbacks);
                return constructorArgs != null ? enhancer.create(constructorArgTypes, constructorArgs) : enhancer.create();
            }
        }
        ((Factory) proxyInstance).setCallbacks(callbacks);
        return proxyInstance;
    }
}

然后将之前的LazyLoaderUtil使用我们新写的CglibUtil:

import lombok.experimental.UtilityClass;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.LazyLoader;
/**
 * 懒加载工具类,可以使用{@link LazyReference} 或者 Enhancer 动态代理创建懒加载对象
 */
@UtilityClass
public class LazyLoaderUtil {
    public static <T> LazyReference<T> lazyReference(SupplierEx<? extends T> lazyLoader) {
        return new LazyReference<>(lazyLoader);
    }
    public static <T> T lazyLoad(Class<T> clazz, LazyLoader lazyLoader) {
        return CglibUtil.create(clazz, lazyLoader);
    }
}

我们来测试一下能否避开构造函数:

@Test
void test_2022_08_04_9_49_49() {
    String uuid = UUID.randomUUID().toString();
    TestBean hhh = LazyLoaderUtil.lazyLoad(TestBean.class, () -> new TestBean(uuid));
    //构造函数中赋值的final对象是null,说明创建的代理对象的父构造方法没有被调用
    //(这里IDEA都提示永不为null了,但是依然为null 嘿嘿嘿嘿)
    Assertions.assertNull(hhh.name);
    Assertions.assertEquals(uuid, hhh.getName());
}

static class TestBean {
    private final String name;

    public TestBean(String name) {
        this.name = (name == null ? "不可以是null哦" : name);
        System.out.println(name);
    }

    public String getName() {
        return name;
    }
}

完美避开!

最终测试

那我们再最后来测试一下,看看LazyLoaderUtil能否安全的兼容大部分SpringBean:

import org.junit.jupiter.api.Test;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cglib.core.TypeUtils;

import java.util.ArrayList;
import java.util.List;

@SpringBootTest
@AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.H2)
public class LazyLoaderUtilTest {
    @Autowired
    ListableBeanFactory listableBeanFactory;

    @Test
    void test_2022_08_04_10_16_01() {
        List<Object> beans = new ArrayList<>();
        for (String beanDefinitionName : listableBeanFactory.getBeanDefinitionNames()) {
            Object bean = listableBeanFactory.getBean(beanDefinitionName);
            //找到bean的真实类,不能代理代理类
            Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
            if (TypeUtils.isFinal(targetClass.getModifiers())) {
                //final 的类不能代理,我们跳过
                continue;
            }
            beans.add(LazyLoaderUtil.lazyLoad(targetClass, () -> bean));
        }
        System.out.println(beans);
    }
}

测试通过,非常完美!

PS: 如果结合静态获取SpringBean的SpringUtilLazyLoaderUtil会变得更香
结合SpringUtil之后的最终形态:

/**
 * 懒加载工具类,可以使用{@link LazyReference} 或者 Enhancer 动态代理创建懒加载对象
 */
@UtilityClass
public class LazyLoaderUtil {
    public static <T> LazyReference<T> lazyReference(SupplierEx<? extends T> lazyLoader) {
        return new LazyReference<>(lazyLoader);
    }

    public static <T> LazyReference<T> lazyReferenceBean(Class<? extends T> beanClass) {
        return new LazyReference<>(() -> SpringUtil.getBean(beanClass));
    }

    public static <T> LazyReference<T> lazyReferenceBean(Class<? extends T> beanClass, T defaultValue) {
        return new LazyReference<>(() -> SpringUtil.getBeanOrDefault(beanClass, defaultValue));
    }

    public static <T> LazyReference<T> lazyReferenceBean(Class<? extends T> beanClass, SupplierEx<? extends T> supplierEx) {
        return new LazyReference<>(() -> SpringUtil.getBeanOrDefault(beanClass, supplierEx));
    }

    public static <T> LazyReference<T> lazyReferenceBean(String beanName, Class<? extends T> beanClass) {
        return new LazyReference<>(() -> SpringUtil.getBean(beanName, beanClass));
    }

    public static <T> T lazyLoad(Class<T> clazz, LazyLoader lazyLoader) {
        return CglibUtil.create(clazz, lazyLoader);
    }

    public static <T> T lazyLoad(Class<? extends T> clazz, SupplierEx<? extends T> lazyLoader) {
        return CglibUtil.create(clazz, (LazyLoader) lazyLoader::getEx);
    }

    public static <T> T lazyLoadBean(Class<? extends T> beanClass) {
        return CglibUtil.create(beanClass, (LazyLoader) () -> SpringUtil.getBean(beanClass));
    }


    public static <T> T lazyLoadBean(Class<? extends T> beanClass, T defaultValue) {
        return CglibUtil.create(beanClass, (LazyLoader) () -> SpringUtil.getBeanOrDefault(beanClass, defaultValue));
    }

    public static <T> T lazyLoadBean(Class<? extends T> beanClass, SupplierEx<? extends T> supplierEx) {
        return CglibUtil.create(beanClass, (LazyLoader) () -> SpringUtil.getBeanOrDefault(beanClass, supplierEx));
    }

    public static <T> T lazyLoadBean(String beanName, Class<? extends T> beanClass) {
        return CglibUtil.create(beanClass, (LazyLoader) () -> SpringUtil.getBean(beanName, beanClass));
    }
}