article/java/spring.md
2025-01-06 13:34:03 +08:00

3.4 KiB

通过 Spring 5 中 Supplier 来获取 Bean

Spring5 中开始提供了 Supplier,可以通过接口回调获取到一个 Bean 的实例,这种方式显然性能更好一些。

如下:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(Book.class);
definition.setInstanceSupplier((Supplier<Book>) () -> {
    Book book = new Book();
    book.setName("深入浅出 Spring Security");
    book.setAuthor("江南一点雨");
    return book;
});
ctx.registerBeanDefinition("b1", definition);
ctx.refresh();
Book b = ctx.getBean("b1", Book.class);
System.out.println("b = " + b);

关键就是通过调用 BeanDefinitionsetInstanceSupplier 方法去设置回调。当然,上面这段代码还可以通过 Lambda 进一步简化:

public class BookSupplier {
    public Book getBook() {
        Book book = new Book();
        book.setName("深入浅出 Spring Security");
        book.setAuthor("江南一点雨");
        return book;
    }
}

然后调用这个方法即可:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(Book.class);
BookSupplier bookSupplier = new BookSupplier();
definition.setInstanceSupplier(bookSupplier::getBook);
ctx.registerBeanDefinition("b1", definition);
ctx.refresh();
Book b = ctx.getBean("b1", Book.class);
System.out.println("b = " + b);

这是不是更有一点 Lambda 的感觉了?

在 Spring 源码中,处理获取 Bean 实例的时候,有如下一个分支,就是处理 Supplier 这种情况的:

AbstractAutowireCapableBeanFactory#createBeanInstance
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
    // Make sure bean class is actually resolved at this point.
    Class<?> beanClass = resolveBeanClass(mbd, beanName);
    if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
        throw new BeanCreationException(mbd.getResourceDescription(), beanName,
        "Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
    }
    Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
    if (instanceSupplier != null) {
        return obtainFromSupplier(instanceSupplier, beanName);
    }
    if (mbd.getFactoryMethodName() != null) {
        return instantiateUsingFactoryMethod(beanName, mbd, args);
    }
    //...
    return instantiateBean(beanName, mbd);
}

@Nullable
private Object obtainInstanceFromSupplier(Supplier<?> supplier, String beanName) {
    String outerBean = this.currentlyCreatedBean.get();
    this.currentlyCreatedBean.set(beanName);
    try {
        if (supplier instanceof InstanceSupplier<?> instanceSupplier) {
            return instanceSupplier.get(RegisteredBean.of((ConfigurableListableBeanFactory) this, beanName));
        }
        if (supplier instanceof ThrowingSupplier<?> throwableSupplier) {
            return throwableSupplier.getWithException();
        }
        return supplier.get();
    }
}

上面 obtainFromSupplier 这个方法,最终会调用到第二个方法。第二个方法中的 supplier.get(); 其实最终就调用到我们自己写的 getBook 方法了。

如上,这是从 Spring5 开始结合 Lamdba 的一种 Bean 注入方式。